🎯

Flutterでインタラクティブなチュートリアルを実装!tutorial_coach_markの使い方

2025/03/19に公開

はじめに

記事の概要

アプリに適切なチュートリアルを導入すれば、ユーザーはアプリの操作をスムーズに理解できます。
アプリのチュートリアルを画像やGIFで説明することもできますが、ユーザーの操作に応じてステップごとに説明を加えたい場合は、インタラクティブなチュートリアルが効果的です。

本記事では、tutorial_coach_markを使って、Flutterでインタラクティブなチュートリアルを導入する方法を解説します。

環境

$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.29.1, on macOS 15.3.2 24D81 darwin-arm64 (Rosetta), locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.2)
[✓] VS Code (version 1.98.1)
[✓] Connected device (6 available)
[✓] Network resources

• No issues found!

インタラクティブなチュートリアルの実装

完成形

Flutterでデフォルトで用意されているカウンターアプリを例にインタラクティブなチュートリアルの導入方法を解説します。
チュートリアルは最終的に以下のようになります。

+ボタンにフォーカスさせる

まずはアプリを初めて開いた後で+ボタンを押すようユーザーを促すステップを追加するために、下記のコードを修正します。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,

        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

インタラクティブなチュートリアルを実装するためのパッケージを追加します。

flutter pub add tutorial_coach_mark

チュートリアルを実装する流れは以下の通りです。

  1. チュートリアルの設定を作成する。
  2. チュートリアルを表示する。

アプリの初回起動時にチュートリアルを実施したいので、initStateメソッドにチュートリアルの設定と表示の処理を書きます。
チュートリアルの設定の作成をメソッド_createTutorial、チュートリアルの表示をメソッド_showTutorialに任せます。

import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';

class _MyHomePageState extends State<MyHomePage> {
  // ...

  
  void initState() {
    super.initState();
    final tutorial = _createTutorial(); // チュートリアルの設定を作成
    _showTutorial(tutorial); // チュートリアルを表示
  }

  TutorialCoachMark _createTutorial() {
    return TutorialCoachMark(targets: _createTargets());
  }

  void _showTutorial(TutorialCoachMark tutorial) {
    tutorial.show(context: context);
  }

  // ...
}

次は、+ボタンをタップするよう促すチュートリアルを下記のように定義します。

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  final plusButtonKey = GlobalKey(); // +ボタンをキーを定義

  // ...

  TutorialCoachMark _createTutorial() {
    return TutorialCoachMark(
      targets: _createTargets(),
      hideSkip: true, // スキップできないようにする
    );
  }

  List<TargetFocus> _createTargets() {
    List<TargetFocus> targets = [];
    targets.add(
      TargetFocus(
        keyTarget: plusButtonKey, // +ボタンが説明対象であることを明示する
        color: Colors.purple,
        contents: [
          TargetContent(
            align: ContentAlign.top,
            builder: (context, controller) {
              return Text(
                'ボタンをタップすると、カウンターが増えます。',
                style: TextStyle(fontSize: 20, color: Colors.white),
              );
            },
          ),
        ],
      ),
    );

    return targets;
  }

  // ...

  
  Widget build(BuildContext context) {
    return Scaffold(
      // ...
      floatingActionButton: FloatingActionButton(
        key: plusButtonKey, // +ボタンにkeyを設定
        // ...
      ),
    );
  }
}

チュートリアルの表示は出来ていますが、タップしてもカウンターの値が増加していません。
カウンターの値が増加するように、問題を以下のように修正します。

  TutorialCoachMark _createTutorial() {
    return TutorialCoachMark(
      targets: _createTargets(),
      hideSkip: true,
      onClickTarget: (target) {
        _incrementCounter(); // +ボタンをタップしたときにカウンターを増加させる
      },
    );
  }

最終的なコードは下記のようになります。

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  final plusButtonKey = GlobalKey();

  
  void initState() {
    super.initState();
    final tutorial = _createTutorial();
    _showTutorial(tutorial);
  }

  TutorialCoachMark _createTutorial() {
    return TutorialCoachMark(
      targets: _createTargets(),
      hideSkip: true,
      onClickTarget: (target) {
        _incrementCounter();
      },
    );
  }

  List<TargetFocus> _createTargets() {
    List<TargetFocus> targets = [];
    targets.add(
      TargetFocus(
        keyTarget: plusButtonKey,
        color: Colors.purple,
        contents: [
          TargetContent(
            align: ContentAlign.top,
            builder: (context, controller) {
              return Text(
                'ボタンをタップすると、カウンターが増えます。',
                style: TextStyle(fontSize: 20, color: Colors.white),
              );
            },
          ),
        ],
      ),
    );

    return targets;
  }

  void _showTutorial(TutorialCoachMark tutorial) {
    tutorial.show(context: context);
  }

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,

        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        key: plusButtonKey,
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

おわりに

最後まで記事を読んでくださり、ありがとうございます!
本記事の内容を応用して、より良いアプリを作ることの手助けになれば幸いです😌

Discussion