Flutter-InkWell

InkWell(InkResponse)のリップルエフェクト/(インク)スプラッシュエフェクトについてしらべた内容について。

基本は「ウィジェットに共通のsplashFactoryを設定するには」、それ以外にも少し。
これは「表示されるすべてのウィジェットのリップルエフェクトを外すには」という命題から調べ始めた内容になっている。

  • 2023/03/21
    誤字修正。
    ToggleButtonsの代替について追記。

InkWellとは

InkWell class - material library - Dart API
API docs for the InkWell class from the material library, for the Dart programming language.

基本的なものはFlutterのサイトで記載されているのだけど、日本語で調べられているものとしては以下のサイトのものが良く分かったもの。

[Flutter] InkResponse と InkWell と Ink、違いを説明できますか? - Qiita
Flutter Advent Calendar#2 の11日目です。昨年はEffective Dartまとめで参加させていただきました。今年はMaterialウィジェットについて書こうと思いましたが…

動きとしては、タップ時にスプラッシュエフェクトを表示するクラスで、入力系ウィジェットのベースとしていろいろな場所、主にボタン系で使われている。

ベースクラスがInkResponseでその派生でInkWellが実装されている。

提供しているエフェクト類

以下のものが提供されているエフェクト。

  • スプラッシュ
    タップするとそこから伝播するようなイメージが作成される。
  • ハイライト
    以下はハイライト色を黄色にしたのだけど、タップすると全体の色が変わる。
  • ホバー
    マウスなどが領域に入ると色が変わる。
  • フォーカス
    デスクトップ版などでタブキーを押しフォーカスが設定されると色が変わる。

スプラッシュそれともリップルなのか

InkWellのスプラッシュ関連の呼称についてちょっと調べてみた。

そうするとスプラッシュとリップルのどちらも書かれていて統一はされていないように感じた。

FlutterのドキュメントのAPIを見るとスプラッシュ(Splash)というメソッドを割り当てられている。
しかし、この機能の紹介が書かれている場所にはリップルと書かれている。

またスプラッシュについても少し調べてみると、本当はインクスプラッシュというのが本当の名称らしい。(出所不明だけど)

アニメーションの定義としては、リップルは押した場所から波紋が描かれるイメージ、スプラッシュは筆からインクが飛び散る様子を描いたものなのかな。

スプラッシュ動作の指定

動作指定は、InkWell(InkResponse)のsplashFactoryにファクトリーを指定することで決定することができる。

実際のアニメーション方法のインターフェースがInteractiveInkFeatureで、この実装を定義しているのがInkRipple(リップル)、InkSplash(スプラッシュ)、NoSplash(スプラッシュ無し)、InkSparkle(Material3で新たに追加)になる。

splashFactoryには上記実装のインスタンスを取り出すためのファクトリーInteractiveInkFeatureFactoryの実装を設定することになる。

InteractiveInkFeatureとInteractiveInkFeatureFactoryの実装の基本

InteractiveInkFeatureとそれを生成するInteractiveInkFeatureFactoryの実装の基本形は次のようになる。
InkRipple、InkSplash、NoSplash、InkSparkleはそれを生成するInteractiveInkFeatureFactoryの個別実装がそれぞれ存在している。

class InkTest extends InteractiveInkFeature {
  InkTest(
      {required super.controller,
      required super.referenceBox,
      required super.color});
  @override
  void paintFeature(Canvas canvas, Matrix4 transform) {
    // TODO: implement paintFeature
  }
  static InteractiveInkFeatureFactory splashFactory = _InkTestFactory();
}

class _InkTestFactory extends InteractiveInkFeatureFactory {
  @override
  InteractiveInkFeature create(
      {required MaterialInkController controller,
      required RenderBox referenceBox,
      required Offset position,
      required Color color,
      required TextDirection textDirection,
      bool containedInkWell = false,
      RectCallback? rectCallback,
      BorderRadius? borderRadius,
      ShapeBorder? customBorder,
      double? radius,
      VoidCallback? onRemoved}) {
    return InkTest(
        controller: controller, referenceBox: referenceBox, color: color);
  }
}

ウィジェットに共通のsplashFactoryを設定するには

一部例外があるけど、全体的に無効にする場合は以下の様なテーマを設定することで、共通のスプラッシュファクトリーを設定することができる。以下例ではスプラッシュ無しのファクトリーを設定している。
ただInkWellのsplashFactoryがハードコードされているものについては適用できない。

  final theme = ThemeData(
    splashFactory: NoSplash.splashFactory,
    useMaterial3: true,
  );

  runApp(GetMaterialApp(
      theme: theme,

コードの説明

関連するウィジェットはInkWell(InkResponse)、TabBar、ボタン類(TextButton, ElevatedButton,  OutlinedButton, MenuItemButton, SubmenuButton, FilledButton)、ウィジェットの構成にInkWellが含まれているもの、ToggleButtonsといったものがある。

InkWell(InkResponse)

splashFactoryがnull(未設定)の場合は、ThemeDataのsplashFactoryを使用するようになっている。

TabBar

ウィジェットに直接指定したsplashFactory>ThemeData. tabBarThemeのsplashFactory>ThemeDataのsplashFactory

上記の順番でsplashFactory決定しているので、個別に指定していなければThemeDataのsplashFactoryが使用される。

ボタン類

ボタン類は各々のウィジェットクラスのスタイルが存在し、その中にsplashFactoryがある。
これを踏まえ、ボタン類のスプラッシュファクトリーの適用アルゴリズムは次のようになる。

「ウィジェットに直接指定したスタイル>ウィジェットクラステーマのスタイル>デフォルトスタイル」

この中のウィジェットクラステーマのスタイルは、ThemeDataの~buttonThemeのスタイルに保存されているものが使用される。ボタン系は種類ごとにテーマが分かれているので、すべてのクラステーマに同一のsplashFactoryを設定するのは大変面倒。

またデフォルトスタイルのうちTextButton, ElevatedButton, OutlinedButtonはInkRipple.splashFactoryを使用しているのがあるため、適用するのが結構面倒になる。

それに対応するパラメータがuseMaterial3をtrueにすること。

これにより、デフォルトスタイルがThemeDataのsplashFactoryを使用するようになるため、設定の共通化が図れる。

useMaterial3がfalseの場合は、以下の様なテーマにする必要がある。

                  final ButtonStyle defaultTextButtonStyle =
                      TextButton.styleFrom(
                          splashFactory: NoSplash.splashFactory);
                  final ButtonStyle defaultElevatedButtonStyle =
                      ElevatedButton.styleFrom(
                          splashFactory: NoSplash.splashFactory);

                  final ButtonStyle defaultOutlinedButtonStyle =
                      OutlinedButton.styleFrom(
                          splashFactory: NoSplash.splashFactory);
                  final theme = ThemeData(
                    splashFactory: NoSplash.splashFactory,
                    textButtonTheme:
                        TextButtonThemeData(style: defaultTextButtonStyle),
                    elevatedButtonTheme: ElevatedButtonThemeData(
                        style: defaultElevatedButtonStyle),
                    outlinedButtonTheme: OutlinedButtonThemeData(
                        style: defaultOutlinedButtonStyle),
                  );

ウィジェットの構成にInkWellが含まれているもの

含まれているInkWellのsplashFactoryが指定されていなければInkWellと同じ決定方法になる。

ToggleButtons

これが曲者。

系統的には「ウィジェットの構成にInkWellが含まれているもの」になるのだけど、ChildにText等を指定した場合それがTextButtonでラッピングされる。

そのTextButtonのスタイル指定でInkRipple.splashFactoryがハードコーデイングされている。

そのため、splashFactoryの共通化が不可能という結果になった。

これを変更することはできないので、唯一スプラッシュ指定が残ってしまうウィジェットかも。

2023/03/21追記

この件Issuesに入っていた。

Cannot change splash effect of ToggleButtons · Issue #119447 · flutter/flutter
I file this as a bug rather than a feature request as it seems like lack of a feature. It is possible to change the appe...

これによるとSegmentedButtonを代わりに使ってくれとなっている。
構造が異なるので変更するのがめんどくさそう。

InkWellを含むウィジェットを作成する場合

InkWellを含むウィジェットを作る場合、できたらsplashFactory部分は未設定とするのが望ましいのだと思う。

またもし設定する必要がある場合は、別途テーマもしくはスタイルを作りその値をデフォルトとするべきであろう。

独自なのであれば、拡張テーマ(ThemeExtensions)にして取り扱うというのもいいと思う。

コメント

タイトルとURLをコピーしました