如何在 Flutter 中使用函数创建动态 TabBarView/渲染新 Tab?

Posted

技术标签:

【中文标题】如何在 Flutter 中使用函数创建动态 TabBarView/渲染新 Tab?【英文标题】:How to create a dynamic TabBarView/ Render a new Tab with a function in Flutter? 【发布时间】:2018-10-06 18:33:12 【问题描述】:

所以我学习了 Flutter 已经有一段时间了,我陷入了困境。对不起,如果这是一个愚蠢的问题。我目前正在尝试构建类似卡片标签的东西。信息和小部件将存储在卡片中。

想象一下 Tinder 之类的东西,它们有多个卡片叠并左右滑动进行导航。

我计划创建它,但我似乎找不到使用按钮添加/渲染新卡片的方法。

这就像向列表中添加一些东西一样,Flutter 将使用一个 ListView 构建器,我们将在其中添加到列表中。但是没有 TabBarView 构建器。这是不可能的事情吗?我尝试在选项卡中放置一个列表,但它仍然不一样。

我在这里创建了一些基本骨架来帮助传达我的意思。所以卡片会左右滑动,appBar中有一个添加卡片的按钮。长度现在是 2,我希望按钮渲染第三张卡片。这可能吗?

提前致谢!

import 'package:flutter/material.dart';

void main() 
  runApp(new MaterialApp(
    home: new CardStack(),

  ));



class CardStack extends StatefulWidget 
  @override
  _MainState createState() => new _MainState();



class _MainState extends State<CardStack> with SingleTickerProviderStateMixin 

  TabController _cardController;

  @override
  void initState() 
    super.initState();
    _cardController = new TabController(vsync: this, length: 2);
  

  @override
  void dispose() 
    _cardController.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 

    return new Scaffold(
      backgroundColor: Colors.grey[300],
      appBar: new AppBar(
          actions: <Widget>[
            new IconButton(
              icon: const Icon(Icons.add),
              tooltip: 'Add Tabs',
              onPressed: null,
            ),
          ],
          title: new Text("Title Here"),
          bottom: new PreferredSize(
          preferredSize: const Size.fromHeight(20.0),
          child: new Theme(
            data: Theme.of(context).copyWith(accentColor: Colors.grey),
            child: new Container(
              height: 50.0,
              alignment: Alignment.center,
              child: new TabPageSelector(controller: _cardController),
            ),
          )
        )
      ),
      body: new TabBarView(
        controller: _cardController,
        children: <Widget>[
          new Center(
            child: new Card(
              child: new Container(
                  height: 450.0,
                  width: 300.0,
                  child: new IconButton(
                    icon: new Icon(Icons.favorite, size: 100.0),
                    tooltip: 'Favorited',
                    onPressed: null,
                  )
              ),
            ),
          ),
          new Center(
            child: new Card(
              child: new Container(
                  height: 450.0,
                  width: 300.0,
                  child: new IconButton(
                    icon: new Icon(Icons.local_pizza, size: 50.0,),
                    tooltip: 'Pizza',
                    onPressed: null,
                  )
              ),
            ),
          ),
        ],
      ),
    );
  

【问题讨论】:

【参考方案1】:

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

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

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);
      
    
  

【讨论】:

这很有帮助。谢谢(y) @Yuriy Luchaninov,如果我想根据选择为选项卡视图添加“删除页面”选项,则应从 PageBuilder 中删除相应页面。 请找到标签删除图片链接参考。 ibb.co/9NPNkkZ 好朋友 这是黄金。非常感谢。【参考方案2】:

试试这个。

要制作动态标签,您可以使用列表并在每次按钮单击时继续附加列表。

技巧:清除列表并重新绘制一个空的小部件,然后根据您的列表再次绘制小部件。

 import 'package:flutter/material.dart';
void main() 
  runApp(new MaterialApp(
    home: new CardStack(),
  ));


class DynamicTabContent 
  IconData icon;
  String tooTip;

  DynamicTabContent.name(this.icon, this.tooTip);


class CardStack extends StatefulWidget 
  @override
  _MainState createState() => new _MainState();


class _MainState extends State<CardStack> with TickerProviderStateMixin 
  List<DynamicTabContent> myList = new List();

  TabController _cardController;

  TabPageSelector _tabPageSelector;

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

    myList.add(new DynamicTabContent.name(Icons.favorite, "Favorited"));
    myList.add(new DynamicTabContent.name(Icons.local_pizza, "local pizza"));

    _cardController = new TabController(vsync: this, length: myList.length);
    _tabPageSelector = new TabPageSelector(controller: _cardController);
  

  @override
  void dispose() 
    _cardController.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      backgroundColor: Colors.grey[300],
      appBar: new AppBar(
          actions: <Widget>[
            new Padding(
              padding: const EdgeInsets.all(1.0),
              child: new IconButton(
                icon: const Icon(
                  Icons.add,
                  size: 30.0,
                  color: Colors.white,
                ),
                tooltip: 'Add Tabs',
                onPressed: () 
                  List<DynamicTabContent> tempList = new List();

                  myList.forEach((dynamicContent) 
                    tempList.add(dynamicContent);
                  );

                  setState(() 
                    myList.clear();
                  );

                  if (tempList.length % 2 == 0) 
                    myList.add(new DynamicTabContent.name(Icons.shopping_cart, "shopping cart"));
                   else 
                    myList.add(new DynamicTabContent.name(Icons.camera, "camera"));
                  

                  tempList.forEach((dynamicContent) 
                    myList.add(dynamicContent);
                  );

                  setState(() 
                    _cardController = new TabController(vsync: this, length: myList.length);
                    _tabPageSelector = new TabPageSelector(controller: _cardController);
                  );
                ,
              ),
            ),
          ],
          title: new Text("Title Here"),
          bottom: new PreferredSize(
              preferredSize: const Size.fromHeight(10.0),
              child: new Theme(
                data: Theme.of(context).copyWith(accentColor: Colors.grey),
                child: myList.isEmpty
                    ? new Container(
                        height: 30.0,
                      )
                    : new Container(
                        height: 30.0,
                        alignment: Alignment.center,
                        child: _tabPageSelector,
                      ),
              ))),
      body: new TabBarView(
        controller: _cardController,
        children: myList.isEmpty
            ? <Widget>[]
            : myList.map((dynamicContent) 
                return new Card(
                  child: new Container(
                      height: 450.0,
                      width: 300.0,
                      child: new IconButton(
                        icon: new Icon(dynamicContent.icon, size: 100.0),
                        tooltip: dynamicContent.tooTip,
                        onPressed: null,
                      )),
                );
              ).toList(),
      ),
    );
  

希望这会有所帮助:)

【讨论】:

我跑了这个例子..我无法获得当前标签的索引..因为您使用地图来显示所有标签,我需要索引来显示某些组件并隐藏一些 此解决方案存在问题,当在 build() 中实例化新的 TabController 时,TabPageSelector 会丢失其状态。我也在努力解决这个问题。请看:webmshare.com/ZQ00d

以上是关于如何在 Flutter 中使用函数创建动态 TabBarView/渲染新 Tab?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter Tab 切换时保留tab的状态

如何在 Flutter 中动态附加到 Column 小部件的子级

Flutter切换tab后保留tab状态

Flutter - 如何创建遵循其父高度的流体/动态容器?

Flutter初步探索(二)使用Tabs

EasyUI创建异步树形菜单和动态添加tab页面