Contents
Note: このレシピではnull-safety導入後のコードを使います。null-safetyがDart言語とFlutterフレームワークにやってきます。現在ベータチャンネルです。より詳しい情報はNull safety in Flutterをご覧ください。
Apps are filled with buttons that execute long-running behaviors.
アプリには、長時間実行される動作を実行するボタンがたくさんあります。
For example, a button might trigger a download, which starts a download process, receives data over time, and then provides access to the downloaded asset.
例えば、ダウンロードが開始されるボタン、これはダウンロードプロセスを開始し、データを受け取り、その後ダウンロードされた情報へのアクセスを提供します。
It’s helpful to show the user the progress of a long-running process, and the button itself is a good place to provide this feedback.
ユーザーに対して実行時間の長いプロセスの進捗状況を可視化することは親切です。そしてボタン自体は実行時間の長いプロセスの進行状況をユーザーに示すと役立ちます。ボタン自体は、このフィードバックを提供するのに適した場所です。
In this recipe, you’ll build a download button that transitions through multiple visual states, based on the status of an app download.
このレシピでは、アプリのダウンロードの進捗状況に応じて複数の視覚的な状態を遷移するダウンロードボタンを作成します。
The following animation shows the app’s behavior:
下記のアニメーションはアプリの振る舞いを示します。
Define a new stateful widget
Your button widget needs to change its appearance over time. Therefore, you need to implement your button with a custom stateful widget.
ボタンウィジェットは、時間の経過とともに外観を変更する必要があります。 したがって、カスタムステートフルウィジェットを使用してボタンを実装する必要があります。
Define a new stateful widget called DownloadButton
.
DownloadButtonという名のstateful widgetを新しく定義します。
@immutable
class DownloadButton extends StatefulWidget {
const DownloadButton({
Key? key,
}) : super(key: key);
@override
_DownloadButtonState createState() => _DownloadButtonState();
}
class _DownloadButtonState extends State<DownloadButton> {
@override
Widget build(BuildContext context) {
// TODO:
return SizedBox();
}
}
Live template機能を使うと速いです。
上記のようにstfulと入力(あるいは途中まで入力して出てきた選択肢を選択)すると、
自動的にボイラープレートコードを出してくれるので、あとはクラス名を入力すればOKです。
StatelessWidgetのサブクラスを宣言したい時はstlessと入力するとボイラープレートコードを出してくれます。
The download button’s visual presentation is based on a given download status.
ダウンロードボタンの見た目は(これから定義する)ダウンロードステータス(状態)に基づいて決定されます。
Define the possible states of the download, and then update DownloadButton
to accept a DownloadStatus
and a Duration
for how long the button should take to animate from one status to another.
取りうる状態値を(列挙型、enumで)定義し、DownloadStatusとDurationを受け取って、DownloadButtonをアップデートします。
Durationはボタンがある状態からまた別の状態へとアニメーション化するのに要する時間を表します。
enum DownloadStatus {
notDownloaded,
fetchingDownload,
downloading,
downloaded,
}
@immutable
class DownloadButton extends StatefulWidget {
const DownloadButton({
Key? key,
required this.status,
this.transitionDuration = const Duration(milliseconds: 500),
}) : super(key: key);
final DownloadStatus status;
final Duration transitionDuration;
@override
_DownloadButtonState createState() => _DownloadButtonState();
}
Note: Each time you define a custom widget, you must decide whether all relevant information is provided to that widget from its parent or if that widget orchestrates the application behavior within itself.
Note:カスタムウィジェットを定義するたびに、すべての関連情報がその親からそのウィジェットに提供されるのか、あるいはそのウィジェットがそれ自体の中でアプリケーションの動作を調整するのかを決定する必要があります。
For example, DownloadButton could receive the current DownloadStatus from its parent, or the DownloadButton could orchestrate the download process itself within its State object.
たとえば、DownloadButtonが親ウィジェットから現在のDownloadStatusを受け取るのか、
あるいは、
DownloadButtonが、自身のStateオブジェクト内でダウンロードプロセスの状態を調整するのか、ということです。
For most widgets, the best answer is to pass the relevant information into the widget from its parent, rather than manage behavior within the widget.
ほとんどのウィジェットの場合、最良の答えは、ウィジェット内で動作の状態を管理するのではなく、関連情報を親ウィジェットから受け取ることです。
By passing in all the relevant information, you ensure greater reusability for the widget, easier testing, and easier changes to application behavior in the future.
関連するすべての情報を親ウィジェットから渡すことで、ウィジェットの再利用性が向上し、テストが容易になり、将来のアプリケーション動作の変更が容易になります。
The download button changes its shape based on the download status.
ダウンロードボタンはダウンロードステータス(状態)に基づいてその形を変えます。
The button displays a grey, rounded rectangle during the notDownloaded
and downloaded
states.
ダウンロードステータス(状態)がnotDownloaded、またはdownloaded状態のときは、ボタンは角が丸まった長方形のグレイ色のボタンとして表示されます。
The button displays a transparent circle during the fetchingDownload
and downloading
states.
ダウンロードステータス(状態)がfetchingDownload、またはdownloading状態のときは、ボタンは透明な円として表示されます。
Based on the current DownloadStatus
, build an AnimatedContainer
with a ShapeDecoration
that displays a rounded rectangle or a circle.
現在のDownloadStatusに基づいて、角が丸まった長方形、あるいは円を表示するAnimatedContainerとShapeDecorationを構築します。
Consider defining the shape’s widget tree within a local _buildXXXX()
method so that the main build()
method remains simple, allowing for the additions that follow.
_DownloadButtonStateのbuildメソッド内をシンプルにするために、ローカル関数_buildButtonShapeを定義して、それを(_DownloadButtonStateの)buildメソッド内で呼び出すようにしましょう。
Also, configure the shape widget tree to accept a child widget, which you’ll use to display text in a later step.
さらに、_buildButtonShape関数の引数childで「テキストを表示するウィジェット」を受け取るようにします。
class _DownloadButtonState extends State<DownloadButton> {
bool get _isDownloading => widget.status == DownloadStatus.downloading;
bool get _isFetching => widget.status == DownloadStatus.fetchingDownload;
bool get _isDownloaded => widget.status == DownloadStatus.downloaded;
@override
Widget build(BuildContext context) {
return _buildButtonShape(
child: SizedBox(),
);
}
Widget _buildButtonShape({
required Widget child,
}) {
return AnimatedContainer(
duration: widget.transitionDuration,
curve: Curves.ease,
width: double.infinity,
decoration: _isDownloading || _isFetching
? ShapeDecoration(
shape: const CircleBorder(),
color: Colors.white.withOpacity(0.0),
)
: const ShapeDecoration(
shape: StadiumBorder(),
color: CupertinoColors.lightBackgroundGray,
),
child: child,
);
}
}
You might wonder why you need a ShapeDecoration
widget for a transparent circle, given that it’s invisible.
この時点ではあなたは、「透明な円のためのShapeDecorationがなぜ必要なのだろう」と思われるかもしれません。「透明」だと目に見えないわけですから。
The purpose of the invisible circle is to orchestrate the desired animation.
不可視の円の目的は、目的のアニメーションを調整することです。
The AnimatedContainer
begins with a rounded rectangle.
AnimatedContainerは角の丸まった長方形の形からスタートします。
When the DownloadStatus
changes to fetchingDownload
, the AnimatedContainer
needs to animate from a rounded rectangle to a circle, and then fade out as the animation takes place.
DownloadStatusがfetchingDownloadに変化した時、AnimatedContainerは「角の丸まった長方形」から「円」へとアニメーション化する必要があります。同時に色もグレイから透明へとアニメーション化されます。
The only way to implement this animation is to define both the beginning shape of a rounded rectangle and the ending shape of a circle.
これを実装する唯一の方法は、アニメーション開始時の形(角の丸まった長方形)とアニメーション終了時の形(円)を両方とも定義することです。
But, you don’t want the final circle to be visible, so you make it transparent, which causes an animated fade-out.
The DownloadButton
displays GET
during the notDownloaded
phase, OPEN
during the downloaded
phase, and no text in between.
DownloadButton
はnotDownloadedフェーズでは「GET」を表示し、downloadedフェーズでは「OPEN」を表示します。そしてその間は何も文字列を表示しません。
Add widgets to display text during each download phase, and animate the text’s opacity in between.
それぞれのdownloadフェーズでテキストを表示するウィジェットを追加して、それぞれの状態に応じてテキストの透明度をアニメーション化します。
Add the text widget tree as a child of the shape widget tree.
shapeウィジェットツリーのchildにtextウィジェットツリーを追加します。
class _DownloadButtonState extends State<DownloadButton> {
@override
Widget build(BuildContext context) {
return _buildButtonShape(
child: _buildText(),
);
}
Widget _buildText() {
final text = _isDownloaded ? 'OPEN' : 'GET';
final opacity = _isDownloading || _isFetching ? 0.0 : 1.0;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: AnimatedOpacity(
duration: widget.transitionDuration,
opacity: opacity,
curve: Curves.ease,
child: Text(
text,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.button?.copyWith(
fontWeight: FontWeight.bold,
color: CupertinoColors.activeBlue,
),
),
),
);
}
}
Display a spinner while fetching download
During the fetchingDownload
phase, the DownloadButton
displays a radial spinner. This spinner fades in from the notDownloaded
phase and fades out to the fetchingDownload
phase.
fetchingDownloadフェーズでは、DownloadButtonはラジアルスピナー(radial spinner)を表示します。
このスピナーはnotDownloadedフェーズ終了時にフェードイン(出現する)して、fetchingDownloadフェーズの終了時にフェードアウト(消える)します。
Implement a radial spinner that sits on top of the button shape and fades in and out at the appropriate times.
Stackの一番上(手前)にスピナーを配置して、適切なタイミングでフェードイン・フェードアウトさせるように実装します。
class _DownloadButtonState extends State<DownloadButton> {
@override
Widget build(BuildContext context) {
return Stack(
children: [
_buildButtonShape(
child: _buildText(),
),
_buildDownloadingProgress(),
],
);
}
Widget _buildDownloadingProgress() {
return Positioned.fill(
child: AnimatedOpacity(
duration: widget.transitionDuration,
opacity: _isDownloading || _isFetching ? 1.0 : 0.0,
curve: Curves.ease,
child: _buildProgressIndicator(),
),
);
}
Widget _buildProgressIndicator() {
return AspectRatio(
aspectRatio: 1.0,
child: CircularProgressIndicator(
backgroundColor: Colors.white.withOpacity(0.0),
valueColor: AlwaysStoppedAnimation(
CupertinoColors.lightBackgroundGray
),
strokeWidth: 2.0,
value: null,
),
);
}
}
参考