Skip to content
Go back

【Flutter】 Stepper 使い倒してみた!

by Aoi

本記事では Stepper Widgetの紹介を行います。

Speaker
ステップ毎にユーザーに作業してもらうUIを作りたいんだけど、
ゼロから作るの大変そう、、、

そんな悩みに応えるのがStepper Widgetです!

Stepper Widget を使えば、以下のgifのようなUIが簡単に作れちゃいます!

本記事ではStepper Widgetの基本的な使い方から、 細かい設定まで徹底的に解説していきます。

ぜひ読んでみてください!

基本的な使い方

基本的な使い方をサンプルコードをベースに解説します。

まずはこちらのサンプルコードをご覧ください。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(primaryColor: Colors.blue),
      home: Scaffold(
        appBar: AppBar(title: const Text('Stepper サンプル')),
        body: const StepperSample(),
      ),
    );
  }
}

//1
class StepperSample extends StatefulWidget {
  const StepperSample({
    Key? key,
  }) : super(key: key);

  @override
  State createState() => _StepperSampleState();
}

class _StepperSampleState extends State {
  //2
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    //3
    return Stepper(
      //4
      currentStep: _index,
      //5
      onStepCancel: () {
        if (_index != 0) {
          setState(() {
            _index -= 1;
          });
        }
      },
      //6
      onStepContinue: () {
        if (_index != 2) {
          setState(() {
            _index += 1;
          });
        }
      },
      //7
      onStepTapped: (int index) {
        setState(() {
          _index = index;
        });
      },
      //8
      steps: const [
        //9
        Step(
          //10
          title: Text('Step 1 タイトル'),
          //11
          content: Text('Step 1 です'),
        ),
        Step(
          title: Text('Step 2 タイトル'),
          content: Text('Step 2 です'),
        ),
        Step(
          title: Text('Step 3 タイトル'),
          content: Text('Step 3 です'),
        ),
      ],
    );
  }
}

ポイントを解説していきます。

//1 Stepper Widget を使用するStateful Widgetです。 Stepperではユーザーの選択がStep1の時、Step2の時で表示が変えなければいけません。 表示を変えるためには、『ユーザーが選択したStepが何番目か』を状態として持っておき、 この状態に応じてUIを表示するのが良いです。 このため、今回は「状態に応じてUIを表示する」のが得意なStateful Widgetを使っています。

//2 『ユーザーが選択したStepが何番目か』を表す状態です。

//3 Stepperの実装部分です。

//4 『何番目のStepを表示するか』を管理するプロパティです。 ここで設定している_indexの値がユーザー操作によって変わることで、 Stepperの表示Stepが変わります。

//5 各StepのCANCELボタンが押された時の動作を表すプロパティです。 Stepperでは、CONTINUE(次へ)ボタンとCANCEL(戻る)ボタンが デフォルトで表示されます。 今回は、CANCELボタンが押された時に_indexの値を1減らし SetStateすることで、 『表示するStepを前のStepにする』動作を設定しています。

//6 各StepのCONTINUEボタンが押された時の動作を表すプロパティです。 今回は、CONTINUEボタンが押された時に_indexの値を1増やし SetStateすることで、 『表示するStepを次のStepにする』動作を設定しています。

//7 各Stepのタイトルを押した時の動作を表すプロパティです。 今回はタイトルが押された時に、 押されたタイトルのindex_indexに設定し、 SetStateすることで、 『押されたStepを表示する』動作を設定しています。

//8 表示するStepのリストを設定するプロパティです。 後述するStepクラスのリストを設定することで、 表示するStep全体を設定することができます。

//9 表示するStepそれ自体を表すクラスです。 後述するtitleプロパティとcontentプロパティが必須です。

//10 Stepのタイトルを設定するプロパティです。 今回はシンプルにテキストを設定していますが、 画像やアイコンなどWidgetならなんでも設定可能です。

//11 Stepの内容(コンテンツ)を設定するプロパティです。 タイトルと同様に、 画像やアイコンなどWidgetならなんでも設定可能です。

以上がStepperの基本的な使い方です。 Stateful Widgetや状態が絡んでくるのでちょっと複雑に感じるかもしれません。 そんな時は、Debugモードや紙で_indexの値を追いかけてみると理解が進むと思います。 ぜひやってみてください!

詳細設定

Stepperでは様々な設定が用意されています。 一つ一つの設定について解説していきます。

CONTINUE 、CANCEL ボタンの変更

Stepperで自動でついてくるCONTINUEボタンと、CANCELボタン、 これらをカスタマイズすることが可能です。

controlsBuilderプロパティにて、 CONTINUEとCANCELが表示されていたコントロール部分のWidgetを変更できます。

以下の例は、CONTINUEのボタンを「次へ」のTextButtonに、 CANCELのボタンを「戻る」のTextButtonに変更した例です。

@override
  Widget build(BuildContext context) {
    return Stepper(
      //12
      controlsBuilder: (BuildContext context, ControlsDetails details) {
        return Row(
          children: [
            TextButton(
              //13
              onPressed: details.onStepContinue,
              child: const Text('次へ'),
            ),
            TextButton(
              //14
              onPressed: details.onStepCancel,
              child: const Text('戻る'),
            ),
          ],
        );
      },
      // ・・・

//12 controlsBuilderの設定部分です。 注目すべきは、ControlsDetailsで、 details.currentStepで現在のStepのインデックスを取得したり、 details.onStepContinuedetails.onStepCancelStepperの同名プロパティの内容を取得したりなど、 親のStepperの色々な情報を読み取ることができます。

//13 details.onStepContinueStepperonStepContinueで設定した動作を設定しています。

//14 details.onStepCancelStepperonStepCancelで設定した動作を設定しています。

marginプロパティを設定することで、Stepperとボタンの間の隙間を調整可能です。

Stepperの方向の変更

type プロパティを設定することでStepperを水平方向に表示することができます。

Stepper(
      type: StepperType.horizontal,
// ・・・

StepperType.horizontalで水平方向、 StepperType.verticalで鉛直方向を指定できます。

水平方向の時、elevationプロパティの値を設定することで、 Stepperの下の影の大きさを変更することが可能です。

各Stepの見た目の変更

Stepperで表示する各Stepも様々なプロパティが用意されており、 見た目の変更が可能です。

サブタイトルの追加

Stepのタイトルの下にサブタイトルを設定可能です。

subtitleプロパティにWidgetを設定することで、タイトルの下に表示させることができます。

Stepper(
      // ・・・
      steps: const [
        Step(
          title: Text('Step 1 タイトル'),
          subtitle: Text('Step 1 サブタイトル'),
          content: Text('Step 1 です'),
        ),
        // ・・・
        ),
      ],
    );

アイコンの変更

丸と数字で表されていたStepperのアイコンを、 いくつかのプリセットのアイコンに変更することが可能です。

StepのStepStateを設定することでアイコンの変更が可能となります。

Stepper(
      // ・・・
      steps: const [
        Step(
          title: Text('Step 1 タイトル'),
          content: Text('Step 1 です'),
          //15
          state:StepState.complete
        ),
        Step(
          title: Text('Step 2 タイトル'),
          content: Text('Step 2 です'),
          //16
          state:StepState.editing
        ),
        Step(
          title: Text('Step 3 タイトル'),
          content: Text('Step 3 です'),
          //17
          state:StepState.disabled
        ),
        Step(
          title: Text('Step 4 タイトル'),
          content: Text('Step 4 です'),
          //18
          state:StepState.error
        ),
      ],
    );

//15 StepState.completeでアイコンをチェックマークに変更できます。

//16 StepState.editingでアイコンをペンマークに変更できます。

//17 StepState.disabledでそのStepを非活動化できます。 これによりStepのタップができなくなります。

//18 StepState.errorでアイコンを三角形のエラーマークに変え、 色を赤色に変更します。

活動化

StepのプロパティであるisActivetrueにすることで、 アイコンの色をThemePrimaryColorに変更可能です。

Stepper(
      // ・・・
      steps: const [
        Step(
          title: Text('Step 1 タイトル'),
          content: Text('Step 1 です'),
          isActive: true
        ),
        // ・・・
        ),
      ],
    );

サンプルコード

以上が詳細設定となります。 最後に、今回紹介した詳細設定を入れ込んだ、サンプルコードを紹介します。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(title: 'Stepper サンプル'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Stepper(
        controlsBuilder: (context, details) {
          return Row(
            children: [
              if (details.currentStep != 2)
                TextButton(
                  onPressed: details.onStepContinue,
                  child: const Text('次へ'),
                ),
              if (details.currentStep != 0)
                TextButton(
                  onPressed: details.onStepCancel,
                  child: const Text('戻る'),
                ),
            ],
          );
        },
        currentStep: _index,
        onStepCancel: () {
          if (_index != 0) {
            setState(() {
              _index -= 1;
            });
          }
        },
        onStepContinue: () {
          if (_index != 2) {
            setState(() {
              _index += 1;
            });
          }
        },
        onStepTapped: (int index) {
          setState(() {
            _index = index;
          });
        },
        steps: [
          Step(
            title: Text('ステップ 1'),
            subtitle: Text('subtitle'),
            content: Text('ステップ 1 コンテンツ'),
            isActive: _index == 0,
            state: _index == 0 ? StepState.editing : StepState.complete,
          ),
          Step(
            title: Text('ステップ 2'),
            content: Text('ステップ 2 コンテンツ'),
            isActive: _index == 1,
            state: _index == 1
                ? StepState.editing
                : _index > 1
                    ? StepState.complete
                    : StepState.disabled,
          ),
          Step(
            title: Text('ステップ 3'),
            content: Text('ステップ 3 コンテンツ'),
            isActive: _index == 2,
            state: _index == 2
                ? StepState.editing
                : _index > 2
                    ? StepState.complete
                    : StepState.disabled,
          ),
        ],
      ),
    );
  }
}

本記事では、Stepper Widgetの紹介を行いました。

基本的な使い方から詳細設定まで、徹底的に解説していきました。

Stepper Widgetを使うとかなり簡単にこなれたUIを実現できるのでオススメです。 ぜひ使ってみてください!

本記事があなたのアプリ開発の一助となることを願っています。

Flutterを一緒に学んでみませんか? Flutter エンジニアに特化した学習コミュニティ、Flutter大学への入会は、 以下の画像リンクから。

編集後記(2022/3/18)

私事ですが、昨日30回目の誕生日を迎えました。

とうとう、30になってしまったなぁ、というのが、正直な感想です。 諸先輩方からしたら、 まだ30じゃないか!、何言ってるんだ! と怒られてしまうかも知れませんね。

気持ちや精神年齢が30歳に追いついていない気がしてなりません。 子供の頃、思っていた30代ってもっと安定していて、落ち着いていたな、と思います。

まあ、そんなこと言っても仕方がないですよね。 足りないところは足りないと認め、埋める努力をしていかなきゃいけません。

子供の頃の自分に、思ってたのと違うかもだけど、充実してるよ!と胸張って言える、 そんな30代を過ごしていこうと思います。

今回紹介したStepper Widgetのように、 一歩一歩、理想に向かって進んで行きます。

週刊Flutter大学では、Flutterに関する技術記事、Flutter大学についての紹介記事を投稿していきます。 記事の更新情報はFlutter大学Twitterにて告知します。


Aoiのプロフィール画像

Aoi

ライター兼個人Flutter開発者 Flutterにて5つのアプリを開発。QiitaではFlutter記事にて約500のContributionを獲得。

Share this post on:

Previous Post
【Flutte大学】 共同開発 とは?新しい技術のチャレンジの場!
Next Post
【Flutter】 Skeleton テンプレート徹底解説