1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutterでインナーシャドウ

Posted at

Flutterでインナーシャドウを描画するコード、検索すると出てくるものが軒並みうまく描画できなかったので自作しました。数年前に調べたときはうまくレンダリングできていた気がするのですが…。

  • 本記事執筆時点のFlutterのバージョン: 3.24.4

結論のコード

CustomPainterを使う方式で実装しました。ざっくり言うと単純にインナーシャドウを描画したい部分に穴を開けたパスを作って、そのパスを描画する際にMaskFilterでシャドウを付けています。

import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class InnerShadowPainter extends CustomPainter {
  const InnerShadowPainter({
    required this.shadows,
    this.borderRadius,
  });

  final List<Shadow> shadows;
  final BorderRadius? borderRadius;

  RRect applyBorderRadius(Rect rect, BorderRadius borderRadius) {
    return RRect.fromRectAndCorners(
      rect,
      topLeft: borderRadius.topLeft,
      topRight: borderRadius.topRight,
      bottomLeft: borderRadius.bottomLeft,
      bottomRight: borderRadius.bottomRight,
    );
  }

  Path createShadowPath(Rect rect, Shadow shadow) {
    final outerRect = rect.inflate(max(shadow.offset.dx.abs(), shadow.offset.dy.abs()) + shadow.blurRadius + 1);
    final path = Path()
      ..fillType = PathFillType.evenOdd
      ..addRect(outerRect);
    final innerRect = rect.translate(shadow.offset.dx, shadow.offset.dy);
    if (borderRadius != null) {
      return path..addRRect(applyBorderRadius(innerRect, borderRadius!));
    } else {
      return path..addRect(innerRect);
    }
  }

  @override
  void paint(Canvas canvas, Size size) {
    final rect = Offset.zero & size;

    canvas.save();

    if (borderRadius != null) {
      canvas.clipRRect(applyBorderRadius(rect, borderRadius!));
    } else {
      canvas.clipRect(rect);
    }

    for (final shadow in shadows) {
      final shadowPath = createShadowPath(rect, shadow);
      final shadowPaint = Paint()
        ..color = shadow.color
        ..maskFilter = MaskFilter.blur(BlurStyle.normal, shadow.blurSigma);
      canvas
        ..translate(shadow.offset.dx, shadow.offset.dy)
        ..drawPath(shadowPath, shadowPaint)
        ..translate(-shadow.offset.dx, -shadow.offset.dy);
    }

    canvas.restore();
  }

  @override
  bool shouldRepaint(InnerShadowPainter oldDelegate) {
    return !listEquals(shadows, oldDelegate.shadows) || borderRadius != oldDelegate.borderRadius;
  }
}

class InnerShadow extends StatelessWidget {
  const InnerShadow({
    super.key,
    required this.shadows,
    this.borderRadius,
    required this.child,
  });

  final List<Shadow> shadows;
  final BorderRadius? borderRadius;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      foregroundPainter: InnerShadowPainter(
        shadows: shadows,
        borderRadius: borderRadius,
      ),
      child: child,
    );
  }
}

使い方

InnerShadow(
  shadows: [
    Shadow(
      color: Color.fromRGBO(0,0,0,0.5),
      blurRadius: 2.0,
      offset: Offset(1, 1),
    ),
  ],
  child: Container(
    width: 200,
    height: 200,
    alignment: Alignment.center,
    color: Colors.white,
  ),
)

結果

result.png

おわりに

最初はsaveLayerとBlendModeをこねくり回していたのですが、よく考えたら素直な方法で描画できました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?