4
2

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 1 year has passed since last update.

Flutterでwebアプリケーションにあるページングを作成してみた

Last updated at Posted at 2021-12-14

#更新履歴

2021/12/16 GitHubのリンクを追加

#背景
webアプリケーションによくあるページングをどうやって実装するのか気になったので、やってみた。
(Flutterだとスクロールさせるから、これを使う機会はないはず。。。)

#開発環境

PC:macOS Big Sur
エディター:Visual Studio Code

Dart:2.14.0
Flutter:2.5.1

※ライブラリーは無し

#内容

●仕様

  • 選択したページの前後1ページ分を表示する。
  • 先頭、末尾を選択している時は+1ページ多く表示する。
  • 先頭、末尾と選択したページの間は「・・・」を表示する。
  • 先頭、末尾以外を選択している時に矢印を表示する。

コードはStatefulで実装。

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_paging_app/paging_page.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Paging Sample App',
      home: PagingPage(),
    );
  }
}
paging_page.dart
import 'package:flutter/material.dart';

class PagingPage extends StatefulWidget {
  @override
  _PagingPageState createState() => _PagingPageState();
}

class _PagingPageState extends State<PagingPage> {
  /// state
  // 選択しているページ番号
  int _selectedIndex = 0;
  // 最大アイテム数 / 10 (1ページの表示数)
  int _maxPage = 0;
  // 全アイテム数
  int _itemCount = 0;
  // ボタンサイズ
  double _buttonSize = 26;
  // 選択の色
  Color _selectedColor = Colors.green;
  // 非選択の色
  Color _notSelectColor = Colors.amber;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Paging Sample'),
      ),
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: Container(
                child: Center(
                  child: Text('コンテンツ'),
                ),
              ),
            ),
            _pagenationBottombar(),
          ],
        ),
      ),
    );
  }

  Widget _pagenationButton(Color color, Function function, Widget widget) {
    return Container(
      height: _buttonSize,
      width: _buttonSize,
      color: color,
      margin: const EdgeInsets.all(2),
      child: InkWell(
        onTap: () {
          setState(
            () {
              function();
              // ↑何かしらのイベントを呼ぶ(APIとか)
            },
          );
        },
        child: Center(child: widget),
      ),
    );
  }

  Widget _pagenationBottombar() {
    // サンプル数
    _itemCount = 100;
    // ページの最大数
    _maxPage = ((_itemCount / 10) == 0 ? 1 : (_itemCount / 10)).ceil();

    // 現在のページから前後1ページを表示する
    // ただし、先頭、末尾の時は前後1個多く表示する
    final beforecurrentPageCount = (_selectedIndex == _maxPage - 1) ? 2 : 1;
    final startPageToEndPageCount = beforecurrentPageCount * 2 + 1;

    final startpage =
        1 < _selectedIndex ? _selectedIndex - beforecurrentPageCount : 0;
    final endpage = startpage + startPageToEndPageCount < _maxPage
        ? startpage + startPageToEndPageCount
        : _maxPage;

    List<Widget> _widget = [];

    // 先頭の矢印
    if (_selectedIndex != 0 && _maxPage > 1) {
      _widget.add(
        _pagenationButton(
          Colors.blue,
          () {
            _selectedIndex--;
          },
          Icon(Icons.arrow_back_ios_new_outlined),
        ),
      );
    }

    // 1と「・・・」
    if (_selectedIndex > 0) {
      if (startpage > 0) {
        _widget.add(
          _pagenationButton(
            _notSelectColor,
            () {
              _selectedIndex = 0;
            },
            Text(
              '1',
            ),
          ),
        );
      }
      if (_selectedIndex > 2) {
        _widget.add(
          Container(
            height: _buttonSize,
            width: _buttonSize,
            color: Colors.transparent,
            margin: const EdgeInsets.all(2),
            child: const Center(
              child: Icon(
                Icons.keyboard_control_outlined,
                color: Colors.black,
              ),
            ),
          ),
        );
      }
    }

    for (var i = startpage; i < endpage; i++) {
      _widget.add(
        _pagenationButton(
          (i == _selectedIndex) ? _selectedColor : _notSelectColor,
          () {
            _selectedIndex = i;
          },
          Text(
            (i + 1).toString(),
          ),
        ),
      );
    }

    // _maxPageと「・・・」
    if (_maxPage - 1 > _selectedIndex) {
      if (_maxPage - 3 > _selectedIndex) {
        _widget.add(
          Container(
            height: _buttonSize,
            width: _buttonSize,
            color: Colors.transparent,
            margin: const EdgeInsets.all(2),
            child: const Center(
              child: Icon(
                Icons.keyboard_control_outlined,
                color: Colors.black,
              ),
            ),
          ),
        );
      }

      if (_maxPage > endpage) {
        _widget.add(
          _pagenationButton(
            _notSelectColor,
            () {
              _selectedIndex = _maxPage - 1;
            },
            Text(
              _maxPage.toString(),
            ),
          ),
        );
      }
    }

    // 末尾の矢印
    if (_selectedIndex != _maxPage - 1 && _maxPage > 1) {
      _widget.add(
        _pagenationButton(
          Colors.blue,
          () {
            _selectedIndex++;
          },
          Icon(Icons.arrow_forward_ios_outlined),
        ),
      );
    }

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: _widget,
    );
  }
}

#結果

一応期待通りの実装にはできた。が、コードはもう少し綺麗にできる気がする。

#参考

4
2
1

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?