![](/img/trans.png)
[英]How to swipe from last to first tab in TabBarView in Flutter
[英]Problem when move from first to last tab on TabBarView Flutter
當我需要從第一個選項卡跳轉到第三個選項卡時,我的 flutter 應用程序遇到問題。 我將在下面更好地解釋:
我正在使用 flutter 構建一個屏幕,當我有一個帶有 3 個選項卡的 TabBar 並在 TabBarView 下方填充此選項卡時。
類似的東西:
Widget _buildReportAppbar() {
return AppBar(
backgroundColor: AppColors.colorPrimary,
elevation: 5,
leading: ...
title: ...
actions: ...
bottom: PreferredSize(
preferredSize: Size.fromHeight(58.0),
child: Padding(
padding: EdgeInsets.only(bottom: 10),
child: TabBar(
labelColor: AppColors.colorPrimary,
labelPadding: EdgeInsets.only(left: 8, right: 8),
labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
unselectedLabelColor: Colors.white,
unselectedLabelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.normal),
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
indicatorWeight: 0,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
controller: _tabController,
tabs: _tabs,
),
),
),
);
}
Widget _buildReportBody() {
return TabBarView(
controller: _tabController,
children: _provideTabScreenList(),
);
}
我的標簽 controller 就像一個魅力,在每個 TabBarView 里面我都有一個 statefulWidget 來構建我的報告屏幕。 每個 StatefulWidget 都有一個 api 調用,它為我帶來報告信息,並使用 SetState(){} 方法將信息放在屏幕上。
List<Widget> _provideTabScreenList() {
_tabScreenList.clear();
_tabScreenList.add(PeriodResumeReport(filterData: currentFilter));
_tabScreenList.add(SaleStatisticReport(filterData: currentFilter));
_tabScreenList.add(SalePerDayReport(filterData: currentFilter));
return _tabScreenList;
}
class PeriodResumeReport extends StatefulWidget {
final _periodResumeReportState = _PeriodResumeReportState();
final SelectedFilter filterData;
PeriodResumeReport({Key key, @required this.filterData}) : super(key: key);
@override
_PeriodResumeReportState createState() => _periodResumeReportState;
//My tabController has a listener that detect the position change and notify the child.
void isVisible() {
_periodResumeReportState.isVisible();
}
}
//I'm using the AutomaticKeepAliveClientMixin to keep the state when i move between the
childs of the TabBarView
class _PeriodResumeReportState extends State<PeriodResumeReport>
with AutomaticKeepAliveClientMixin<PeriodResumeReport> {
var _loadingData = false;
var _apiErrorMessage = "";
var _hasResponseFromApi = false;
var _response = ...api response;
@override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
//Load first data when the screen visible for the first time.
@override
void initState() {
super.initState();
_reloadData();
}
@override
Widget build(BuildContext context) {
super.build(context);
return ListView(
children: <Widget>[
_loadingData ? LinearProgressIndicator() : Container(),
_apiErrorMessage.isNotEmpty
? Padding(...)
: Container(),
_hasResponseFromApi ? _buildTotalOfSalesContainer() : Container(),
_hasResponseFromApi ? _buildComparativeChart() : Container(),
],
);
}
Widget _buildTotalOfSalesContainer() {
return ...;
}
Widget _buildComparativeChart() {
return ...;
}
//reload data if the user return to the screen
void isVisible() {
_reloadData();
}
Future<void> _reloadData() async {
//show ProgressBar and clear apiError
setState(() {
_loadingData = true;
_apiErrorMessage = "";
});
try {
var response = .... api call ....
....
.... handle api response
....
setState(() {
_response = response;
_loadingData = false;
_hasResponseFromApi = true;
});
}
@override
bool get wantKeepAlive => true;
}
使用此代碼,一切正常。 但是有一個問題,如果我將第一個標簽滑動到第二個標簽,然后再滑動到第三個標簽,一切都可以。 如果我在第一個選項卡中並單擊以移動到第三個選項卡而不是傳遞到之前的第二個選項卡,則會出現問題。 這樣做,第二個選項卡會因以下錯誤而崩潰:
Exception has occurred.
FlutterError (setState() called after dispose(): _SaleStatisticReportState#8c846(lifecycle
state: defunct)
This error happens if you call setState() on a State object for a widget that no longer
appears in the widget tree (e.g., whose parent widget no longer includes the widget in its.
build). This error can occur when code calls setState() from a timer or an animation
callback.
The preferred solution is to cancel the timer or stop listening to the animation in the
dispose() callback. Another solution is to check the "mounted" property of this object
before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object
is retaining a reference to this State object after it has been removed from the tree. To
avoid memory leaks, consider breaking the reference to this object during dispose().)
在這一行:
@override
void setState(fn) {
if(mounted){
super.setState(fn);
}
}
我不知道有什么問題,也許從第一個選項卡直接滑動到第三個選項卡的動作運行 isVisible 方法並在第二個選項卡上啟動 api 調用但是,因為我在第三個選項卡上,setState(){}在第二次崩潰。 我如何解決這個問題?
問題出在 _reloadData 方法中,因為您在 initstate 中調用此方法,因此此方法調用 setState,即使屏幕未掛載。 要解決此問題,請嘗試以下解決方法。
在以下解決方案中,我確保已安裝屏幕,如果已安裝,則僅調用 setState。
Future<void> _reloadData() async {
//show ProgressBar and clear apiError
if(mounted){
setState(() {
_loadingData = true;
_apiErrorMessage = "";
});
}
try {
var response = .... api call ....
....
.... handle api response
....
if(mounted)
setState(() {
_response = response;
_loadingData = false;
_hasResponseFromApi = true;
});
}
}
更新:
class DeleteWidget extends StatefulWidget {
@override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
int clockHours = 10;
int clockMinutes = 10;
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
backgroundColor: Colors.teal,
appBar: AppBar(
title: const Text('Tabbed AppBar'),
bottom: TabBar(
isScrollable: true,
tabs: [
Tab(text: "1"),
Tab(text: "2"),
Tab(text: "3"),
],
),
),
body: TabBarView(
children: [
Home1(),
Home2(),
Home3(),
],
),
),
);
}
}
class Home1 extends StatefulWidget {
@override
_Home1State createState() => _Home1State();
}
class _Home1State extends State<Home1> {
@override
void initState() {
super.initState();
callme();
}
@override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
callme() async {
await Future.delayed(Duration(seconds: 1));
setState(() {});
}
@override
Widget build(BuildContext context) {
return Container();
}
}
class Home2 extends StatefulWidget {
@override
_Home2State createState() => _Home2State();
}
class _Home2State extends State<Home2>
with AutomaticKeepAliveClientMixin<Home2> {
@override
void initState() {
super.initState();
callme();
}
@override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
callme() async {
await Future.delayed(Duration(seconds: 1));
// if (mounted) {
setState(() {});
//}
}
@override
Widget build(BuildContext context) {
super.build(context);
return Container();
}
@override
bool get wantKeepAlive => true;
}
class Home3 extends StatefulWidget {
@override
_Home3State createState() => _Home3State();
}
class _Home3State extends State<Home3>
with AutomaticKeepAliveClientMixin<Home3> {
@override
void initState() {
super.initState();
callme();
}
callme() async {
await Future.delayed(Duration(seconds: 1));
setState(() {});
}
@override
Widget build(BuildContext context) {
super.build(context);
return Container();
}
@override
bool get wantKeepAlive => true;
}
更新:
如何從父小部件調用子方法。
class DeleteWidget extends StatefulWidget {
@override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
GlobalKey<Home1State> _keyChild1;
@override
void initState() {
super.initState();
_keyChild1 = GlobalKey();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
child: Text("press"),
onPressed: () {
_keyChild1.currentState.callme();
},
),
Home1(
key: _keyChild1,
)
],
),
);
}
}
class Home1 extends StatefulWidget {
Home1({Key key}) : super(key: key);
@override
Home1State createState() => Home1State();
}
class Home1State extends State<Home1> {
callme() {
print("method call from parent");
setState(() {});
}
@override
Widget build(BuildContext context) {
return Container(
child: Text("Home1"),
);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.