从一个选项卡滑动到另一个选项卡时,TabView 不保留状态

Posted

技术标签:

【中文标题】从一个选项卡滑动到另一个选项卡时,TabView 不保留状态【英文标题】:TabView does not preserve state when swiping from one tab to another 【发布时间】:2020-11-22 21:06:11 【问题描述】:

上下文:这里的页面有一个TabView 在标签之间导航所有这些标签都在使用flutter_bloc(版本 6.0.1)。

问题:当滑动到任何选项卡时,状态不会被保留,整个小部件树正在重建,如下面的 gif 所示

这里是build() 方法:

 @override
  Widget build(BuildContext context) 
    super.build(context);
    return DefaultTabController(
      initialIndex: 0,
      length: 3,
      child: Scaffold(
        backgroundColor: Colors.white,
        appBar: _buildAppBarWithTabs(),
        body: TabBarView(
          children: <Widget>[
            defaultViewforCategory(1), //Women
            defaultViewforCategory(3), //Men
            defaultViewforCategory(2), //Kids
          ],
        ),
      ),
    );
  

这里是函数defaultViewforCategory()的实现

Widget defaultViewforCategory(int mainCategoryId) 
    return PageStorage(
      bucket: bucket,
      key: PageStorageKey(mainCategoryId),
      child: ConstrainedBox(
        constraints: BoxConstraints(maxWidth: 1200),
        child: ListView(
          scrollDirection: Axis.vertical,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(bottom: 150),
              child: Container(
                height: 800,
                child: RefreshIndicator(
                  onRefresh: () => refreshTimeline(),
                  child: CustomScrollView(
                    scrollDirection: Axis.vertical,
                    slivers: <Widget>[
                      SliverToBoxAdapter(
                        child: MasonryGrid(
                          column: getResponsiveColumnNumber(context, 1, 2, 6),
                          children: <Widget>[
                            // First Bloc
                            BlocProvider(
                              create: (context) 
                                BrandBloc(repository: _brandRepository);
                              ,
                              child: Container(
                                width: 200,
                                alignment: Alignment.center,
                                height: 90,
                                child: BrandScreen(
                                  brandBloc: context.bloc(),
                                ),
                              ),
                            ),
                            CategoryScreen(
                              // Second Bloc
                              categoryBloc: CategoryBloc(
                                  mainCategoryId: mainCategoryId,
                                  repository: _categoryRepository),
                            ),

                            // -------------- Featured Items--------------------------
                            Container(
                              width: 200,
                              alignment: Alignment.center,
                              height: 350,
                              child: _buildFeaturedItemsList(mainCategoryId),
                            ),
                            Placeholder(strokeWidth: 0, color: Colors.white)
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  

尝试过的解决方案: 1 - 我尝试了AutomaticKeepAliveClientMixin,但结果证明mixin 在使用BottomNavigationBar 切换到另一个页面时保留页面的状态。

2 - PageStorage 没有解决问题。

问题:如何阻止TabView在用户每次滑动到另一个标签时重新构建?

【问题讨论】:

也许尝试将_buildAppBarWithTabs()defaultViewforCategory 更改为小部件而不是函数?我在某处读到它在这样做时以不同的方式构建小部件树.. 【参考方案1】:

正如您所说,问题之一是每次显示选项卡时都会重建TabBarView。该问题是一个开放主题here。因此,每次屏幕更改时都会创建一个新的 Bloc 实例。

注意因为CategoryBloc 不是使用BlocProvider 传递的,所以您应该手动处理该集团。

这里的一个简单解决方案是将BlocProviderTabBarView 之外的层次结构中向上移动 - 例如,构建方法中的第一个组件。

注意从性能角度来看,这是可以的,因为当请求一个 bloc 时,BLOC 会延迟初始化。

现在更微妙的问题是CategoryBloc 的创建方式(因为有一个动态构造函数)。在这里你可以有两个解决方案:

    您要么修改CategoryBloc 以供所有类别屏幕共享 - 在这里我无法为您提供太多帮助,因为我没有它的代码。这个想法是通过eventsemits 发送mainCategoryId 一个带有结果的新状态。在这种情况下,您应该将mainCategoryId 转发到state,并且在BlocBuilder 上,仅当mainCategoryId 与CategoryScreen id 匹配时才使用buildWhen 参数构建(可以在创建屏幕时传递) .并且不要忘记在 TabBarView 孩子之外使用 BlocProvider 提供 CategoryBloc。

    将 CategoryBloc 创建移到 TabBarView 之外并缓存它们以供进一步访问。我在下面创建了一个示例来强调这一点。

    // ...
    
    ///
    /// Categories blocs cache.
    ///
    Map<int, CategoryBloc> _categoriesBlocs;
    
    ///
    /// Creates UNIQUE instances of CategoryBloc by id.
    ///
    CategoryBloc getCategoryBlocById(int id) 
        // If you don't already have a bloc for that particular id, create a new one
        // and cache it (by saving it in the Map)
        this._categoriesBlocs.putIfAbsent(
            id,
            () => CategoryBloc(
            mainCategoryId: id,
                      repository: _categoryRepository,
            ));
    
        // Return the cached category bloc
        return this._categoriesBlocs[id];
      
    
    ///
    /// This is very important. Because we manually create the BLOCs we have 
    /// to manually dispose them 
    ///
    @override
    void dispose() 
        for (var bloc in this._categoriesBlocs) 
         bloc.displose();
        
        super.dispose();
    
    
    @override
    Widget build(BuildContext context) 
        super.build(context);
    
        return MultiBlocProvider(
            providers: [
                BlocProvider(
                    create: (context) => BrandBloc(repository: _brandRepository),
                )
            ],
            child: DefaultTabController(
                initialIndex: 0,
                length: 3,
                child: Scaffold(
                    backgroundColor: Colors.white,
                    appBar: _buildAppBarWithTabs(),
                    body: TabBarView(
                        children: <Widget>[
                            defaultViewforCategory(1), //Women
                            defaultViewforCategory(3), //Men
                            defaultViewforCategory(2), //Kids
                        ],
                    ),
                ),
            ),
        );
    
    
      // ...
    
      CategoryScreen(
      // Second Bloc
      // Now, here you will get the same BLOC instance every time
          categoryBloc: getCategoryBlocById(mainCategoryId),
      ),
    
      // ...
    

【讨论】:

以上是关于从一个选项卡滑动到另一个选项卡时,TabView 不保留状态的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI:Tabview,更改选项卡时保持状态

使用tabview更改选项卡时如何使SwiftUI中的计时器保持触发

用户切换浏览器选项卡时如何从 JApplet 中隐藏 JDialog?

当通过手势识别器滑动切换到仅点击选项卡时,在 tabbarcontroller 中切换选项卡会给出不同的结果

如何在 p:tabView 组件中动态添加和删除选项卡

使用 SwiftUI 的 TabbedView 切换到其他选项卡时查看内容不显示