フラッタ底部ナビゲーションバーのための状態持続性技術
概要
Setup
1. Stack and OffStage
2. AutomaticKeepAliveClientMixin
それから、我々は彼らを解決するために2、3のアプローチをためします.我々は結果を比較し、我々はどちらを進めるために決めることができます.
Here's the GitHub repo with all the code.
セットアップ
We're going to start with the basic app containing bottom navigation with two tabs.
- Tab 1: Scrollable list of items.
- Tab 2: Displaying the escaped seconds of a Timer.
何を達成したいですか?
- Create the navigation bar page only when we open the page.
- Preserve scroll position of navigation bar page in Tab 1.
- Preserve the escaped time of Timer in Tab 2.
実装
Let's start with a new Flutter project.
親ウィジェット:下部ナビゲーションバー
我々は単純な
Scaffold
with BottomNavigationBar
2を含むTabs
.class BasicBottomNavigation extends StatefulWidget {
const BasicBottomNavigation({Key? key}) : super(key: key);
@override
State<BasicBottomNavigation> createState() => _BasicBottomNavigationState();
}
class _BasicBottomNavigationState extends State<BasicBottomNavigation> {
int currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: [ /// List of tab page widgets
const _Tabbar1(),
const _Tabbar2(),
][currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index; /// Switching tabs
});
},
items: const [
BottomNavigationBarItem(icon: Text("1"), label: "Tab"),
BottomNavigationBarItem(icon: Text("2"), label: "Tab"),
],
),
);
}
}
タブ1:アイテムのスクロール可能なリスト
私たちは
ListView
インデックスを表示するListTile
.class _Tabbar1 extends StatelessWidget {
const _Tabbar1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
print("Tabbar 1 build");
return Scaffold(
appBar: AppBar(title: const Text("Tab bar 1")),
body: ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text("${index + 1}"),
);
},
itemCount: 50,
),
);
}
}
タブ2 :タイマーのエスケープ秒を表示する
私たちはTicker タイマーを実行し、我々のエスケープ時間ごとに2秒を更新します.
Fun Fact: Ticker is used in Flutter for callbacks during Animation frames.
class _Tabbar2 extends StatefulWidget {
const _Tabbar2({Key? key}) : super(key: key);
@override
State<_Tabbar2> createState() => _Tabbar2State();
}
class _Tabbar2State extends State<_Tabbar2>
with SingleTickerProviderStateMixin {
late final Ticker _ticker;
Duration _escapedDuration = Duration.zero;
get escapedSeconds => _escapedDuration.inSeconds.toString();
@override
void initState() {
super.initState();
print("Tabbar 2 initState");
_ticker = createTicker((elapsed) {
if (elapsed.inSeconds - _escapedDuration.inSeconds == 1) {
setState(() {
_escapedDuration = elapsed;
});
}
});
_ticker.start();
}
@override
void dispose() {
_ticker.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Tab bar 2")),
body: Center(
child: Text(escapedSeconds),
),
);
}
}
デモ
結果
- Tabs are initialized only when we click on them.
- The scroll position is not preserved.
- Escaped time of the Timer is not preserved.
Nothing was preserved. We create new tab pages every time we click on them. The scroll position is lost we switch back to Tab 1
. The Timer starts from 0 whenever we open Tab 2
.
There is no problem with this approach as long as we don't need to preserve any state.
But since we do, let's look at how we can achieve it.
スタックとオフステージ
One way to persist the bottom navigation bar page is to use Stack ウィジェット.我々は、すべてのページを子供たちとして加えます
Stack
一番下のタブの順序で、現在選択されている一番下のタブについて一度に1つの子を表示します.実装
We'll wrap theTabbar
widgets with OffStage そして、Stack
.offstageパラメータはブール値をとります.それが本当ならば、子供はそうです
hidden
or offstage
, それ以外の場合、子が表示されます.tabbarクラスに変更はありません.
親ウィジェット:下部ナビゲーションバー
return Scaffold(
body: Stack( /// Added Stack Widget
children: [
Offstage( /// Wrap Tab with OffStage
offstage: currentIndex != 0,
child: const _Tabbar1(),
),
Offstage(
offstage: currentIndex != 1,
child: const _Tabbar2(),
),
],
),
デモ
結果
- Tabs are not initialized only when we click on them.
- The scroll position is preserved.
- Escaped time of the Timer is preserved.
All the tabs are initialized with the parent Widget. Hence the timer in Tabbar 2
started before we even opened that Tab. The good thing is that it preserves the scroll position and escaped time.
If creating all the tabs at once does not affect the performance and is what we want, then we use this technique.
1 .代替インデックス付きスタック
Turns out there's aWidget
(as always with Flutter 😇) called IndexedStack 我々が使うことができるそれ.それは同じ結果を持つコードが少ないです.親ウィジェット:下部ナビゲーションバー
return Scaffold(
body: IndexedStack( /// Replaced with IndexedStack
index: currentIndex,
children: const [
_Tabbar1(),
_Tabbar2(),
],
),
2 .自動化
As the name suggests, this mixin makes the client (Tabbar child widgets) keep themselves alive (not disposed of) after we switch the tabs. It also creates the Tab only when it is first clicked and not with the Parent Widget like the above methods.
実装
AutomaticKeepAliveClientMixin needs a PageView 親ウィジェットで.ですから、PageViewで本体をラップし、タブのリストを子として渡します.Further Reading: Other than PageView, there's a TabBarView (for top app bar tabs), which also makes AutomaticKeepAliveClientMixin work for tabs (child widgets) because it uses PageView internally.
親ウィジェット:下部ナビゲーションバー
class AliveMixinDemo extends StatefulWidget {
const AliveMixinDemo({Key? key}) : super(key: key);
@override
State<AliveMixinDemo> createState() => _AliveMixinDemoState();
}
class _AliveMixinDemoState extends State<AliveMixinDemo> {
final PageController controller = PageController(); /// initializing controller for PageView
int currentIndex = 0;
final tabPages = [
const _Tabbar1(),
const _Tabbar2(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: PageView( /// Wrapping the tabs with PageView
controller: controller,
children: tabPages,
onPageChanged: (index) {
setState(() {
currentIndex = index; /// Switching bottom tabs
});
},
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (index) {
controller.jumpToPage(index); /// Switching the PageView tabs
setState(() {
currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(icon: Text("1"), label: "Tab"),
BottomNavigationBarItem(icon: Text("2"), label: "Tab"),
],
),
);
}
}
タブ1:アイテムのスクロール可能なリスト
私たちは
StatefulWidget
こちらからAutomaticKeepAliveClientMixin
だけで動作するState
クラスで定義されるクラスimplementation ."A mixin with convenience methods for clients of AutomaticKeepAlive. Used with State subclasses."
この後2つだけ追加する必要があります.
まず、呼び出し
super.build()
ビルドメソッドの内部.第二に、wantKeepAlive
返り値true
.class _Tabbar1 extends StatefulWidget {
const _Tabbar1({Key? key}) : super(key: key);
@override
State<_Tabbar1> createState() => _Tabbar1State();
}
class _Tabbar1State extends State<_Tabbar1>
with AutomaticKeepAliveClientMixin { /// Using the mixin
@override
Widget build(BuildContext context) {
super.build(context); /// Calling build method of mixin
print("Tabbar 1 build");
return Scaffold(
appBar: AppBar(title: const Text("Tab bar 1")),
body: ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text("${index + 1}"),
);
},
itemCount: 50,
),
);
}
@override
bool get wantKeepAlive => true; /// Overriding the value to preserve the state
}
タブ2 :タイマーのエスケープ秒を表示する
変更は
Tabbar 1
上記クラス.class _Tabbar2State extends State<_Tabbar2>
with SingleTickerProviderStateMixin,
AutomaticKeepAliveClientMixin { /// Using the mixin
late final Ticker _ticker;
@override
Widget build(BuildContext context) {
super.build(context); /// Calling build method of mixin
return Scaffold(
appBar: AppBar(title: const Text("Tab bar 2")),
body: Center(
child: Text(escapedSeconds),
),
);
}
@override
bool get wantKeepAlive => true; /// Overriding the value to preserve the state
}
デモ
結果
- Tabs are initialized only when we click on them.
- The scroll position is preserved.
- Escaped time of the Timer is preserved.
The Tabbar 2
is initialized only the first time when we click on it. The Timer preserves its state and so does the scrolling position in Tabbar 1
.
If we want to programmatically change the
keepAlive
condition, then we can use theupdateKeepAlive()
method ofAutomaticKeepAliveClientMixin
. For further reading, refer to this StackOverflow answer.
結論
We can choose any one approach from the above options according to our requirements.
- Don't want to preserve any state -> standard
BottomBarNavigation
. - Want to preserve state but fine with creating all the tabs at once ->
IndexedStack
orStack and OffStage
. - Want to preserve state and build tabs only once when clicked on them ->
AutomaticKeepAliveClientMixin
.
IndexedStack
is the simplest approach whileAutomaticKeepAliveClientMixin
covers our need. Since we usually have API calls in tabs and don't want to call them every time we switch to that tab.
ファイナルノート
Thank you for reading this article. If you enjoyed it, consider sharing it with other people.
If you find any mistakes, please let me know.
Feel free to share your opinions below.
Reference
この問題について(フラッタ底部ナビゲーションバーのための状態持続性技術), 我々は、より多くの情報をここで見つけました https://dev.to/nicks101/state-persistence-techniques-for-the-flutter-bottom-navigation-bar-3ikcテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol