最終的にこういったチュートリアルが作れるようになります
左から、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は実際の画面に対して組み込んでいくようなチュートリアルです。
私はチュートリアル側の実装をすべてmodel側に実装しましたが、別に同じページ内で実装しても良いと思います。
ざっくりいうと、フォーカスを当てたい箇所にGlobalKey
を仕込んでおいて、List<TargetFocus>
で挙動を指定するという感じです。
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
を追加していきます。
keyTarget
でGlobalKey
を指定することによって、その要素にフォーカスが向きます。
また、以下のように、shapeにShapeLightFocus.RRec
を指定するとフォーカスが四角になります。
// package:tutorial_coach_mark/animated_focus_light.dart のインポートが必要
shape: ShapeLightFocus.RRect,
radius: 5,
実際にチュートリアルを表示する際はTutorialCoachMark
に定義したList<TargetFocus>
を渡してやります。
target
がクリックされたときの処理とskip
がクリックされたときの処理をそれぞれonClickTraget
、onClickSkip
に定義します。
私が用意したサンプルではprint
しているだけです。
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を用いたチュートリアルを紹介します。
以下のようなものが作成できます。
公式ページを見ていただければわかりますが、本当はもっとリッチなページが作成できます。(本当は画像やアイコンを複数配置して、かっこよくスライドさせられる)
私の例ではStatelessWidgetで実装してしまいましたが、どうやらStatefulWidgetでないとうまく動かないようです。
スライドして表示するページを用意し、index
とValueNotifier<double> notifier
を渡しながらAnimatedBackgroundColor
のなかのchildren
要素でそれらを呼ぶ、といった感じでしょうか。
私は今回こちらを採用しなかったので、機会あればもう少し公式ドキュメント読み込んで実装してみようとおもいます。
flutter_overboardを使ったチュートリアル
最後にflutter_overboardを使ったチュートリアルの作成です。以下のようなチュートリアルページが作成できます。
公式はこちら
こちらは細かい制御こそできないものの、かなり簡単に初回起動時に表示されるようなタイプのチュートリアルページを実装できます。
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
でページを定義して、リストで保持しておき、OverBoard
のpages
で渡してあげるだけです。
かなり簡単ですが、PageModel
で指定できる要素は少なく、カスタマイズしにくいです。
PageModel.withChild
として呼べばchild
でその他のWidgetを呼べるので色々表示しようと思えばできます。
比較してみて
アプリの初回起動時に1度だけ表示するチュートリアルページはflutter_overboardが使いやすかったです。
アプリ操作時のスクリーンショットを撮って、少しわかりやすく加工、テキスト追加などで済ませてしまえば、サクッと実装できそうだなと感じました。
また、tutorial_coach_markは操作が分かりづらいであろうページに個別に仕込んでおいて、AppBarのメニューなどからヘルプとして呼び出したりできるようにすると良いのかなと思いました。
新機能を追加したときなどに使い方をレクチャーするために導入するのにも良さそうです。
おまけ①〜アセットファイル〜
Flutterでの開発時にアセットフォルダから画像などを読み込む場合は、pubspec.yamlに読み込む画像を追加する必要があるようです。(私は知りませんでした・・・)
flutter:
assets:
- assets/hello.json
- assets/flutter-logo.png
おまけ②〜初回起動時のみの表示〜
iOSであればNSUserDefaults、AndroidならSharedPreferencesを使って永続化するのが一般的かと思いますが、FlutterにもSharedPreferences
があります。
pubspec.yaml
にshared_preferences
追加してpub get
します。
トップ画面でWidgetsBinding.instance.addPostFrameCallback()
を呼びます。
これによって画面描画が完了した直後にメソッドの呼び出しができます。
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => _showTutorial(context));
呼び出したメソッドの中でSharedPreferences
を使って初回起動かどうかチェックしています。
初回起動でチュートリアル画面の表示が終わったら、setBool()
で初回起動フラグにtrueを渡してフラグを永続化します。
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);
}
}