🐝
【Flutter】枠線を光らせる Container & CustomPaint (InnerShadow)
今回やること
今回は小ネタです 💡
枠線を光らせます。
光っているよう見せます。
想像はつくと思いますが Shadow のお話です。
Flutter では InnerShadow が実装されていないので、
自作で表現する必要があります。
今回は 2 種類の方法で InnerShadow を表現しました。
- Container に小さい Container を入れて表現
- CustomPaint を使って表現
どのように表現しているか
簡単に言うと重ねているだけです。
ベースの Container に dropShadow、border、bgColor を付けて
その上に小さい Container をのせて、この Container にも dropShadow を付けます。
CustomPaint の場合は、OuterShadow(緑)→InnerShadow(グレー)→border(薄緑) の順に重ねています。
Container Ver.
class ShiningContainer extends StatelessWidget {
final Color borderColor;
final Color outerColor;
final Color innerColor;
final double radius;
final double thickness;
final Widget child;
final double? width;
final double? height;
const ShiningContainer({
Key? key,
required this.borderColor,
required this.outerColor,
required this.innerColor,
required this.radius,
required this.thickness,
required this.child,
this.width,
this.height,
}) : super(key: key);
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
padding: const EdgeInsets.all(10), // paddingで内側のContainerを小さくしている
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius),
border: Border.all(color: borderColor, width: thickness),
color: outerColor,
boxShadow: [
BoxShadow(
color: outerColor,
spreadRadius: 1,
blurRadius: 15,
),
],
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius - thickness),
color: innerColor,
boxShadow: [
BoxShadow(
color: innerColor,
spreadRadius: 7,
blurRadius: 7,
),
],
),
child: child),
);
}
}
CustomPaint Ver.
class ShiningBorderPainter extends CustomPainter {
final Color borderColor;
final Color outerColor;
final Color innerColor;
final double radius;
final double thickness;
ShiningBorderPainter(
{required this.borderColor,
required this.outerColor,
required this.innerColor,
required this.radius,
required this.thickness});
void paint(Canvas canvas, Size size) {
final shadowWidth = thickness / 1.5;
final Paint borderPaint = Paint()
..color = borderColor
..style = PaintingStyle.stroke
..strokeWidth = thickness;
final outerPaint = Paint()
..color = outerColor
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8.0);
final innerPaint = Paint()
..color = innerColor
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8.0);
final RRect outerRect = RRect.fromLTRBR(0, 0, size.width, size.height, Radius.circular(radius));
final RRect borderRect = RRect.fromLTRBR(
shadowWidth, shadowWidth, size.width - shadowWidth, size.height - shadowWidth, Radius.circular(radius));
final RRect innerRect = RRect.fromLTRBR(shadowWidth * 2, shadowWidth * 2, size.width - shadowWidth * 2,
size.height - shadowWidth * 2, Radius.circular(radius));
canvas.drawRRect(outerRect, outerPaint);
canvas.drawRRect(innerRect, innerPaint);
canvas.drawRRect(borderRect, borderPaint);
}
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
終わりに
簡単ではありますが、1 つの表現方法として紹介させていただきました。
もっと簡単に InnerShadow が実現できれば良いのですが。
他に良い方法があればコメントいただけると嬉しいです。
コード全文
import 'package:flutter/material.dart';
class ShiningBorderPage extends StatelessWidget {
const ShiningBorderPage({super.key});
Widget build(BuildContext context) {
final Color _shadowColor = Colors.green.shade400;
final Color _borderColor = Colors.green.shade100;
return Scaffold(
backgroundColor: Colors.grey.shade900,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ShiningContainer(
width: 400,
height: 150,
outerColor: _shadowColor,
borderColor: _borderColor,
innerColor: Colors.grey.shade900,
radius: 24,
thickness: 4,
child: const Center(
child: Text('Container',
style: TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold)),
)),
SizedBox(
width: 400,
height: 150,
child: CustomPaint(
painter: ShiningBorderPainter(
outerColor: _shadowColor,
borderColor: _borderColor,
innerColor: Colors.grey.shade900,
radius: 24,
thickness: 4),
child: const Center(
child: Text('CustomPaint',
style: TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold)),
)),
)
],
),
));
}
}
class ShiningContainer extends StatelessWidget {
final Color borderColor;
final Color outerColor;
final Color innerColor;
final double radius;
final double thickness;
final Widget child;
final double? width;
final double? height;
const ShiningContainer({
Key? key,
required this.borderColor,
required this.outerColor,
required this.innerColor,
required this.radius,
required this.thickness,
required this.child,
this.width,
this.height,
}) : super(key: key);
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius),
border: Border.all(color: borderColor, width: thickness),
color: outerColor,
boxShadow: [
BoxShadow(
color: outerColor,
spreadRadius: 1,
blurRadius: 15,
),
],
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius - thickness),
color: innerColor,
boxShadow: [
BoxShadow(
color: innerColor,
spreadRadius: 7,
blurRadius: 7,
),
],
),
child: child),
);
}
}
class ShiningBorderPainter extends CustomPainter {
final Color borderColor;
final Color outerColor;
final Color innerColor;
final double radius;
final double thickness;
ShiningBorderPainter(
{required this.borderColor,
required this.outerColor,
required this.innerColor,
required this.radius,
required this.thickness});
void paint(Canvas canvas, Size size) {
final shadowWidth = thickness / 1.5;
final Paint borderPaint = Paint()
..color = borderColor
..style = PaintingStyle.stroke
..strokeWidth = thickness;
final outerPaint = Paint()
..color = outerColor
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8.0);
final innerPaint = Paint()
..color = innerColor
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8.0);
final RRect outerRect = RRect.fromLTRBR(0, 0, size.width, size.height, Radius.circular(radius));
final RRect borderRect = RRect.fromLTRBR(
shadowWidth, shadowWidth, size.width - shadowWidth, size.height - shadowWidth, Radius.circular(radius));
final RRect innerRect = RRect.fromLTRBR(shadowWidth * 2, shadowWidth * 2, size.width - shadowWidth * 2,
size.height - shadowWidth * 2, Radius.circular(radius));
canvas.drawRRect(outerRect, outerPaint);
canvas.drawRRect(innerRect, innerPaint);
canvas.drawRRect(borderRect, borderPaint);
}
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
Discussion