SKILL

Flutterで一覧画面(ListView)を作って表示する

モバイルアプリの基礎、一覧画面の作り方ってどうやるの?

このことを実践する形式で説明していきます。

FlutterはUIツール大まかなデザインパーツはあるので、それを組み合わせてリストを作っていくそんなイメージで実装し画面に表示していきます。

基本的には公式のチュートリアルのとおりですが、自分なりの理解を図にしながら説明したいと思います。

まずはHollow world

画面の真ん中にHollow worldを出していきます。

Hollow worldという文字に意味はありません。初めて触る言語やアプリケーション作成をする場合に良く使われる文言です。

 

デモアプリケーションの新規作成

Android Studio を起動しましょう。Start a new Flutter Project > Flutter Applicationを選択

Next を選択

次の画面ではProject Name と Project location だけ自分が選んだところに設定し、Next

デモアプリケーションがmain.dartに入った初期状態となります。

デモアプリケーションを削除してHollow Worldを表示する

まずはmain.dartを編集します。Flutterではmain.dartが入り口になっており、基本的にはこのソースを最初に通ります。

デモアプリケーションもカウントアップで面白いですが、編集には邪魔なので削除し、次のソースに変更します。

//main.dart

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('flutter'),
        ),
        body: const Center(
          child: const Text('Hello World'),
        ),
      ),
    );
  }
}

とりあえずHollow Worldの表示までできました。

ソースの中身はいろいろ書いてありますが、MaterialAppという大きな型にhomeを設定し、その中に良く見るappbarを設置

bodyにHollow worldを入れていきます。イメージ的には下のような図

このように上位のボックスから下のボックスにデザインパーツを当てはめるイメージです。

 

ランダムな単語を表示するように改変

今はHollow Worldを固定文字として表示するだけですが、これでは動きがありません。

ランダムな文字列を生成するライブラリをインポートします。

//pubspec.yaml

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  english_words: ^3.1.5 #追加

english_wordsaというライブラリです。バージョンもそのままで大丈夫です。

english_wordsaをダブルクリック後電球マークをクリックしてPub getを行います。これによってライブラリがインターネットからダウンロードされて自動的に設定されます。

Android Studio上部のPub upgradeをクリックします。pubspec.lockファイルに必要な記述が行われます。

//main.dart

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) {
    final wordPair = WordPair.random(); // 追加
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: Center(
          //child: Text('Hello World'),   // コメントアウト
          child: Text(wordPair.asPascalCase),  // 追加
        ),
      ),
    );
  }
}

このような形でランダムな文字列が表示されるようになります。Android Studio上で保存するとホットリードによって画面の文言が変更されます。

 

一覧画面(ListView)を作る

ここからゴールに向けて一覧画面作成のための作り込みをしていきます。

stateful widgetを導入する

stateful widgetとstateless widgetについては非常に奥が深く別の記事でまとめたいと思います。

今は次のような理解で先に進めます。statefulはステータスを全て管理するという意味で状態を常に保存するようなイメージ。statelessはその逆でステータスを管理しないという意味で状態を持たないようなイメージです。

動的に動かしたりする際にstatefulが必要になるという概念で大丈夫です。

それを導入したのが下記。見た目は何も変わりません。

//main.dart

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) {
    //final wordPair = WordPair.random(); // コメントアウト
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: Center(
          //child: Text('Hello World'),   // コメントアウト
          //child: Text(wordPair.asPascalCase),  // コメントアウト
          child: RandomWords(), //追加
        ),
      ),
    );
  }
}

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

class _RandomWordsState extends State<RandomWords> { //追加
  @override //追加
  Widget build(BuildContext context) { //追加
    final wordPair = WordPair.random();  //追加
    return Text(wordPair.asPascalCase); //追加
  }
}

はいこのような感じです。特に見た目に違いはありませんまた、ステートフルウィジェットを使っていますが特にステートを管理はせず単語をそのまま表示しているだけです。

List Viewを導入して無限スクロールでランダム単語を出すようにする

ここから本題のList Viewを使って一覧作っていきます。

まずは全体のソースコードを示します。

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) {
    //final wordPair = WordPair.random();
    return MaterialApp(
      title: 'Startup Name Generator',
      home: Scaffold(
        body: Center(
          //child: Text('Hello World'),
          //child: Text(wordPair.asPascalCase),
          child: RandomWords(),
        ),
      ),
    );
  }
}

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

class _RandomWordsState extends State<RandomWords> {
  final List<WordPair> _suggestions = <WordPair>[]; //追加
  final TextStyle _biggerFont = const TextStyle(fontSize: 18); //追加
  @override
  Widget build(BuildContext context) {
    //final wordPair = WordPair.random();//コメントアウト
    //return Text(wordPair.asPascalCase);//コメントアウト
    return Scaffold (
      appBar: AppBar(
        title: Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),
    );
  }

  Widget _buildSuggestions() {
    return ListView.builder(
      padding: const EdgeInsets.all(16),
        itemBuilder: (BuildContext _context, int i) {
        if (i.isOdd) {
          return Divider();
        }
        final int index = i ~/ 2;
        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      });
  }

  Widget _buildRow(WordPair pair) {
    return ListTile(
      title: Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }
}

クラスごとにどのようなことをしているのか説明します。

MyAppクラス

main()から呼ばれる最初のクラスです。

画面の大枠を決める役割でchild:RandomWords()でRandomWordsクラスを呼び出します。

 

RandomWords クラス

RandomWordsクラスではStatefulWidgetを継承します。役割としてはStateFulWidgetを機能に追加することです。

StateFulWidgetは状態を保持するために継承します。

@orverrideはStateFulWidgetのcreateState()メソッドをここで指定したロジックに置き換えるという意味です。

=> _RandomWordsState(); はreturn _RandomWordsState(); と同じです。

 

_RandomWordsState クラス

_RandomWordsStateクラスは最終的にMapAppクラスのStetelessWigetの上に被せるようにStatefulWidgetを乗せた時の画面全体を定義しているクラスです。

buildメソッドを見るとMapAppクラスのbuildメソッドとほぼ同じような記述があります。なので、その概念はMapAppの役割とほぼ同じです。

body: _buildSuggestions()でプライベートメソッドの_buildSuggestions()を呼んでいます。

_buildSuggestionsメソッドはListView.builderで一覧(ListView)を作っています。一覧全体の仕様を決めています。一覧の1つの行毎にpadding 16pxで設定しています。

itemBuilder: (BuildContext _context, int i)はListView画面を下にスクロールした時にiがインクリメントされる仕様です。また、ロジック部分なので少しピックアップして説明します。

if (i.isOdd) {
 return Divider();
}
final int index = i ~/ 2;
if (index >= _suggestions.length) {
 _suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);

i.isOddはiの値が奇数の時だけTRUEになります。if文の中のDivider()は線を引くという機能があります。

図の赤枠のところです。

i ~/ 2は除算して整数を返すという意味です。iが3なら1となります。このロジックを入れる意味は1つ上の処理で奇数となった時はreturnされるので無駄にiの値をインクリメントしたくないという意図です。index = i でも同じように動作はします。

_suggestionsには今現在画面上に出ている英単語の一覧が入っているリストです。indexは画面を下にスクロールするとインクリメントする値です。

この2つを比較してスクロールするした時、予め英単語が作られていた個数よりもさらに下にスクロールしようとした際に_suggestionsのリストにさらに英単語を追加しておくという処理です。設定上10個づつ作って追加しておくようです。

_buildRowメソッドは英単語の入っている1行の中のフォント設定やタイトルの設定を行っています。

最後に実行してみます。

ListVIewでランダムに生成される英単語を一覧にした結果が得られます。

 

Flutterで実装してみての所感

良かったこと、悪かったこと書きますが、結論だけ書くと非常に実装しやすくどんどん動くものを作っていきたいと思いました。最高です!

実装していて最初に驚いたのはホットリロード機能です。これはswiftなどのモバイル言語を開発していると差がわかるのですが、コードを改変してコンパイルしてからエミュレータに反映という手間があります。

しかし、ホットリロード機能により、保存さえすればすぐに結果がわかるので、トライアンドエラーが非常に簡単でした。

Dart言語についてはほぼjavaの感覚で使えるため元々javaを習得していたこともあり特に難はありませんでした。

悪かったところは、ListView、statefulWiget、sttatelessWigetなど定義された型を使うので、自分のやりたいことを達成するために前提として覚えないといけない型の学習が必要かなと思いました

 

-SKILL
-, ,

© 2021 Engineer Life Blog