2022/6/5/Flutter/translation of Create gradient chat bubbles

 

Contents

Create gradient chat bubbles

グラデーションバブルチャットの作成。

Traditional chat apps display messages in chat bubbles with solid color backgrounds. Modern chat apps display chat bubbles with gradients that are based on the bubbles’ position on the screen. In this recipe, you’ll modernize the chat UI by implementing gradient backgrounds for the chat bubbles.

従来のチャットアプリは、背景が無地のチャットバブルにメッセージを表示します。 最新のチャットアプリは、画面上のバブルの位置に基づいたグラデーションでチャットバブルを表示します。 このレシピでは、チャットバブルにグラデーションの背景を実装することで、チャットUIを最新化します。

 

The following animation shows the app’s behavior:

下記にアニメーションがアプリの挙動を示します。

Scrolling the gradient chat bubbles

 


Understand the challenge

チャレンジを理解する。

The traditional chat bubble solution probably uses a DecoratedBox or a similar widget to paint a rounded rectangle behind each chat message.

従来のチャットバブルソリューションでは、おそらく、DecoratedBoxまたは類似のウィジェットを使用して、各チャットメッセージの後ろに角の丸い長方形を描きます。

 

That approach is great for a solid color or even for a gradient that repeats in every chat bubble.

このアプローチは、単色の場合や、一つ一つのチャットバブルで繰り返されるグラデーションの場合にも最適です。

 

However, modern, full-screen, gradient bubble backgrounds require a different approach. The full-screen gradient, combined with bubbles scrolling up and down the screen, requires an approach that allows you to make painting decisions based on layout information.

ただし、最新のフルスクリーンのグラデーションバブル背景には、別のアプローチが必要です。 フルスクリーンのグラデーションと、画面を上下にスクロールするバブルを組み合わせるには、レイアウト情報に基づいてペイント(描画方法)を決定できるアプローチが必要です。

 

Each bubble’s gradient requires knowledge of the bubble’s location on the screen.

各バブルのグラデーションには、画面上のバブルの位置に関する情報が必要です。

 

This means that the painting behavior requires access to layout information.

これは、描画の際に(それぞれのバブルの)レイアウトの情報にアクセスする必要があることを意味します。

 

Such painting behavior isn’t possible with typical widgets because widgets like Container and DecoratedBox make decisions about background colors before layout occurs, not after.

ContainerやDecoratedBoxなどのウィジェットは、レイアウトが発生した後ではなく、発生する前に背景色を決定するため、このようなペイント動作は通常のウィジェットでは不可能です。

 

In this case, because you require custom painting behavior, but you don’t require custom layout behavior or custom hit test behavior, a CustomPainter is a great choice to get the job done.

この場合、カスタムペイント動作が必要ですが、カスタムレイアウト動作やカスタムヒットテスト動作は必要ないため、CustomPainterは作業を完了するための優れた選択肢です。


Note: In cases where you need control over the child layout, but you don’t need control over the painting or hit testing, consider using a Flow widget.

注:子のレイアウトを制御する必要があるが、ペイントやヒットテストを制御する必要がない場合は、Flowウィジェットの使用を検討してください。

 

In cases where you need control over the layout, painting, and hit testing, consider defining a custom RenderBox.

レイアウト、ペイント、ヒットテストを制御する必要がある場合は、カスタムのRenderBoxを定義することを検討してください。


Replace original background widget

オリジナルのバックグラウンドウィジェットを置き換える。

Replace the widget responsible for drawing the background with a new stateless widget calledBubbleBackground.

背景の描画を担当するウィジェットを、BubbleBackground という新しいステートレス ウィジェットに置き換えます。

 

Include a colors property to represent the full-screen gradient that should be applied to the bubble.

バブルに適用する必要があるフルスクリーングラデーションを表すcolorsプロパティを含めます。

BubbleBackground(
  // The colors of the gradient, which are different
  // depending on which user sent this message.
  colors: message.isMine
      ? const [Color(0xFF6C7689), Color(0xFF3A364B)]
      : const [Color(0xFF19B7FF), Color(0xFF491CCB)],
  // The content within the bubble.
  // バブルの中のコンテンツ(内容)
  child: DefaultTextStyle.merge(
    style: const TextStyle(
      fontSize: 18.0,
      color: Colors.white,
    ),
    child: Padding(
      padding: const EdgeInsets.all(12.0),
      child: Text(message.text),
    ),
  ),
);

 


Create a custom painter

CustomPainterを生成する。

Next, introduce an implementation for BubbleBackground as a stateless widget.

次にステートレスウィジェットとしてBubbleBackgroundウィジェットの実装に移ります。

 

For now, define the build()method to return a CustomPaint with a CustomPainter called BubblePainter. BubblePainter is used to paint the bubble gradients.

現時点では、BubblePainterという名のCustomPainterを持つCustomPaintを返すbuild()メソッドを定義します。BubblePainterはバブルのグラデーションを描画するのに使われます。

CustomPaintウィジェットのコンストラクタの引数に、CustomPainterクラスのサブクラスであるBubblePainterクラスのインスタンスを渡す。そのCustomPaintウィジェットをbuild()メソッドの返り値として返す。

CustomPainterクラスの親クラスがListenableクラス。

@immutable
class BubbleBackground extends StatelessWidget {
  const BubbleBackground({
    super.key,
    required this.colors,
    this.child,
  });

  final List<Color> colors;
  final Widget? child;

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: BubblePainter(
        colors: colors,
      ),
      child: child,
    );
  }
}

class BubblePainter extends CustomPainter {
  BubblePainter({
    required List<Color> colors,
  }) : _colors = colors;

  final List<Color> _colors;

  @override
  void paint(Canvas canvas, Size size) {
    // TODO:
  }

  @override
  bool shouldRepaint(BubblePainter oldDelegate) {
    // TODO:
    return false;
  }
}

 


Provide access to scrolling information

スクロール情報へのアクセスを提供する

The CustomPainter requires the information necessary to determine where its bubble is within the ListView’s bounds, also known as the Viewport.

CustomPainterには、バブルがListViewの境界内のどこにあるかを判断するために必要な情報(Viewportとして知られている)が必要です。

 

Determining the location requires a reference to the ancestor ScrollableState and a reference to the BubbleBackground’s BuildContext. Provide each of those to the CustomPainter.

位置を決定するためには、先祖ウィジェットのScrollableStateへの参照と、

BubbleBackgroundウィジェットのBuildContextへの参照が必要です。それらをCustomPainterに渡します。

BubblePainter(
  colors: colors,
  bubbleContext: context,
  scrollable: ScrollableState(),
),

 

class BubblePainter extends CustomPainter {
  BubblePainter({
    required ScrollableState scrollable,
    required BuildContext bubbleContext,
    required List<Color> colors,
  })  : _scrollable = scrollable,
        _bubbleContext = bubbleContext,
        _colors = colors;

  final ScrollableState _scrollable;
  final BuildContext _bubbleContext;
  final List<Color> _colors;

  @override
  bool shouldRepaint(BubblePainter oldDelegate) {
    return oldDelegate._scrollable != _scrollable ||
        oldDelegate._bubbleContext != _bubbleContext ||
        oldDelegate._colors != _colors;
  }
}

 


Paint a full-screen bubble gradient

The CustomPainter now has the desired gradient colors, a reference to the containing ScrollableState, and a reference to this bubble’s BuildContext.

CustomPainterには、目的のグラデーションカラーと、祖先であるListViewのScrollableStateへの参照、そしてそれぞれのバブルのBuildContextへの参照を全て渡しました。

 

This is all the information that the CustomPainter needs to paint the full-screen bubble gradients.

これが、CustomPainterがフルスクリーンバブルグラデーションを描くために必要なすべての情報です。

 

Implement the paint() method to calculate the position of the bubble, configure a shader with the given colors, and then use a matrix translation to offset the shader based on the bubble’s position within the Scrollable.

paint() メソッドを実装して、バブルの位置を計算し、与えられた色でシェーダーを構成し、マトリックス変換を使用して Scrollable (ListView)内でのバブルの位置に基づいてシェーダーをオフセットします。

class BubblePainter extends CustomPainter {
  BubblePainter({
    required ScrollableState scrollable,
    required BuildContext bubbleContext,
    required List<Color> colors,
  })  : _scrollable = scrollable,
        _bubbleContext = bubbleContext,
        _colors = colors;

  final ScrollableState _scrollable;
  final BuildContext _bubbleContext;
  final List<Color> _colors;

  @override
  bool shouldRepaint(BubblePainter oldDelegate) {
    return oldDelegate._scrollable != _scrollable ||
        oldDelegate._bubbleContext != _bubbleContext ||
        oldDelegate._colors != _colors;
  }

  @override
  void paint(Canvas canvas, Size size) {
    final scrollableBox = _scrollable.context.findRenderObject() as RenderBox;
    final scrollableRect = Offset.zero & scrollableBox.size;
    final bubbleBox = _bubbleContext.findRenderObject() as RenderBox;

    final origin =
        bubbleBox.localToGlobal(Offset.zero, ancestor: scrollableBox);
    final paint = Paint()
      ..shader = ui.Gradient.linear(
        scrollableRect.topCenter,
        scrollableRect.bottomCenter,
        _colors,
        [0.0, 1.0],
        TileMode.clamp,
        Matrix4.translationValues(-origin.dx, -origin.dy, 0.0).storage,
      );
    canvas.drawRect(Offset.zero & size, paint);
  }
}

Congratulations! You now have a modern, chat bubble UI.

おめでとうございます。モダンなチャットバブルUIができましたね。

Note: The recipe doesn’t yet work on the web because Flutter doesn’t yet support Paintshaders.

注意: FlutterはまだPaint shadersをサポートしていないので、このレシピはwebでは動かせません。

 

Note: Each bubble’s gradient changes as the user scrolls because the BubbleBackgroundwidget invokes Scrollable.of(context).

注意: BubbleBackground ウィジェットは Scrollable.of(context) を呼び出すため、ユーザーがスクロールすると各気泡のグラデーションが変化します。

 

This method sets up an implicit dependency on the ancestor ScrollableState, which causes the BubbleBackground widget to rebuild every time the user scrolls up or down.

このメソッドは祖先のScrollableStateへの依存を暗黙的にセットアップします。それにより、ユーザーが(ListViewを)上下にスクロールするたびにBubbleBackgroundウィジェットのリビルドが発生します。

 

See the InheritedWidget documentation for more information about these types of dependencies.

InheritedWidgetのドキュメントを見てこの種の依存関係に関する情報を参照してください。

ScrollView >> BoxScrollView >> ListView

上記のような継承関係になっています。

そしてListViewの祖先クラスのScrollViewクラスのbuildメソッド内でScrollableウィジェットを生成して返しています。

ScrollableウィジェットはStatefulWidgetのサブクラスで、ScrollableStateクラスが状態フィールド_position(ScrollPosition型)を保持して、その変化に応じて適切にListViewの挙動を管理しています。

そしてScrollableStateクラスのbuildメソッド内で、InheritedWidgetのサブクラスである_ScrollableScopeインスタンスを生成して、使っています。ですので、Scrollable.of(context)によりアクセスした状態の変更の都度その通知が子孫クラスに送られ、リビルドにつながる、InheritedWidgetの仕組みですね、はい。

続いて、ScrollPositionクラスは、

Listenable >> ChangeNotifier >> ViewportOffset >> ScrollPosition

上記のような継承関係になっています。つまりScrollPostionはListenableのサブクラス。

そして、

class BubblePainter extends CustomPainter {
  BubblePainter({
    required ScrollableState scrollable,
    required BuildContext bubbleContext,
    required List<Color> colors,
  })  : _scrollable = scrollable,
        _bubbleContext = bubbleContext,
        _colors = colors,
        super(repaint: scrollable.position);//← here

  final ScrollableState _scrollable;
  final BuildContext _bubbleContext;
  final List<Color> _colors;

BubblePainterのコンストラクタ呼出時に親クラスのCustomPainterの_repaintフィールドに「ListenableであるScrollPosition」をセットしています。

これをする必要があるので、

Scrollable.of(context)!

上記のofメソッド呼び出しでScrollableState(のpositionフィールド)にアクセスしています。

CustomPainter自体もListenableであり、addListenerメソッドにより、受け取ったScrollPositionからの通知をリッスンしてCustomPaintに通知を送る、という感じですね、はい。

ということでスクロールによるスクロール位置の変化により、リビルドが起こり、再描画が行われる、という感じですね、はい。

 


Recap

The fundamental challenge when painting based on the scroll position, or the screen position in general, is that the painting behavior must occur after the layout phase is complete.

スクロール位置、あるいは一般的な画面位置に基づいて描画する場合の基本的な課題は、レイアウトフェーズが完了した後に描画動作を行う必要があることです。

 

CustomPaint is a unique widget that allows you to execute custom painting behaviors after the layout phase is complete.

CustomPaintは、レイアウトフェーズが完了した後に、カスタムの描画動作を実行できるユニークなウィジェットです。

 

If you execute the painting behaviors after the layout phase, then you can base your painting decisions on the layout information, such as the position of the CustomPaint widget within a Scrollable or within the screen.

レイアウトフェーズの後にペイントビヘイビアを実行すれば、Scrollable内やスクリーン内のCustomPaintウィジェットの位置などのレイアウト情報に基づいてペイントを決定することができます。

 

Note: This recipe doesn’t provide an interactive DartPad because Paint shaders have not yet been implemented for the web. You can run this recipe on a mobile or desktop device by cloning the example code. See the “Gradient Bubbles” example under the “cookbook” directory.

注意: Paint シェーダがまだウェブ用に実装されていないため、このレシピはインタラクティブな DartPad を提供しません。サンプルコードをクローンすることで、モバイルやデスクトップデバイスでこのレシピを実行することができます。「cookbook」ディレクトリの下の「Gradient Bubbles」サンプルを参照してください。

参考

https://docs.flutter.dev/cookbook/effects/gradient-bubbles

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です