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:
下記にアニメーションがアプリの挙動を示します。
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 Paint
shaders.
注意: FlutterはまだPaint shadersをサポートしていないので、このレシピはwebでは動かせません。
Note: Each bubble’s gradient changes as the user scrolls because the BubbleBackground
widget 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」サンプルを参照してください。
参考