Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Add Material touch ripples / FlutterKaigi 2023

Add Material touch ripples / FlutterKaigi 2023

kosuke matsumura

November 09, 2023
Tweet

More Decks by kosuke matsumura

Other Decks in Technology

Transcript

  1. • Android / iOS / Flutter • 個⼈でもFlutter製アプリを公開 • X(Twitter)

    / @kosuke_mtm  2 松村航裕 / Kosuke Matsumura ⾃⼰紹介
  2. 設⽴⽇  3 会社概要 NAVITIME JAPAN 2000年 3⽉ 1⽇ 約420名

    (2023年10⽉現在) 社員数 約80%がエンジニア 経営理念 経路探索エンジンの技術で 
 世界の産業に奉仕する
  3.  4 事業領域 NAVITIME JAPAN メディア‧トラベル事業 ドライブ事業 ツーリング事業 バス‧ウォーキング事業 キャリア協業事業

    MaaS/CASE/交通データ事業 ビジネスナビタイム事業 ソリューション事業 公共交通事業 運転代⾏事業 ロケーションマーケティング事業 スポーツビジネス事業
  4.  5 事業領域 NAVITIME JAPAN メディア‧トラベル事業 ドライブ事業 ツーリング事業 バス‧ウォーキング事業 キャリア協業事業

    MaaS/CASE/交通データ事業 ビジネスナビタイム事業 ソリューション事業 公共交通事業 運転代⾏事業 ロケーションマーケティング事業 スポーツビジネス事業
  5.  6 事業領域 / Flutter採⽤事業 NAVITIME JAPAN メディア‧トラベル事業 ドライブ事業 ツーリング事業

    バス‧ウォーキング事業 キャリア協業事業 MaaS/CASE/交通データ事業 ビジネスナビタイム事業 ソリューション事業 公共交通事業 運転代⾏事業 ロケーションマーケティング事業 スポーツビジネス事業
  6.  7 事業領域 / Flutter採⽤事業 NAVITIME JAPAN メディア‧トラベル事業 ドライブ事業 ツーリング事業

    バス‧ウォーキング事業 キャリア協業事業 MaaS/CASE/交通データ事業 ビジネスナビタイム事業 ソリューション事業 公共交通事業 運転代⾏事業 ロケーションマーケティング事業 スポーツビジネス事業 ナビタイムのコアである地図やナビゲーションは、 
 Android/iOSそれぞれの独⾃ライブラリを開発‧運⽤しているため、 
 ネイティブ実装のアプリの⽅が多い https://speakerdeck.com/navitimejapan/ fl utterhanabitaimuziyapanfalsexin-gui-apurikai-fa-dehui-ku
  7. Agenda • タッチエフェクトとは • Material Design 3 Guideline • State

    Layer • 内部実装の解説 • Material • InkWell / InkResponse • タッチエフェクトの付け⽅ • Ink  11 資料中のソースコードはFlutter 3 . 1 3 . 3 のものです。また、簡略化のため⼀部省略および改変しています。 https://github.com/ fl utter/ fl utter/tree/ 3 . 1 3 . 3 /packages/ fl utter/lib/src
  8. ユーザーがオブジェクト(図中のContainer)をタッチしたことを視覚的に認識できるようにしたもの  13 Material Design 3 Guideline タッチエフェクトとは https://m 3

    .material.io/foundations/interaction/states/state-layers コンテンツ State layer Container = Materialウィジェット = Materialウィジェットの表⾯ = Materialウィジェットのchild
  9. 要素の表層を覆う半透過のレイヤーで、透過度で状態を⽰す 
  14 State layer タッチエフェクトとは https://m 3 .material.io/foundations/interaction/states/state-layers#ec

    6 8 aa 4 0 -c 1 aa- 4 1 0 a-b 6 7 7 -e 8 3 f 6 f 2 ba 0 2 1 Hover 不透明度 8 % Focus 不透明度 1 2 % Press 不透明度 1 2 % Drag 不透明度 1 6 % State layerの⾊は、 
 基本的にはonColorが使われる
  10.  15 State layer タッチエフェクトとは コンテンツ State layer Container =

    Materialウィジェット = Materialウィジェットの表⾯ = Materialウィジェットのchild https://m 3 .material.io/foundations/interaction/states/state-layers タッチエフェクトもState layerで描画されます。 次は、Materialウィジェットの内部実装を追ってState layerの実態を探ります。
  11. class Material extends StatefulWidget { … @override Widget build(BuildContext context)

    { … contents = NotificationListener<LayoutChangedNotification>( onNotification: (notification) { … }, child: _InkFeatures( absorbHitTest: widget.type != MaterialType.transparency, color: backgroundColor, child: contents, ), ); …  16 Materialウィジェット MaterialウィジェットにおけるState layer .BUFSJBM
  12. class Material extends StatefulWidget { … @override Widget build(BuildContext context)

    { … contents = NotificationListener<LayoutChangedNotification>( onNotification: (notification) { … }, child: _InkFeatures( absorbHitTest: widget.type != MaterialType.transparency, color: backgroundColor, child: contents, ), ); …  17 Materialウィジェット MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT
  13.  18 Materialウィジェット MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT class _InkFeatures extends

    SingleChildRenderObjectWidget { … @override _RenderInkFeatures createRenderObject(BuildContext context) { return _RenderInkFeatures(…); } @override void updateRenderObject(BuildContext context, _RenderInkFeatures renderObject) { … } }
  14.  19 Materialウィジェット MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT @3FOEFS*OL'FBUVSFT class _InkFeatures

    extends SingleChildRenderObjectWidget { … @override _RenderInkFeatures createRenderObject(BuildContext context) { return _RenderInkFeatures(…); } @override void updateRenderObject(BuildContext context, _RenderInkFeatures renderObject) { … } }
  15.  20 Materialウィジェット MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT class _RenderInkFeatures extends

    RenderProxyBox implements MaterialInkController { … List<InkFeature>? _inkFeatures; @override void addInkFeature(InkFeature feature) { … _inkFeatures!.add(feature); } @override void paint(PaintingContext context, Offset offset) { … for (final InkFeature inkFeature in inkFeatures) { inkFeature._paint(canvas); } } } @3FOEFS*OL'FBUVSFT
  16.  21 Materialウィジェット MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT class _RenderInkFeatures extends

    RenderProxyBox implements MaterialInkController { … List<InkFeature>? _inkFeatures; @override void addInkFeature(InkFeature feature) { … _inkFeatures!.add(feature); } @override void paint(PaintingContext context, Offset offset) { … for (final InkFeature inkFeature in inkFeatures) { inkFeature._paint(canvas); } } } -JTU*OL'FBUVSF @3FOEFS*OL'FBUVSFT
  17. class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController { … List<InkFeature>? _inkFeatures;

    @override void addInkFeature(InkFeature feature) { … _inkFeatures!.add(feature); } @override void paint(PaintingContext context, Offset offset) { … for (final InkFeature inkFeature in inkFeatures) { inkFeature._paint(canvas); } } }  22 Materialウィジェット MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF *OL'FBUVSFͷ഑ྻΛ࢖ͬͯQBJOU͍ͯ͠Δ @3FOEFS*OL'FBUVSFT
  18. class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController { … List<InkFeature>? _inkFeatures;

    @override void addInkFeature(InkFeature feature) { … _inkFeatures!.add(feature); } @override void paint(PaintingContext context, Offset offset) { … for (final InkFeature inkFeature in inkFeatures) { inkFeature._paint(canvas); } } }  23 Materialウィジェット MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF *OL'FBUVSFͷ഑ྻΛ࢖ͬͯQBJOU͍ͯ͠Δ ʮ4UBUF-BZFSʢ.BUFSJBMͷද໘ʣʯͱ͸ @3FOEFS*OL'FBUVSFTͷ͜ͱ @3FOEFS*OL'FBUVSFT *OL'FBUVSF͕ λονΤϑΣΫτͷඳըΛ୲͏
  19. class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController { … List<InkFeature>? _inkFeatures;

    @override void addInkFeature(InkFeature feature) { … _inkFeatures!.add(feature); } @override void paint(PaintingContext context, Offset offset) { … for (final InkFeature inkFeature in inkFeatures) { inkFeature._paint(canvas); } } }  24 InkFeatureの登録 MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController @3FOEFS*OL'FBUVSFT
  20. class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController { … List<InkFeature>? _inkFeatures;

    @override void addInkFeature(InkFeature feature) { … _inkFeatures!.add(feature); } @override void paint(PaintingContext context, Offset offset) { … for (final InkFeature inkFeature in inkFeatures) { inkFeature._paint(canvas); } } }  25 InkFeatureの登録 MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT /// An interface for creating /// [InkSplash]s and [InkHighlight]s on a [Material]. /// /// Typically obtained via [Material.of]. abstract class MaterialInkController { void addInkFeature(InkFeature feature); … } -JTU*OL'FBUVSF MaterialInkController addInkFeature @3FOEFS*OL'FBUVSFT
  21. class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController { … List<InkFeature>? _inkFeatures;

    @override void addInkFeature(InkFeature feature) { … _inkFeatures!.add(feature); } @override void paint(PaintingContext context, Offset offset) { … for (final InkFeature inkFeature in inkFeatures) { inkFeature._paint(canvas); } } }  26 InkFeatureの登録 MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT /// An interface for creating /// [InkSplash]s and [InkHighlight]s on a [Material]. /// /// Typically obtained via [Material.of]. abstract class MaterialInkController { void addInkFeature(InkFeature feature); … } -JTU*OL'FBUVSF MaterialInkController addInkFeature .BUFSJBM*OL$POUSPMMFSʹBEE*OL'FBUVSF͕։͍͍ͯΔ @3FOEFS*OL'FBUVSFT
  22.  27 InkFeatureの登録 MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController addInkFeature

    8JEHFU class Material extends StatefulWidget { static MaterialInkController of(BuildContext context) { final MaterialInkController? controller = maybeOf(context); return controller!; } Material.of(context) @3FOEFS*OL'FBUVSFT .BUFSJBMPGͰ .BUFSJBM*OL$POUSPMMFSʹΞΫηεՄೳ
  23. class InkResponse extends StatelessWidget { … InkFeature _createSplash(…) { final

    MaterialInkController inkController = Material.of(context); … InkFeature? splash; splash = (widget.splashFactory ?? Theme.of(context).splashFactory).create( controller: inkController,…); return splash; } }  28 InkFeatureの登録 MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController addInkFeature *OL8FMM*OL3FTQPOTF @3FOEFS*OL'FBUVSFT ※InkResponseはInkWellの基底クラス
  24. class InkResponse extends StatelessWidget { … InkFeature _createSplash(…) { final

    MaterialInkController inkController = Material.of(context); … InkFeature? splash; splash = (widget.splashFactory ?? Theme.of(context).splashFactory).create( controller: inkController,…); return splash; } }  29 InkFeatureの登録 MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController addInkFeature *OL8FMM*OL3FTQPOTF @3FOEFS*OL'FBUVSFT ※InkResponseはInkWellの基底クラス *OL'FBUVSFΛੜ੒͢ΔTQMBTI'BDUPSZʹ .BUFSJBM*OL$POUSPMMFSΛ౉͍ͯ͠Δ
  25. class InkRipple extends InkFeature { InkRipple({ required MaterialInkController controller, …

    }) : … { … controller.addInkFeature(this); }  30 InkFeatureの登録 MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController addInkFeature *OL3JQQMFFYUFOET *OL'FBUVSF @3FOEFS*OL'FBUVSFT ίϯετϥΫλͰ.BUFSJBM*OL$POUSPMMFSΛड͚औΓ ॳظԽ࣌ʹࣗ෼ΛBEE*OL'FBUVSF͍ͯ͠Δ splashFactory.create() *OL8FMM*OL3FTQPOTF ※⼀例としてInkRippleを取り上げています
  26. class InkRipple extends InkFeature { InkRipple({ required MaterialInkController controller, …

    }) : … { … controller.addInkFeature(this); }  31 InkFeatureの登録 MaterialウィジェットにおけるState layer .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController addInkFeature @3FOEFS*OL'FBUVSFT ίϯετϥΫλͰ.BUFSJBM*OL$POUSPMMFSΛड͚औΓ ॳظԽ࣌ʹࣗ෼ΛBEE*OL'FBUVSF͍ͯ͠Δ splashFactory.create() *OL8FMM*OL3FTQPOTF ※⼀例としてInkRippleを取り上げています *OL8FMMͰੜ੒͞Εͨ*OL3JQQMF౳͕ɺ @3FOEFS*OL'FBUVSFT্Ͱ λονΤϑΣΫτͱͯ͠ඳը͞ΕΔ *OL3JQQMFFYUFOET *OL'FBUVSF
  27. InkWell / InkResponse オブジェクトにタッチエフェクトを付与するには、⼀般的にInkWellを使います  40 class InkWell extends InkResponse

    { const InkWell({ super.key, super.child, super.onTap, super.splashFactory, … bool?… enableFeedback = true, }) : super( containedInkWell: true, highlightShape: BoxShape.rectangle, enableFeedback: enableFeedback ?? true, ); } *OL8FMM͸*OL3FTQPOTFΛܧঝ ྆ऀͱ΋λονΤϑΣΫτΛ෇༩͢ΔͨΊͷ΋ͷ
  28. class InkWell extends InkResponse { const InkWell({ super.key, super.child, super.onTap,

    super.splashFactory, … bool?… enableFeedback = true, }) : super( containedInkWell: true, highlightShape: BoxShape.rectangle, enableFeedback: enableFeedback ?? true, ); } InkWell / InkResponse オブジェクトにタッチエフェクトを付与するには、⼀般的にInkWellを使います  41 違いは2つのパラメータくらいで、 雑に⾔うとInkResponseは円形、InkWellは矩形のエフェクトが発⽣します。
  29.  42 highlightShapeパラメータ InkWell / InkResponse IJHIMJHIU4IBQFͷ஋ ϋΠϥΠτͷදࣔ DJSDMF 


    *OL3FTQPOTFͰ࢖༻ ԁܗ SFDUBOHMF *OL8FMMͰ࢖༻ ۣܗ ۣܗͷؙ֯͸CPSEFS3BEJVTύϥϝʔλͰࢦఆՄೳ press/focus/hover時のハイライトの挙動を定義します enum BoxShape { rectangle, circle, // Don't add more, instead create a new ShapeBorder. } #PY4IBQF͸ۣܗ͔ԁͷ୒Ͱɺ ࠓޙ૿͑Δ͜ͱ΋૝ఆ͞Ε͍ͯͳ͍
  30.  43 containedInkWellパラメータ InkWell / InkResponse DPOUBJOFE*OL8FMMͷ஋ λονΤϑΣΫτͷڍಈ '"-4& *OL3FTQPOTFͰ࢖༻

    ԁܗ λονҐஔ͔Βঃʑʹ 
 ΢ΟδΣοτதԝ΁ͱ޲͔͏ 536& *OL8FMMͰ࢖༻ ۣܗ λονҐஔ͔ΒΤϑΣΫτ͕޿͕Δ ۣܗͷؙ֯͸CPSEFS3BEJVTύϥϝʔλͰࢦఆՄೳ タッチエフェクトの挙動を定義します 元は、タッチエフェクトがウィジェットの境界を越えるかどうか、という意味のフラグ
  31.  47 SplashFactory InkWell / InkResponse abstract class InteractiveInkFeatureFactory {

    … @factory InteractiveInkFeature create({ required MaterialInkController controller, required RenderBox referenceBox, required Color color, bool containedInkWell = false, … }); } タッチエフェクトの挙動を決めるInkFeature(InteractiveInkFeature)を⽣成するfactoryを定義します このfactoryで⽣成されるInteractiveInkFeatureは 主に InkSplash/InkRipple/InkSparkle (,NoSplash) TQMBTI'BDUPSZͷܕ͸ *OUFSBDUJWF*OL'FBUVSF'BDUPSZܕ
  32.  48 InkSplash / InkRipple / InkSparkle SplashFactory factory ThemeData()

    { ... final bool useInkSparkle = platform == TargetPlatform.android && !kIsWeb; splashFactory ??= useMaterial3 ? useInkSparkle ? InkSparkle.splashFactory : InkRipple.splashFactory : InkSplash.splashFactory; → InkSparkleを使うのはAndroid M 3 ではInkSparkleかInkRipple
  33. factory ThemeData() { ... final bool useInkSparkle = platform ==

    TargetPlatform.android && !kIsWeb; splashFactory ??= useMaterial3 ? useInkSparkle ? InkSparkle.splashFactory : InkRipple.splashFactory : InkSplash.splashFactory;  49 InkSplash / InkRipple / InkSparkle SplashFactory M 3 ではInkSparkleかInkRipple M 2 はInkSplash → InkSparkleを使うのはAndroid
  34. factory ThemeData() { ... final bool useInkSparkle = platform ==

    TargetPlatform.android && !kIsWeb; splashFactory ??= useMaterial3 ? useInkSparkle ? InkSparkle.splashFactory : InkRipple.splashFactory : InkSplash.splashFactory;  50 InkSplash / InkRipple / InkSparkle SplashFactory M 2 M 3 (Android以外) M 3 
 Android ※InkSplashはShaderを使うため、Web⾮対応 【タッチ時の挙動】 M 3 になって速くなった Sparkleによりキラキラしている
  35.  51 InkSplash / InkRipple / InkSparkle SplashFactory タッチ後、すぐ指を離すと 


    InkSplashのみタッチエフェクトがキャンセルされます 【タッチキャンセル時の挙動】 M 2 M 3 (Android以外) M 3 
 Android factory ThemeData() { ... final bool useInkSparkle = platform == TargetPlatform.android && !kIsWeb; splashFactory ??= useMaterial3 ? useInkSparkle ? InkSparkle.splashFactory : InkRipple.splashFactory : InkSplash.splashFactory;
  36. Android 1 2 から登場したタッチエフェクトの表現。 
 β版だとsparkleが強かったが、 
 stable版でだいぶ落ち着いた表現に。  52

    InkSparkle SplashFactory https://github.com/ fl utter/ fl utter/issues/ 8 2 8 5 0 https:// 9 to 5 google.com/ 2 0 2 1 / 0 7 / 1 4 /android- 1 2 -ripple-sparkle/ Android 1 2 β版のタッチエフェクトの様⼦ FlutterでもM 3 対応の⼀環として登場 
 ※M 3 のガイドラインには載っていない
  37.  55 いつものCounterアプリ Materialウィジェットを使う class MyApp extends StatelessWidget { …

    @override Widget build(BuildContext context) { return MaterialApp( home: const MyHomePage(title: 'Home Page'), ); } } class MyHomePage extends StatefulWidget { … @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Center( … class Scaffold extends StatefulWidget { … @override Widget build(BuildContext context) { … return _ScaffoldScope( hasDrawer: hasDrawer, geometryNotifier: _geometryNotifier, child: ScrollNotificationObserver( child: Material( child:… .BUFSJBM"QQ͸ɺ.BUFSJBMͱ͸ผ෺ 4DB PMEͷதʹ.BUFSJBM͕͍ΔͨΊɺ 4DB PMEԼͰ͸*OL8FMMΛ࢖͑Δ
  38.  57 Card Materialウィジェットを使う Card( clipBehavior: Clip.hardEdge, child: InkWell( child:

    …, onTap: () { … }, ), ) clipBehaviorで .none以外を指定する必要がある  57
  39.  58 TextButton / OutlinedButton / ElevatedButton Materialウィジェットを使う class ElevatedButton

    extends ButtonStyleButton { } abstract class ButtonStyleButton extends StatefulWidget { @override Widget build(BuildContext context) { … final Widget result = ConstrainedBox( child: Material( shape: resolvedShape!.copyWith(side: resolvedSide), child: InkWell( onTap: widget.onPressed, customBorder: resolvedShape.copyWith(side: resolvedSide), child: … ), ), ); return Semantics(child: _InputPadding(child: result)); } } #VUUPO܈͸ɺ#VUUPO4UZMF#VUUPOΛܧঝ ಺෦Ͱ.BUFSJBM *OL8FMMΛ࣋ͭ
  40. class ElevatedButton extends ButtonStyleButton { } abstract class ButtonStyleButton extends

    StatefulWidget { @override Widget build(BuildContext context) { … final Widget result = ConstrainedBox( child: Material( shape: resolvedShape!.copyWith(side: resolvedSide), child: InkWell( onTap: widget.onPressed, customBorder: resolvedShape.copyWith(side: resolvedSide), child: … ), ), ); return Semantics(child: _InputPadding(child: result)); } }  59 TextButton / OutlinedButton / ElevatedButton Materialウィジェットを使う $BSEͷ৔߹͸$MJQͷࢦఆ͕ඞཁ͕ͩͬͨɺ #VUUPOͷ৔߹͸#PSEFS4JEF͕ࢦఆ͞Ε͍ͯΔͨΊɺෆཁ
  41.  60 IconButton(M 3 ) Materialウィジェットを使う class IconButton extends StatelessWidget

    { @override Widget build(BuildContext context) { if (theme.useMaterial3) { return _SelectableIconButton(…); } … } } class _SelectableIconButton extends StatefulWidget { @override Widget build(BuildContext context) { return _IconButtonM3(…); } } class _IconButtonM3 extends ButtonStyleButton {…} IconButtonはStatelessWidgetだが、M 3 の場合は内部的に ButtonStyleButtonを使っているため、実装はほぼ同じ。
  42.  61 IconButton(M 2 ) Materialウィジェットを使う class IconButton extends StatelessWidget

    { @override Widget build(BuildContext context) { if (theme.useMaterial3) { return _SelectableIconButton(…); } … return Semantics( child: InkResponse( radius: splashRadius ?? … child: result, …), … ); } } .ͱ͸ҟͳΓɺ *OL3FTQPOTF͕࢖ΘΕ͍ͯΔ Materialウィジェットは内包していないので、 
 不透過レイヤーが下にあるとタッチエフェクトは表⽰されません
  43.  63 IconButton(M 2 vs M 3 ) / タッチエフェクトの挙動

    Materialウィジェットを使う M 2 M 3 特に指定しないとサイズ固定( 3 5 pt) 
 円形 アイコンサイズに応じたサイズになる 
 実は矩形 *OL8FMM *OL3FTQPOTF
  44.  64 IconButton(M 2 vs M 3 ) / タッチエフェクトの挙動

    Materialウィジェットを使う M 2 M 3 IconButton( onPressed: () {}, icon: Container( padding: const EdgeInsets.symmetric(vertical: 16), color: Colors.yellowAccent.withAlpha(128), child: Icon( Icons.flutter_dash_sharp, ), ), *DPOʹ1BEEJOHΛՃ͑ͯΈΔͱʜ
  45.  65 IconButton(M 2 vs M 3 ) / タッチエフェクトの挙動

    Materialウィジェットを使う M 2 M 3 円のサイズが固定のため、 
 Paddingの分だけ下寄りになってしまう 矩形のため、アイコンの形状によっては 
 楕円のようになることもある *OL8FMM *OL3FTQPOTF
  46.  66 IconButton(M 2 vs M 3 ) / タッチエフェクトの挙動

    Materialウィジェットを使う M 2 M 3 円のサイズが固定のため、 
 Paddingの分だけ下寄りになってしまう 矩形のため、アイコンの形状によっては 
 楕円のようになることもある *OL8FMM *OL3FTQPOTF ɹ.ͷ*DPO#VUUPOɿίϯςϯπͷαΠζͱ ɹɹɹɹɹɹɹɹɹɹɹɹɹෆಁաϨΠϠʔʹ஫ҙ ɹ.ͷ*DPO#VUUPOɿλονΤϑΣΫτͷܗঢ়ʹ஫ҙ
  47. class Ink extends StatefulWidget { Ink({ Color? color, Decoration? decoration,

    this.child, }) : decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null); … final Decoration? decoration; @override Widget build(BuildContext context) { return Padding( child: Builder(builder: _build), ); } …  70 内部実装 Ink .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController *OL @3FOEFS*OL'FBUVSFT
  48. class Ink extends StatefulWidget { Ink({ Color? color, Decoration? decoration,

    this.child, }) : decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null); … final Decoration? decoration; @override Widget build(BuildContext context) { return Padding( child: Builder(builder: _build), ); } …  71 内部実装 Ink .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController *OL @3FOEFS*OL'FBUVSFT .BUFSJBM΢ΟδΣοτʹ ృΓ͍ͨ$PMPS%FDPSBUJPOΛࢦఆ
  49. class Ink extends StatefulWidget { Ink({ Color? color, Decoration? decoration,

    this.child, }) : decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null); … final Decoration? decoration; @override Widget build(BuildContext context) { return Padding( child: Builder(builder: _build), ); } …  72 内部実装 Ink .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController *OL @3FOEFS*OL'FBUVSFT $PMPSΛࢦఆͨ͠৔߹΋ %FDPSBUJPOʹม׵
  50. class Ink extends StatefulWidget { Ink({ Color? color, Decoration? decoration,

    this.child, }) : decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null); … final Decoration? decoration; @override Widget build(BuildContext context) { return Padding( child: Builder(builder: _build), ); } …  73 内部実装 Ink .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController *OL @3FOEFS*OL'FBUVSFT *OLࣗମ͸ඳը͞Εͳ͍ %FDPSBUJPO͸࢖ΘΕͳ͍
  51. class Ink extends StatefulWidget { Ink({ Color? color, Decoration? decoration,

    this.child, }) : decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null); … final Decoration? decoration; @override Widget build(BuildContext context) { return Padding( child: Builder(builder: _build), ); } … Widget _build(BuildContext context) { … _ink = InkDecoration( decoration: widget.decoration, controller: Material.of(context), ); return widget.child; }  74 内部実装 Ink .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController *OL @3FOEFS*OL'FBUVSFT EFDPSBUJPO͸ *OL%FDPSBUJPO *OL'FBUVSF ʹ౉͞ΕΔ
  52. class InkDecoration extends InkFeature { InkDecoration({ required Decoration? decoration, required

    super.controller, … }) : … controller.addInkFeature(this); }  75 内部実装 Ink .BUFSJBM @*OL'FBUVSFT *OL *OL%FDPSBUJPO @3FOEFS*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController
  53. class InkDecoration extends InkFeature { InkDecoration({ required Decoration? decoration, required

    super.controller, … }) : … controller.addInkFeature(this); }  76 内部実装 Ink .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController addInkFeature *OL *OL%FDPSBUJPO @3FOEFS*OL'FBUVSFT *OL಺Ͱੜ੒͞Εͨ%FDPSBUJPOͰ $BOWBTʹඳը͢Δ*OL'FBUVSFΛੜ੒
  54. class InkDecoration extends InkFeature { InkDecoration({ required Decoration? decoration, required

    super.controller, … }) : … controller.addInkFeature(this); }  77 内部実装 Ink .BUFSJBM @*OL'FBUVSFT -JTU*OL'FBUVSF MaterialInkController addInkFeature *OL *OL%FDPSBUJPO @3FOEFS*OL'FBUVSFT *OL'FBUVSFΛ @3FOEFS*OL'FBUVSFTʹొ࿥
  55.  81 内部実装(図解) Ink *OL'FBUVSF @3FOEFS*OL'FBUVSFT *OL8FMM .BUFSJBM *OL%FDPSBUJPO *OL

    *OLࣗମ͸ಁա .BUFSJBMͷද໘ͷ৭ %FDPSBUJPO Λ ม͑Δ͜ͱ͕Ͱ͖Δ
  56.  82 ⾊を変えたい場合 Ink ColoredBox( color: Colors.lightGreenAccent, child: InkWell( onTap:

    () {…}, child: …, ), ) ColoredBoxやContainerで指定しているcolor部分を、Inkに差し替える Ink( color: Colors.lightGreenAccent, child: InkWell( onTap: () {…}, child: …, ), )
  57.  83 画像の上にタッチエフェクトをつけたい場合 Ink Ink.image({ this.padding, required ImageProvider image, this.child,

    }) : decoration = BoxDecoration( image: DecorationImage(image: image, …), ); 画像を指定すると、color同様にDecorationに変換される
  58.  84 画像の上にタッチエフェクトをつけたい場合 Ink Ink.image({ this.padding, required ImageProvider image, this.child,

    }) : decoration = BoxDecoration( image: DecorationImage(image: image, …), ); 画像を指定すると、color同様にDecorationに変換される UJQT 画像の⾓丸は、Ink.imageではできない 
 その場合、BoxDecorationを⾃作するのが早い Ink( decoration: BoxDecoration( image: DecorationImage(image: imageProvider), borderRadius: BorderRadius.all(Radius.circular(8)), ), )
  59. Ink.image({ this.padding, required ImageProvider image, this.child, }) : decoration =

    BoxDecoration( image: DecorationImage(image: image, …), ); Ink( decoration: BoxDecoration( image: DecorationImage(image: imageProvider), borderRadius: BorderRadius.all(Radius.circular(8)), ), )  85 画像の上にタッチエフェクトをつけたい場合 Ink UJQT 画像の⾓丸は、Ink.imageではできない 
 その場合、BoxDecorationを⾃作するのが早い ؙ֯ࢦఆ 画像を指定すると、color同様にDecorationに変換される
  60.  87 MaterialType.transparency Material Material( type: MaterialType.transparency, child: …, )

    MaterialTypeにtransparencyが⽤意されている enum MaterialType { /// ۣܗɻσϑΥϧτ canvas, /// ؙ֯ͷۣܗɻCardͰ࢖͏ card, /// ԁܗɻσϑΥϧτͰ͸৭ͳ͠ɻʢFABͰ࢖͏ʣ circle, /// ؙ֯ͷۣܗɻσϑΥϧτͰ৭ͳ͠ɻʢϘλϯͰ࢖͏ʣ button, /// ಁ໌Ͱink splashes ΍ highlightsΛඳը͢Δɻ transparency }
  61.  88 MaterialType.transparencyによる挙動の違い Material @override Widget build(BuildContext context) { final

    Color? backgroundColor = _getBackgroundColor(context); assert( backgroundColor != null || widget.type == MaterialType.transparency, 'If Material type is not MaterialType.transparency, ‘a color must either be passed in through the ‘`color` property, or be defined in the theme', ); … if (widget.type == MaterialType.transparency) { return _transparentInterior( shape: shape, clipBehavior: widget.clipBehavior, contents: contents, ); } return _MaterialInterior(…); } USBOTQBSFODZͷ৔߹ɺ CBDLHSPVOE$PMPS͸OVMMͰ͋Δඞཁ͕͋Δ
  62.  89 MaterialType.transparencyによる挙動の違い Material @override Widget build(BuildContext context) { final

    Color? backgroundColor = _getBackgroundColor(context); assert( backgroundColor != null || widget.type == MaterialType.transparency, 'If Material type is not MaterialType.transparency, ‘a color must either be passed in through the ‘`color` property, or be defined in the theme', ); … if (widget.type == MaterialType.transparency) { return _transparentInterior( shape: shape, clipBehavior: widget.clipBehavior, contents: contents, ); } return _MaterialInterior(…); } FMFWBUJPO΍TIBQF͕มߋ͞Εͨͱ͖ʹ "OJNBUJPOΛ࣮ߦ͢Δ*NQMJDJUMZ"OJNBUFE8JEHFU
  63. @override Widget build(BuildContext context) { final Color? backgroundColor = _getBackgroundColor(context);

    assert( backgroundColor != null || widget.type == MaterialType.transparency, 'If Material type is not MaterialType.transparency, ‘a color must either be passed in through the ‘`color` property, or be defined in the theme', ); … if (widget.type == MaterialType.transparency) { return _transparentInterior( shape: shape, clipBehavior: widget.clipBehavior, contents: contents, ); } return _MaterialInterior(…); }  90 MaterialType.transparencyによる挙動の違い Material static Widget _transparentInterior({… }) { final _ShapeBorderPaint child = _ShapeBorderPaint(…); return ClipPath( clipper: ShapeBorderClipper( shape: shape, textDirection:Directionality.maybeOf(context) ), … ); }
  64. @override Widget build(BuildContext context) { final Color? backgroundColor = _getBackgroundColor(context);

    assert( backgroundColor != null || widget.type == MaterialType.transparency, 'If Material type is not MaterialType.transparency, ‘a color must either be passed in through the ‘`color` property, or be defined in the theme', ); … if (widget.type == MaterialType.transparency) { return _transparentInterior( shape: shape, clipBehavior: widget.clipBehavior, contents: contents, ); } return _MaterialInterior(…); } static Widget _transparentInterior({… }) { final _ShapeBorderPaint child = _ShapeBorderPaint(…); return ClipPath( clipper: ShapeBorderClipper( shape: shape, textDirection:Directionality.maybeOf(context) ), … ); }  91 MaterialType.transparencyによる挙動の違い Material USBOTQBSFODZͷ৔߹ɺ୯७ͳ4IBQF#PSEFS$MJQQFS 㱺.BUFSJBMಛ༗ͷ"OJNBUJPO͕ແޮʹͳ͍ͬͯΔ
  65.  92 Material or Ink ? Material Inkで代⽤できるならそれに越したことはないけど、 
 Buttonなどでも内部的にMaterialを使⽤しているので、そこまで神経質になる必要はない。

    特に汎⽤的なカスタムWidgetを作っている場合などは、 
 むしろ深く考えずにMaterialを使った⽅が良いと、個⼈的には思います。 class ElevatedButton extends ButtonStyleButton { } abstract class ButtonStyleButton extends StatefulWidget { @override Widget build(BuildContext context) { … final Widget result = ConstrainedBox( child: Material( child: InkWell( …
  66. Summary • タッチエフェクトは、Materialウィジェットの表⾯(_RenderInkFeatures)で描画される • タッチエフェクトはM 2 とM 3 で挙動が少し異なる •

    タッチエフェクトが表⽰されない場合はInkをうまく使う • 難しく考えずに、Materialを使ってしまってもOK!  94
  67. 参考 • Material Design 
 https://m 3 .material.io/ • InkResponse

    と InkWell と Ink、違いを説明できますか? 
 https://qiita.com/mkosuke/items/e 5 0 6 2 5 6 5 1 5 1 7 9 d 0 f 4 2 1 b • Flutterはナビタイム ジャパンの新規アプリ開発で輝く 
 https://speakerdeck.com/navitimejapan/ fl utterhanabitaimuziyapanfalsexin-gui-apurikai- fa-dehui-ku  96