2
0

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 5 years have passed since last update.

Flutter学習4日目 -公式を読む 5. インタラクティブ機能を実装-

2
Posted at

※自分の学習記録のメモとしてこの記事を書いています。

1日目:Flutter学習1日目 -公式を読む 1. インストール編-
2日目前編:Flutter学習2日目前編 -公式を読む 2. エディターの設定編-
2日目後編:Flutter学習2日目後編 -公式を読む 3. テストドライブ編-
3日目: Flutter学習3日目 -公式を読む 4. スターターFlutterアプリを作成する-
4日目: Flutter学習4日目 -公式を読む 5. インタラクティブ機能を実装-(イマココ)

今回はWrite Your First Flutter App, part 2ページの通り、3日目で作成したスターターFlutterアプリにインタラクティブ機能を追加したり、ルート(2ページ目)を実装していきます!✊

パート2で学ぶこと

  • iOS、Android、Webで自然に見えるFlutterアプリの書き方
    開発サイクルを早くするためのホットリロードの使い方
  • ステートフルウィジェットにインタラクティブ性を追加する方法
  • セカンドスクリーンの作成方法とナビゲート方法
  • テーマを使ってアプリの見た目を変える方法

パート2で作るもの

スタートアップ企業のために、提案された名前の無限のリストを生成するシンプルなモバイルアプリから始めます。コードラボが終了するまでに、エンドユーザーは名前を選択したり、選択解除したりして、最適なものを保存することができます。アプリバーの右上にあるリストアイコンをタップすると、お気に入りの名前だけをリストアップした新しいページ(ルートと呼ばれる)に移動します。

Flutter環境をセットアップする

パート1を完了していない場合は、Write your first Flutter app, part 1を参照してください。

リストにアイコンを追加

このステップでは、各行にハート♡のアイコンを追加します。次のステップでは、タップ可能にしてお気に入りを保存します。

RandomWordsState_saved Setを追加します。このSetには、ユーザーがお気に入りにした単語のペアリングが保存されます。適切に実装されたSetは重複したエントリを許可しないので、ListよりもSetの方が好ましいです。


class _RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
+ final _saved = Set<WordPair>();
  final _biggerFont = TextStyle(fontSize: 18.0);
  ...
}

_buildRow関数では、単語のペアリングがまだお気に入りに追加されていないことを確認するために、alreadySavedチェックを追加します。


Widget _buildRow(WordPair pair) {
+ final alreadySaved = _saved.contains(pair);
  ...
}

テキストの後にアイコンを追加します。


Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return ListTile(
    title: Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
+   trailing: Icon(
+     alreadySaved ? Icons.favorite : Icons.favorite_border,
+     color: alreadySaved ? Colors.red : null,
+   ),
  );
}

これで各列にハートが表示されるはずですが、まだインタラクティブではありません。

スクリーンショット 2020-08-02 8.00.00.png

インタラクティブ性の追加

このステップでは、ハートのアイコンをタップ可能にします。ユーザーがリスト内の項目をタップしてお気に入りの状態を切り替えると、その単語のペアが保存されたお気に入りのセットに追加または削除されます。

これを行うには、_buildRow関数を修正します。既にお気に入りに追加されている単語エントリを再度タップすると、お気に入りから削除されます。タイルがタップされた場合、この関数は setState() を呼び出してフレームワークに状態が変わったことを通知します。

以下のように、_buildRowメソッドにonTapを追加します。


Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return ListTile(
    title: Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: Icon(
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),
+   onTap: () {
+     setState(() {
+       if (alreadySaved) {
+         _saved.remove(pair);
+       } else { 
+         _saved.add(pair); 
+       } 
+     });
+   },
  );
}

Tip💡:Flutter のリアクティブスタイルフレームワークでは、setState() を呼び出すと、State オブジェクトの build() メソッドが呼び出され、UI が更新されます。

タイルをタップすることで、エントリーをお気に入りにしたり、お気に入りから外したりすることができます。

スクリーンショット 2020-08-02 8.11.57.png

新しい画面に遷移する

このステップでは、お気に入りを表示する新しいページ (Flutter のルートと呼ばれる) を追加します。ホームルートと新しいルートの間をナビゲートする方法を学びます。

Flutterでは、Navigatorはアプリのルートを含むスタックを管理します。Navigatorのスタックにルートをプッシュすると、そのルートの表示が更新されます。Navigatorのスタックからルートをポップすると、表示は前のルートに戻ります。

次に、_RandomWordsStatebuild メソッドで AppBar にリストアイコンを追加します。ユーザがリストアイコンをクリックすると、保存したお気に入りを含む新しいルートが Navigator にプッシュされ、アイコンが表示されます。

アイコンとそれに対応するアクションをbuildメソッドに追加します。


class _RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Startup Name Generator'),
        actions: [
          IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

Tip💡: ウィジェットのプロパティの中には、単一のウィジェット(child )を取り、他のプロパティ(例えば action)は、角括弧([])で示されるように、ウィジェットの配列(children)を取ります。

RandomWordsStateクラスに_pushSaved()関数を追加します。


void _pushSaved() {
}

次に、ルートを構築してNavigatorのスタックにプッシュします。そのアクションにより、新しいルートが表示されるように画面が変更されます。新しいページのコンテンツは、MaterialPageRoutebuilderプロパティに匿名関数でビルドされます。

Navigator.pushを呼び出して、ナビゲータのスタックにルートをプッシュします。


void _pushSaved() {
  Navigator.of(context).push(
  );
}

次に、MaterialPageRouteとそのビルダーを追加します。とりあえず、ListTileの行を生成するコードを追加します。ListTiledivideTiles()メソッドは、各ListTile間に水平方向の間隔を追加します。divide変数は、便利な関数toList()によってリストに変換された最終的な行を保持します。


void _pushSaved() {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (BuildContext context) {
          final tiles = _saved.map(
            (WordPair pair) {
              return ListTile(
                title: Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );
          final divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();

          return Scaffold(
            appBar: AppBar(
              title: Text('Saved Suggestions'),
            ),
            body: ListView(children: divided),
          );
        },
      ),
    );
  }
}

builder プロパティは、SavedSuggestions という名前の新しいルートのアプリバーを含むScaffoldを返します。新しいルートの本体は ListTiles 行を含む ListView で構成されています。各行は区切り線で区切られています。

これでお気に入りしたリストが表示されるようになりました🎉

スクリーンショット 2020-08-02 8.49.24.png
スクリーンショット 2020-08-02 8.49.26.png

テーマを使ってUIを変更する

このステップでは、アプリのテーマを変更します。テーマはアプリの外観をコントロールします。物理デバイスまたはエミュレータに依存するデフォルトのテーマを使用するか、ブランディングを反映させるためにテーマをカスタマイズすることができます。

ThemeDataクラスを設定することで、アプリのテーマを簡単に変更できます。アプリはデフォルトのテーマを使用しますが、アプリのprimary colorを白に変更します。


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
+     theme: ThemeData(
+       primaryColor: Colors.white,
+     ),
      home: RandomWords(),
    );
  }
}

Primary colorが変更されたことによりアプリバーの色が変わりました🎉

スクリーンショット 2020-08-02 8.58.49.png

コードの全体像はこちら。

marin.dart
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',    
      theme: ThemeData(
        primaryColor: Colors.white,
      ),        
      home: RandomWords(),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  _RandomWordsState createState() => _RandomWordsState();
}

class _RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _saved = Set<WordPair>();
  final _biggerFont = TextStyle(fontSize: 18.0);
  void _pushSaved() {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (BuildContext context) {
          final tiles = _saved.map(
            (WordPair pair) {
              return ListTile(
                title: Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );
          final divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();
          return Scaffold(
            appBar: AppBar(
              title: Text('Saved Suggestions'),
            ),
            body: ListView(children: divided),
          );
        },
      ),
    );
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Startup Name Generator'),
        actions: [
          IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  Widget _buildSuggestions() {
    return ListView.builder(
        padding: EdgeInsets.all(16.0),
        itemBuilder: (context, i) {
          if (i.isOdd) return Divider(); 
          final index = i ~/ 2; 
          if (index >= _suggestions.length) {
            _suggestions.addAll(generateWordPairs().take(10));  
          }
          return _buildRow(_suggestions[index]);
        });
  }
  Widget _buildRow(WordPair pair) {
    final alreadySaved = _saved.contains(pair); 
    return ListTile(
      title: Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
      trailing: Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: alreadySaved ? Colors.red : null,
      ),
      onTap: () {
        setState(() {
          if (alreadySaved) {
            _saved.remove(pair);
          } else { 
            _saved.add(pair); 
          } 
        });
      },
    );
  }
}

感想

今回はアプリらしい動きが学べたので、かんたんなアプリだったらもう既に作れそうで一段落したな〜と達成感がありました✨
Qiita記事書くのもようやく慣れてきました😂

次はLearn moreFlutter for web developersを読み進めます!

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?