60
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Flutter #3Advent Calendar 2020

Day 7

【Flutter】スマホアプリでよく見かけるチュートリアルページを作る方法(3つのパッケージを比較)

Posted at

最終的にこういったチュートリアルが作れるようになります

左から、tutorial_coach_mark flutter_sliding_tutorial flutter_overboardを使用したチュートリアルページです。

また、GitHubにサンプルコードを用意しました。
ローカルにクローンしてpub getした後にビルドすれば上記のアプリを試せます。
ぜひ自分のアプリに組み込んでみてください。

環境

Mac OS X 10.15.7
Windows 10 Pro
両方で動作確認済み

iOSシミュレータ:iPhone 12 Max Proで動作確認
Androidエミュレータ:APIレベル28のPixel 3aで動作確認

[✓] Flutter (Channel stable, 1.22.4, on Mac OS X 10.15.7 19H114 darwin-x64, locale ja-JP)
 
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 12.3)
[!] Android Studio (version 4.1)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[!] VS Code (version 1.52.0)
    ✗ Flutter extension not installed; install from
      https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[✓] Connected device (1 available)

※Android Studioのプラグインのパスがバージョンアップにより微妙に変わってflutter doctorでエラーが発生するバグがあるようです。動作には影響ないので、修正されるまで放置しています。
※VSCodeのエラーが出ていますが、VSCodeはFlutterのビルドに使用していないので無視しています。

tutorial_coach_markを使ったチュートリアル

1つ目はtutorial_coach_markを用いた以下のようなチュートリアルを作成します。
tutorial_coach_mark.gif
公式のページ

tutorial_coach_markは実際の画面に対して組み込んでいくようなチュートリアルです。
私はチュートリアル側の実装をすべてmodel側に実装しましたが、別に同じページ内で実装しても良いと思います。
ざっくりいうと、フォーカスを当てたい箇所にGlobalKeyを仕込んでおいて、List<TargetFocus>で挙動を指定するという感じです。

tutorial_coach_mark_model.dart
class TutorialCoachMarkModel extends ChangeNotifier {
  final List<TargetFocus> targets = List();
  final GlobalKey keyButton = GlobalKey();
  final GlobalKey keyButton1 = GlobalKey();
  final GlobalKey keyButton2 = GlobalKey();

  void init() {
    targets.add(
      TargetFocus(
        identify: "Target 0",
        keyTarget: keyButton,
        contents: [
          ContentTarget(
              align: AlignContent.bottom,
              child: Container(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Text(
                      "フォーカスを当ててみる",
                      style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.white,
                          fontSize: 20.0),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 10.0),
                      child: Text(
                        "もう一度focusを押すとfocus1にフォーカスが移ります",
                        style: TextStyle(color: Colors.white),
                      ),
                    )
                  ],
                ),
              ))
        ],
      ),
    );

定義したターゲットリストにTargetFocusを追加していきます。
keyTargetGlobalKeyを指定することによって、その要素にフォーカスが向きます。

また、以下のように、shapeにShapeLightFocus.RRecを指定するとフォーカスが四角になります。

tutorial_coach_mark_model.dart
// package:tutorial_coach_mark/animated_focus_light.dart のインポートが必要
        shape: ShapeLightFocus.RRect,
        radius: 5,

実際にチュートリアルを表示する際はTutorialCoachMarkに定義したList<TargetFocus>を渡してやります。
targetがクリックされたときの処理とskipがクリックされたときの処理をそれぞれonClickTragetonClickSkipに定義します。
私が用意したサンプルではprintしているだけです。

tutorial_coach_mark_model.dart
 void showTutorial(BuildContext context) {
    TutorialCoachMark(context,
        targets: targets,
        colorShadow: Colors.red,
        textSkip: "SKIP",
        paddingFocus: 10,
        opacityShadow: 0.8, onFinish: () {
      print("finish");
    }, onClickTarget: (target) {
      print(target);
    }, onClickSkip: () {
      print("skip");
    })
      ..show();
  }

flutter_sliding_tutorialを使ったチュートリアル

次にflutter_sliding_tutorialを用いたチュートリアルを紹介します。
以下のようなものが作成できます。
flutter_sliding_tutorial.gif
公式ページを見ていただければわかりますが、本当はもっとリッチなページが作成できます。(本当は画像やアイコンを複数配置して、かっこよくスライドさせられる)

私の例ではStatelessWidgetで実装してしまいましたが、どうやらStatefulWidgetでないとうまく動かないようです。

スライドして表示するページを用意し、indexValueNotifier<double> notifierを渡しながらAnimatedBackgroundColorのなかのchildren要素でそれらを呼ぶ、といった感じでしょうか。
私は今回こちらを採用しなかったので、機会あればもう少し公式ドキュメント読み込んで実装してみようとおもいます。

flutter_overboardを使ったチュートリアル

最後にflutter_overboardを使ったチュートリアルの作成です。以下のようなチュートリアルページが作成できます。
flutter_overboard_demo.gif

公式はこちら
こちらは細かい制御こそできないものの、かなり簡単に初回起動時に表示されるようなタイプのチュートリアルページを実装できます。

flutter_overboard_page.dart
class FlutterOverboardPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('FlutterOverboardPage'),
      ),
      body: OverBoard(
        pages: pages,
        showBullets: true,
        skipCallback: () {
          // when user select SKIP
          Navigator.pop(context);
        },
        finishCallback: () {
          // when user select NEXT
          Navigator.pop(context);
        },
      ),
    );
  }

  final pages = [
    PageModel(
        color: const Color(0xFF95cedd),
        imageAssetPath: 'assets/0.png',
        title: '文字を表示できます',
        body: '細かい説明をbodyに指定して書くことが出来ます',
        doAnimateImage: true),
    PageModel(
        color: const Color(0xFF9B90BC),
        imageAssetPath: 'assets/1.png',
        title: '左右のスワイプ',
        body: 'NEXTを押さなくても左右にスワイプすることで画面の切替が出来ます',
        doAnimateImage: true),
    PageModel.withChild(
        child: Padding(
            padding: EdgeInsets.only(bottom: 25.0),
            child: Text(
              "さあ、始めましょう",
              style: TextStyle(
                color: Colors.white,
                fontSize: 32,
              ),
            )),
        color: const Color(0xFF5886d6),
        doAnimateChild: true)
  ];
}

PageModelでページを定義して、リストで保持しておき、OverBoardpagesで渡してあげるだけです。
かなり簡単ですが、PageModelで指定できる要素は少なく、カスタマイズしにくいです。
PageModel.withChildとして呼べばchildでその他のWidgetを呼べるので色々表示しようと思えばできます。

比較してみて

アプリの初回起動時に1度だけ表示するチュートリアルページはflutter_overboardが使いやすかったです。
アプリ操作時のスクリーンショットを撮って、少しわかりやすく加工、テキスト追加などで済ませてしまえば、サクッと実装できそうだなと感じました。
また、tutorial_coach_markは操作が分かりづらいであろうページに個別に仕込んでおいて、AppBarのメニューなどからヘルプとして呼び出したりできるようにすると良いのかなと思いました。
新機能を追加したときなどに使い方をレクチャーするために導入するのにも良さそうです。

おまけ①〜アセットファイル〜

Flutterでの開発時にアセットフォルダから画像などを読み込む場合は、pubspec.yamlに読み込む画像を追加する必要があるようです。(私は知りませんでした・・・)

pubspec.yaml
flutter:
  assets:
    - assets/hello.json
    - assets/flutter-logo.png

おまけ②〜初回起動時のみの表示〜

iOSであればNSUserDefaults、AndroidならSharedPreferencesを使って永続化するのが一般的かと思いますが、FlutterにもSharedPreferencesがあります。
pubspec.yamlshared_preferences追加してpub getします。

トップ画面でWidgetsBinding.instance.addPostFrameCallback()を呼びます。
これによって画面描画が完了した直後にメソッドの呼び出しができます。

main.dart
class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) => _showTutorial(context));

呼び出したメソッドの中でSharedPreferencesを使って初回起動かどうかチェックしています。
初回起動でチュートリアル画面の表示が終わったら、setBool()で初回起動フラグにtrueを渡してフラグを永続化します。

main.dart
void _showTutorial(BuildContext context) async {
    final pref = await SharedPreferences.getInstance();

    if (pref.getBool('isAlreadyFirstLaunch') != true) {
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => FlutterOverboardPage(),
          fullscreenDialog: true,
        ),
      );
      pref.setBool('isAlreadyFirstLaunch', true);
    }
  }

参考

60
42
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
60
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?