10
8

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.

【追記あり】BottomNavigationBarでWidgetの状態を維持する方法。(Flutter)

Last updated at Posted at 2019-11-28

はじめに

 FlutterのBottomNavigationBarはタブバーを使ってページの切り替えを支援するWidgetです。実はこのWidget、ページを切り替えるときにいちいち初期化します。スクロールしていた位置などを記憶したいときには手を加える必要があります。PageStoreやAutomaticKeepAliveClientMixinを使う方法などがあるようですが、今回はその中で最も手軽であろうIndexedStackを使って対処する方法を共有します。IndexedStackの詳細は公式ページを参考にするのがおすすめです。
https://api.flutter.dev/flutter/widgets/IndexedStack-class.html

※追記あり
コードその1でWidgetによってはエラーが生じる場合があります。
inheritFromWidgetOfExactType() or inheritFromElement() was called before initState() completed 
というような内容です。その場合はinitStateの外で_pageListにWidgetを追加する必要があります。追加するWidgetがStatelessWidgetであれば簡単に解決できます。コードその2がその方法です。

StatefulWidgetでもScoped Modelを使うとStatelessWidgetにすることができます。あとはコードその2と同じように書けば解決できます。書いたものがコードその3となります。

コードその1

 3つのタブとそれぞれのページでListViewを表示するコードです。

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

void main() {
  runApp(MaterialApp(
    title: 'Named Routes Demo',
    initialRoute: '/',
    routes: {
      '/': (context) => MainPage(),
    },
    theme: ThemeData(
        primarySwatch: Colors.deepOrange,
        accentColor: Colors.deepOrangeAccent
    ),
  ));
}

class MainPage extends StatefulWidget{
  @override
  State<StatefulWidget> createState() => MainState();
}
class MainState extends State<MainPage>{

  int _selectedIndex=0;
  List<Widget> _pageList=[];
  
  @override
  void initState(){
    super.initState();
    _pageList.add(
      ListView.builder(
        itemBuilder: (context,index){
          return Text('aaa   $index');
        },
      )
    );
    _pageList.add(
      ListView.builder(
        itemBuilder: (context,index){
          return Text('bb   $index');
        },
      ),
    );
    _pageList.add(
      ListView.builder(
        itemBuilder: (context,index){
          return Text('c   $index');
        },
      )
    );
  }

  @override
  Widget build(context){
    return Scaffold(
      body:IndexedStack(
        index: _selectedIndex,
        children:_pageList,
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
              title: Text('A'),
              icon: Icon(Icons.home)
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            title: Text('B'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            title: Text('C'),
          ),
        ],
        selectedItemColor: Colors.amber[300],
        currentIndex: _selectedIndex,
        onTap: (int index){
          setState(() {
            _selectedIndex=index;
          });
        },
      ),
    );
  }
}

コードその2

initStateでWidgetの追加をしないようにしたものです。StatelessWidgetしか追加できない難点があります。

main.dart
void main() {
  runApp(MaterialApp(
    title: 'Named Routes Demo',
    initialRoute: '/',
    routes: {
      '/': (context) => MainPage(),
    },
    theme: ThemeData(
        primarySwatch: Colors.deepOrange,
        accentColor: Colors.deepOrangeAccent
    ),
  ));
}

class MainPage extends StatefulWidget{
  @override
  State<StatefulWidget> createState() => MainState();
}
class MainState extends State<MainPage>{

  int _selectedIndex=0;
  List<Widget> _pageList=[PageA(),PageB(),PageC()];

  @override
  Widget build(context){
    return Scaffold(
      body:IndexedStack(
        index: _selectedIndex,
        children:_pageList,
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
              title: Text('A'),
              icon: Icon(Icons.home)
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            title: Text('B'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            title: Text('C'),
          ),
        ],
        selectedItemColor: Colors.amber[300],
        currentIndex: _selectedIndex,
        onTap: (int index){
          setState(() {
            _selectedIndex=index;
          });
        },
      ),
    );
  }
}

class PageA extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('A   $index');
      },
    );
  }
}

class PageB extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('B   $index');
      },
    );
  }
}

class PageC extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('C   $index');
      },
    );
  }
}

コードその3

コードその2をScoped ModelにしてStatefulWidgetにも対応できるようにしたものです。この例では恩恵が分かりにくいかもしれません。StatefulWidgetも追加するときに効果を発揮してくれるはずです。個人的には3つの中で1番おすすめです。

main.dart
class MainPage extends StatelessWidget{

  final List<Widget> _pageList=[PageA(),PageB(),PageC()];

  @override
  Widget build(BuildContext context) {
    return ScopedModelDescendant<IndexModel>(
      builder: (context,child,model){
        return Scaffold(
          body:IndexedStack(
            index: model.selectedIndex,
            children: _pageList,
          ),
          bottomNavigationBar: BottomNavigationBar(
            items: const <BottomNavigationBarItem>[
              BottomNavigationBarItem(
                  title: Text('A'),
                  icon: Icon(Icons.home)
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.business),
                title: Text('B'),
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.school),
                title: Text('C'),
              ),
            ],
            selectedItemColor: Colors.amber[300],
            currentIndex: model.selectedIndex,
            onTap: (int index){
              model.selectedIndex=index;
            },
          ),
        );
      }
    );
  }
}

class PageA extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('A   $index');
      },
    );
  }
}

class PageB extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('B   $index');
      },
    );
  }
}

class PageC extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('C   $index');
      },
    );
  }
}

class IndexModel extends Model {
  int _selectedIndex = 0;
  get selectedIndex=>_selectedIndex;
  set selectedIndex(value){
    _selectedIndex=value;
    notifyListeners();
  }
}

実行結果

ezgif-2-bc58f89ff1cd.gif

10
8
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
10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?