Flutter Bottom 내비게이션 바의 상태 지속성 기법
개요
Setup
1. Stack and OffStage
2. AutomaticKeepAliveClientMixin
하단 탐색 기능이 있는 기본 Flutter 앱을 설정하고 직면한 문제를 살펴보겠습니다. 탭을 전환할 때마다 하단 탐색 모음 페이지를 초기화하지 않거나 하단 탭 페이지의 상태를 유지하는 것과 같습니다.
그런 다음 이를 해결하기 위해 몇 가지 접근 방식을 시도합니다. 우리는 결과를 비교하고 앞으로 진행할 것을 결정할 수 있습니다.
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
를 포함하는 BottomNavigationBar
가 있는 간단한 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을 사용하여 타이머를 실행하고 이스케이프된 기간을 매초 업데이트합니다.
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.
1. 스택과 오프스테이지
One way to persist the bottom navigation bar page is to use Stack 위젯.모든 페이지를 아래쪽 탭의 순서로
Stack
의 자식으로 추가하고 현재 선택한 아래쪽 탭과 관련하여 한 번에 하나의 자식을 표시합니다.구현
We'll wrap theTabbar
widgets with OffStage 및 Stack
가 포함된 하위 목록입니다.오프스테이지 매개변수는 부울 값을 사용합니다. 참이면 자식은
hidden
또는 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. AutomaticKeepAliveClientMixin
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
는 implementation에 정의된 AutomaticKeepAliveClientMixin
클래스에서만 작동하기 때문에 여기서는 State
로 대체합니다."A mixin with convenience methods for clients of AutomaticKeepAlive. Used with State subclasses."
이 후에 두 가지만 추가하면 됩니다.
먼저 빌드 메서드 내에서
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
이 문제에 관하여(Flutter Bottom 내비게이션 바의 상태 지속성 기법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/nicks101/state-persistence-techniques-for-the-flutter-bottom-navigation-bar-3ikc텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)