LoginSignup
64
46

More than 5 years have passed since last update.

Flutter の ListView / GridView で無限リストビュー

Posted at

Flutter の ListView / GridView でいわゆる「無限リストビュー」を実装する方法を紹介します。アイテムを表示しつつ、末尾に到達したら次のアイテムを読み込んでいくやつです。

ListView / GridView には itemBuilder を受け取るコンストラクタがあります。

new ListView.builder(
  itemBuilder: (BuildContext context, int index) {
    // ここで index に応じた Widget を返す
    return new Text(index.toString());
  }
),

itemBuilder を使う場合は null を返したらリストビューの末尾であることを意味します。

つまり、以下のようにすることで、無限リストビューになります。

  • 要素があるうちは Widget を返す
  • 要素がなくなったら、次のアイテムを読み込む
  • 新しくアイテムを読み込んだら、setState で build させる

よって、おおよそ以下のような感じになります。

itemBuilder: (BuildContext context, int index) {
  // List<String> items; というプロパティがあるとして
  var length = items?.length ?? 0;

  if (index == length) {
    // アイテム数を超えたので次のページを読み込む
    _load();

    // 画面にはローディング表示しておく
    return new Center(
      child: new Container(
        margin: const EdgeInsets.only(top: 8.0),
        width: 32.0,
        height: 32.0,
        child: const CircularProgressIndicator(),
      ),
    );
  } else if (index > length) {
    // ローディング表示より先は無し
    return null;
  }

  // アイテムがあるので返す
  return new Text(items[index]);
},

以下、コード全体。サンプルとして、GitHub の flutter/flutter の Issues をリスト表示しています。

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';

import 'package:http/http.dart' as http;

void main() {
  runApp(new MaterialApp(
    home: new FooPage(),
  ));
}

class FooPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new FooPageState();
  }
}

class FooPageState extends State<FooPage> {
  int page = 1;

  List<String> issues;

  bool loading = false;

  @override
  Widget build(BuildContext context) {
    var length = issues?.length ?? 0;
    return new Scaffold(
      appBar: new AppBar(title: new Text("Infinite")),
      body: new ListView.builder(
        itemBuilder: (BuildContext context, int index) {
          if (index == length) {
            // アイテム数を超えたので次のページを読み込む
            _load();

            // 画面にはローディング表示しておく
            return new Center(
              child: new Container(
                margin: const EdgeInsets.only(top: 8.0),
                width: 32.0,
                height: 32.0,
                child: const CircularProgressIndicator(),
              ),
            );
          } else if (index > length) {
            // ローディング表示より先は無し
            return null;
          }

          // アイテムがあるので返す
          var title = issues[index];
          return new Container(
            decoration: new BoxDecoration(
              border: new Border(
                bottom: new BorderSide(color: Colors.grey.shade300),
              ),
            ),
            child: new ListTile(
              key: new ValueKey<String>(title),
              title: new Text(title),
            ),
          );
        },
      ),
    );
  }

  Future<void> _load() async {
    if (loading) {
      return null;
    }
    loading = true;
    try {
      var url = "https://api.github.com/repositories/31792824/issues?page=${page}";
      var resp = await http.get(url);
      var data = json.decode(resp.body);
      setState(() {
        page += 1;
        if (data is List) {
          if (issues == null) {
            issues = <String>[];
          }
          data.forEach((dynamic elem) {
            if (elem is Map) {
              issues.add(elem['title'] as String);
            }
          });
        }
      });
    } finally {
      loading = false;
    }
  }
}

出来ました。

64
46
2

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
64
46