颤动中 TabView 的动态子项

Posted

技术标签:

【中文标题】颤动中 TabView 的动态子项【英文标题】:Dynamic children for TabView in flutter 【发布时间】:2018-12-09 14:27:47 【问题描述】:

我正在尝试构建一个包含子列表的选项卡式视图。

类别标签和列表内容都将从数据库中获取。

我正在从调用者页面传递标签,并成功地将它们作为列表传递。 现在我正在尝试加载我的列表,并且我已经构建了一个成功返回 Future ListView 的小部件 (myList)。

问题有两个:

    每次我向左或向右滑动时,列表都会自行重建,而我希望它只构建一次 如何使用我编写的代码让标签的子项实际反映标签并根据我拥有的类别数量动态加载?

现在我的代码是这样的:

import 'package:flutter/material.dart';
import 'package:flutter_app/ui/menu_category_list.dart';

// Each TabBarView contains a _Page and for each _Page there is a list
// of _CardData objects. Each _CardData object is displayed by a _CardItem.

List<Tab> Tabs(List<String> l)
  List<Tab> list;
  for (String c in l) 
    list.add(new Tab(text: c));
  
  return list;




class TabsDemo extends StatelessWidget 

  const TabsDemo( Key key , this.categorie) : super(key: key);

  final List<Tab> categorie;

  @override
  Widget build(BuildContext ctxt) 
    return new MaterialApp(
      title: "Nice app",
      home: new DefaultTabController(
      length: 5,
      child: new Scaffold(
        appBar: new AppBar(
          title: new Text("Title"),
          bottom: new TabBar(
            tabs:
              categories,
              //new Tab(text: "First Tab"),
              //new Tab(text: "Second Tab"),

          ),

        ),
        body: new TabBarView(
            children: [
              new MenuCategoryList(),
              new MenuCategoryList(),
              new MenuCategoryList(),
              new MenuCategoryList(),
              new MenuCategoryList()
            ]
        )
      ),
    )
    );
  

currently result

提前非常感谢

【问题讨论】:

【参考方案1】:

您可以使用List&lt;E&gt;.generate 来实现此目的。

import 'package:flutter/material.dart';

假设您有一组从您的呼叫者页面传递的类别。假设这是您的类别列表。

List<String> categories = ["a", "b", "c", "d", "e", "f", "g", "h"];

然后你可以做这样的事情来实现你想要的。

class TabsDemo extends StatefulWidget 
  @override
  _TabsDemoState createState() => _TabsDemoState();


class _TabsDemoState extends State<TabsDemo> 
  TabController _controller;

  @override
  void initState() 
    super.initState();
  

    @override
    Widget build(BuildContext ctxt) 
      return new MaterialApp(
        home: DefaultTabController(
            length: categories.length,
            child: new Scaffold(
              appBar: new AppBar(
                title: new Text("Title"),
                bottom: new TabBar(
                  isScrollable: true,
                    tabs: List<Widget>.generate(categories.length, (int index)
                  print(categories[0]);
                  return new Tab(icon: Icon(Icons.directions_car), text: "some random text");

                ),

              ),
            ),

        body: new TabBarView(
             children: List<Widget>.generate(categories.length, (int index)
                print(categories[0]);
                return new Text("again some random text");

             ),
          )
       ))
      );
  

您还可以将不同的小部件设置为选项卡的视图。您可以创建一个页面列表并遵循相同的方法。

【讨论】:

工作就像一个魅力。非常感谢! 谢谢,如何根据类型设置动态图标?【参考方案2】:

绝对正确List&lt;E&gt;.generate 最佳解决方案。

如果您需要修改数组,就会出现问题。它们在于这样一个事实,即在修改数组时,您没有机会使用相同的控制器。

对于这种情况,您可以使用下一个自定义小部件:

import 'package:flutter/material.dart';

  void main() => runApp(MyApp());

  class MyApp extends StatelessWidget 
    @override
    Widget build(BuildContext context) 
      return MaterialApp(
        title: 'Flutter Demo',
        home: MyHomePage(),
      );
    
  

  class MyHomePage extends StatefulWidget 
    @override
    _MyHomePageState createState() => _MyHomePageState();
  

  class _MyHomePageState extends State<MyHomePage> 
    List<String> data = ['Page 0', 'Page 1', 'Page 2'];
    int initPosition = 1;

    @override
    Widget build(BuildContext context) 
      return Scaffold(
        body: SafeArea(
          child: CustomTabView(
            initPosition: initPosition,
            itemCount: data.length,
            tabBuilder: (context, index) => Tab(text: data[index]),
            pageBuilder: (context, index) => Center(child: Text(data[index])),
            onPositionChange: (index)
              print('current position: $index');
              initPosition = index;
            ,
            onScroll: (position) => print('$position'),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () 
            setState(() 
              data.add('Page $data.length');
            );
          ,
          child: Icon(Icons.add),
        ),
      );
    
  

  /// Implementation

  class CustomTabView extends StatefulWidget 
    final int itemCount;
    final IndexedWidgetBuilder tabBuilder;
    final IndexedWidgetBuilder pageBuilder;
    final Widget stub;
    final ValueChanged<int> onPositionChange;
    final ValueChanged<double> onScroll;
    final int initPosition;

    CustomTabView(
      @required this.itemCount,
      @required this.tabBuilder,
      @required this.pageBuilder,
      this.stub,
      this.onPositionChange,
      this.onScroll,
      this.initPosition,
    );

    @override
    _CustomTabsState createState() => _CustomTabsState();
  

  class _CustomTabsState extends State<CustomTabView> with TickerProviderStateMixin 
    TabController controller;
    int _currentCount;
    int _currentPosition;

    @override
    void initState() 
      _currentPosition = widget.initPosition ?? 0;
      controller = TabController(
        length: widget.itemCount,
        vsync: this,
        initialIndex: _currentPosition,
      );
      controller.addListener(onPositionChange);
      controller.animation.addListener(onScroll);
      _currentCount = widget.itemCount;
      super.initState();
    

    @override
    void didUpdateWidget(CustomTabView oldWidget) 
      if (_currentCount != widget.itemCount) 
        controller.animation.removeListener(onScroll);
        controller.removeListener(onPositionChange);
        controller.dispose();

        if (widget.initPosition != null) 
          _currentPosition = widget.initPosition;
        

        if (_currentPosition > widget.itemCount - 1) 
            _currentPosition = widget.itemCount - 1;
            _currentPosition = _currentPosition < 0 ? 0 : 
            _currentPosition;
            if (widget.onPositionChange is ValueChanged<int>) 
               WidgetsBinding.instance.addPostFrameCallback((_)
                if(mounted) 
                  widget.onPositionChange(_currentPosition);
                
               );
            
         

        _currentCount = widget.itemCount;
        setState(() 
          controller = TabController(
            length: widget.itemCount,
            vsync: this,
            initialIndex: _currentPosition,
          );
          controller.addListener(onPositionChange);
          controller.animation.addListener(onScroll);
        );
       else if (widget.initPosition != null) 
        controller.animateTo(widget.initPosition);
      

      super.didUpdateWidget(oldWidget);
    

    @override
    void dispose() 
      controller.animation.removeListener(onScroll);
      controller.removeListener(onPositionChange);
      controller.dispose();
      super.dispose();
    

    @override
    Widget build(BuildContext context) 
      if (widget.itemCount < 1) return widget.stub ?? Container();

      return Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Container(
            alignment: Alignment.center,
            child: TabBar(
              isScrollable: true,
              controller: controller,
              labelColor: Theme.of(context).primaryColor,
              unselectedLabelColor: Theme.of(context).hintColor,
              indicator: BoxDecoration(
                border: Border(
                  bottom: BorderSide(
                    color: Theme.of(context).primaryColor,
                    width: 2,
                  ),
                ),
              ),
              tabs: List.generate(
                widget.itemCount,
                    (index) => widget.tabBuilder(context, index),
              ),
            ),
          ),
          Expanded(
            child: TabBarView(
              controller: controller,
              children: List.generate(
                widget.itemCount,
                    (index) => widget.pageBuilder(context, index),
              ),
            ),
          ),
        ],
      );
    

    onPositionChange() 
      if (!controller.indexIsChanging) 
        _currentPosition = controller.index;
        if (widget.onPositionChange is ValueChanged<int>) 
          widget.onPositionChange(_currentPosition);
        
      
    

    onScroll() 
      if (widget.onScroll is ValueChanged<double>) 
        widget.onScroll(controller.animation.value);
      
    
  

【讨论】:

【参考方案3】:

您可以在 Tabbarview 小部件中使用 for 循环来使用动态子项

List<String> categories = ["category 1" , "category 2", "category 3",];
return TabBarView(
  children:[
    for(var category in categories)
      Text(category), // this widget will show a text with specific category. You can use any other widget
  ],
);

【讨论】:

【参考方案4】:

空安全版本

import 'package:flutter/material.dart';

class CustomTabView extends StatefulWidget 
  final int? itemCount;
  final IndexedWidgetBuilder? tabBuilder;
  final IndexedWidgetBuilder? pageBuilder;
  final Widget? stub;
  final ValueChanged<int>? onPositionChange;
  final ValueChanged<double>? onScroll;
  final int? initPosition;


  CustomTabView(this.itemCount, this.tabBuilder, this.pageBuilder, this.stub,
    this.onPositionChange, this.onScroll, this.initPosition);

  @override
  _CustomTabsState createState() => _CustomTabsState();


class _CustomTabsState extends State<CustomTabView> with TickerProviderStateMixin 
  late TabController controller;
  late int _currentCount;
  late int _currentPosition;

  @override
  void initState() 
    _currentPosition = widget.initPosition!;
    controller = TabController(
      length: widget.itemCount!,
      vsync: this,
      initialIndex: _currentPosition,
    );
    controller.addListener(onPositionChange);
    controller.animation!.addListener(onScroll);
    _currentCount = widget.itemCount!;
    super.initState();
  

  @override
  void didUpdateWidget(CustomTabView oldWidget) 
    if (_currentCount != widget.itemCount) 
      controller.animation!.removeListener(onScroll);
      controller.removeListener(onPositionChange);
      controller.dispose();

      if (widget.initPosition != null) 
        _currentPosition = widget.initPosition!;
      

      if (_currentPosition > widget.itemCount! - 1) 
        _currentPosition = widget.itemCount! - 1;
        _currentPosition = _currentPosition < 0 ? 0 :
        _currentPosition;
        if (widget.onPositionChange is ValueChanged<int>) 
          WidgetsBinding.instance!.addPostFrameCallback((_)
            if(mounted) 
              widget.onPositionChange!(_currentPosition);
            
          );
        
      

      _currentCount = widget.itemCount!;
      setState(() 
        controller = TabController(
          length: widget.itemCount!,
          vsync: this,
          initialIndex: _currentPosition,
        );
        controller.addListener(onPositionChange);
        controller.animation!.addListener(onScroll);
      );
     else if (widget.initPosition != null) 
      controller.animateTo(widget.initPosition!);
    

    super.didUpdateWidget(oldWidget);
  

  @override
  void dispose() 
    controller.animation!.removeListener(onScroll);
    controller.removeListener(onPositionChange);
    controller.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 
    if (widget.itemCount! < 1) return widget.stub ?? Container();

    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        Container(
          alignment: Alignment.center,
          child: TabBar(
            isScrollable: true,
            controller: controller,
            labelColor: Theme.of(context).primaryColor,
            unselectedLabelColor: Theme.of(context).hintColor,
            indicator: BoxDecoration(
              border: Border(
                bottom: BorderSide(
                  color: Theme.of(context).primaryColor,
                  width: 2,
                ),
              ),
            ),
            tabs: List.generate(
              widget.itemCount!,
                  (index) => widget.tabBuilder!(context, index),
            ),
          ),
        ),
        Expanded(
          child: TabBarView(
            controller: controller,
            children: List.generate(
              widget.itemCount!,
                  (index) => widget.pageBuilder!(context, index),
            ),
          ),
        ),
      ],
    );
  

  onPositionChange() 
    if (!controller.indexIsChanging) 
      _currentPosition = controller.index;
      if (widget.onPositionChange is ValueChanged<int>) 
        widget.onPositionChange!(_currentPosition);
      
    
  

  onScroll() 
    if (widget.onScroll is ValueChanged<double>) 
      widget.onScroll!(controller.animation!.value);
    
  

【讨论】:

以上是关于颤动中 TabView 的动态子项的主要内容,如果未能解决你的问题,请参考以下文章

Primefaces TabView 动态

SwiftUI:TabView 动态强调颜色

PrimeNG - 如何在 p-tabView 组件中动态添加和删除 p-tabPanel

如何动态收紧 TabView 的高度?

动态 TabView primefaces,选项卡渲染属性不起作用

SwiftUI:将项目动态添加到 TabView [重复]