Flutterアプリでtutorial_coach_markパッケージを使ってチュートリアルを簡単に実装する

「来期アニメ」アプリにチュートリアルをつけたいと思ってどんなパッケージがあるか調べました。
いくつかの中から、評判が良くて表示もかっこいいtutorial_coach_markを選んで試してみました。

tutorial_coach_mark | Flutter Package
Guide that helps you to present your app and its features in a beautiful, simple and customizable way.

パッケージのページにあるExampleを参考にして、試しにカウンターアプリに組み込んでみた結果がこれです。

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  tutorial_coach_mark: ^1.0.3 # 追加

main.dart

チュートリアルを表示したいウィジェットに、ターゲットを特定するためのkeyを設定します。

GlobalKey keyButton2= GlobalKey(); // ターゲットキー

Container(
              key: keyButton2, // ターゲットキーを設定
              child: Text(
                '$_counter',
                style: Theme.of(context).textTheme.headline4,
              ),
)

ターゲットウィジェットのチュートリアル表示を作ります。

  • keyTarget:ターゲットキーを指定
  • color:チュートリアル表示する際のマスクカラーを指定
  • align:ターゲットウィジェットの上下左右のどこに出すかを指定
  • controller.previous():チュートリアルを戻る
targets.add(
      TargetFocus(
        identify: "Target 2",
        color: Colors.blue, // マスクカラー。デフォルトは赤。
        keyTarget: keyButton2, // ターゲットキーを指定。
        contents: [
          TargetContent(
            align: ContentAlign.top, // ターゲットウィジェットのどちら側にチュートリアルを出すか。
            builder: (context, controller) {
              return Container(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Text(
                      "チュートリアルタイトル2",
                      style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.white,
                          fontSize: 20.0),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 10.0),
                      child: Text(
                        "チュートリアル説明。\nターゲットの上側に出す指定。\nマスクカラーは青。\nTextはターゲット指定できないらしいのでContainerで囲んだ。\n戻るボタンを置ける。",
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        controller.previous(); // 「チュートリアル戻る」ボタン
                      },
                      child: Icon(Icons.chevron_left),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
);

たったこれだけでいい感じのチュートリアルが動くようになります。
あとは必要な分だけ個別のチュートリアルを作るだけ。

最後にチュートリアルの開始方法は以下です。
各種イベント処理の指定や終了ボタンのテキスト等が設定できるようです。

  void showTutorial() {
    tutorialCoachMark = TutorialCoachMark(
      context,
      targets: targets,
      colorShadow: Colors.red,
      textSkip: "終了",
      paddingFocus: 10,
      opacityShadow: 0.8,
      onFinish: () {
        print("finish");
      },
      onClickTarget: (target) {
        print('onClickTarget: $target');
      },
      onSkip: () {
        print("skip");
      },
      onClickOverlay: (target) {
        print('onClickOverlay: $target');
      },
    )..show();
  }

まとめ

めちゃくちゃ簡単に、しかもいい感じのかっこいいチュートリアルを作れるのこのパッケージはかなり素晴らしいと思いました。

使い方は大体わかったので明日から「来期アニメ」アプリに組み込んでいこうと思います。
途中経過はまた記事にします。

この記事を気に入っていただけたなら、ぜひ私の自作アプリのインストールをお願いします。
インストール数が全然伸びなくてモチベーション低下中です。。

ソース全文

import 'package:flutter/material.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  late TutorialCoachMark tutorialCoachMark;
  List<TargetFocus> targets = <TargetFocus>[];

  GlobalKey keyButton1 = GlobalKey();
  GlobalKey keyButton2 = GlobalKey();
  GlobalKey keyButton3 = GlobalKey();

  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      showTutorial();
      _counter++;
    });
  }

  @override
  void initState() {
    initTargets();
    showTutorial();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: <Widget>[
          PopupMenuButton(
            key: keyButton3,
            icon: Icon(Icons.view_list, color: Colors.white),
            itemBuilder: (context) => [
              PopupMenuItem(
                child: Text("menu1"),
              ),
              PopupMenuItem(
                child: Text("menu2"),
              ),
              PopupMenuItem(
                child: Text("menu3"),
              ),
            ],
          )
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'チュートリアル実行回数',
            ),
            Container(
              key: keyButton2,
              child: Text(
                '$_counter',
                style: Theme.of(context).textTheme.headline4,
              ),
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        key: keyButton1,
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }

  void initTargets() {
    targets.add(
      TargetFocus(
        identify: "Target 1",
        keyTarget: keyButton1,
        contents: [
          TargetContent(
            align: ContentAlign.left,
            builder: (context, controller) {
              return Container(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Text(
                      "チュートリアルタイトル1",
                      style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.white,
                          fontSize: 20.0),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 10.0),
                      child: Text(
                        "チュートリアル説明。ターゲットの左側に出す指定。マスクカラーはデフォルト。",
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );

    targets.add(
      TargetFocus(
        identify: "Target 2",
        color: Colors.blue,
        keyTarget: keyButton2,
        contents: [
          TargetContent(
            align: ContentAlign.top,
            builder: (context, controller) {
              return Container(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Text(
                      "チュートリアルタイトル2",
                      style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.white,
                          fontSize: 20.0),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 10.0),
                      child: Text(
                        "チュートリアル説明。\nターゲットの上側に出す指定。\nマスクカラーは青。\nTextはターゲット指定できないらしいのでContainerで囲んだ。\n戻るボタンを置ける。",
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        controller.previous();
                      },
                      child: Icon(Icons.chevron_left),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );

    targets.add(
      TargetFocus(
        identify: "Target 3",
        color: Colors.orange,
        keyTarget: keyButton3,
        contents: [
          TargetContent(
            align: ContentAlign.bottom,
            builder: (context, controller) {
              return Container(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Text(
                      "チュートリアルタイトル3",
                      style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.white,
                          fontSize: 20.0),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 10.0),
                      child: Text(
                        "チュートリアル説明。\nターゲットの下側に出す指定。\nマスクカラーはオレンジ。",
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        controller.previous();
                      },
                      child: Icon(
                        Icons.chevron_left,
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );
  }

  void showTutorial() {
    tutorialCoachMark = TutorialCoachMark(
      context,
      targets: targets,
      colorShadow: Colors.red,
      textSkip: "終了",
      paddingFocus: 10,
      opacityShadow: 0.8,
      onFinish: () {
        print("finish");
      },
      onClickTarget: (target) {
        print('onClickTarget: $target');
      },
      onSkip: () {
        print("skip");
      },
      onClickOverlay: (target) {
        print('onClickOverlay: $target');
      },
    )..show();
  }
}

コメント

  1. […] […]

タイトルとURLをコピーしました