flutter 无法为抖动中的TabBarView设置动态高度

pgky5nke  于 2023-01-27  发布在  Flutter
关注(0)|答案(3)|浏览(232)

我正在尝试创建TabBar,它将位于页面中间(描述小部件必须在顶部)。
问题是我必须手动设置包含TabBarView的Container小部件的高度。如果我没有设置这个高度,我会得到错误Horizontal viewport was given unbounded height.
顶级小部件:

Widget build(BuildContext context) {
    return Scaffold(
      appBar: CustomAppBar(),
      body: SingleChildScrollView(
        child: Column(
          children: <Widget>[Description(), Tabs()],
        ),
      ),
    );
  }

选项卡小部件:

class Tabs extends StatelessWidget {
  final _tabs = [
    Tab(
      icon: Icon(Icons.menu),
      text: 'Menu',
    ),
    Tab(
      icon: Icon(Icons.mode_comment),
      text: 'Reviews',
    )
  ];

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
        length: _tabs.length,
        child: Column(
          children: <Widget>[
            TabBar(
              labelColor: PickColors.black,
              indicatorSize: TabBarIndicatorSize.tab,
              tabs: _tabs,
            ),
            Container(
              width: double.infinity,
              height: 200, // I need to remove this and make height dynamic
              child: TabBarView(
                children: <Widget>[MenuTab(), ReviewsTab()],
              ),
            ),
          ],
        ));
  }
}

因为标签的内容是动态的,所以高度也是动态的。我不能在这里使用静态高度。
有没有一个静态高度的容器的替代品?我怎样才能使我的标签的高度动态?

z2acfund

z2acfund1#

我已经通过将SingleChildScrollView更改为ListView并编写自己的TabView小部件(在Stack Package 器中包含选项卡)修复了这个问题。
顶层小部件主体 Package 从列和SingleChildScrollView更改为ListView:

Widget build(BuildContext context) {
    SizeConfig().init(context);
    return Scaffold(
      appBar: RestaurantInfoAppBar(),
      body: ListView(
        children: <Widget>[Description(), Tabs()],
      ),
    );
  }

选项卡小部件-删除了具有静态宽度 Package 器的容器:

Widget build(BuildContext context) {
    return DefaultTabController(
        length: _tabs.length,
        child: Column(
          children: <Widget>[
            TabBar(
              labelColor: PickColors.black,
              indicatorSize: TabBarIndicatorSize.tab,
              tabs: _tabs,
            ),
            TabsView(
              tabIndex: _tabIndex,
              firstTab: MenuTab(),
              secondTab: ReviewsTab(),
            )
          ],
        ));
  }

新的自定义TabsView组件目前只处理两个选项卡(因为我只需要两个),但可以很容易地更改为处理动态数量的选项卡:

class TabsView extends StatelessWidget {
  TabsView(
      {Key key,
      @required this.tabIndex,
      @required this.firstTab,
      @required this.secondTab})
      : super(key: key);

  final int tabIndex;
  final Widget firstTab;
  final Widget secondTab;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        AnimatedContainer(
          child: firstTab,
          width: SizeConfig.screenWidth,
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
          transform: Matrix4.translationValues(
              tabIndex == 0 ? 0 : -SizeConfig.screenWidth, 0, 0),
          duration: Duration(milliseconds: 300),
          curve: Curves.easeIn,
        ),
        AnimatedContainer(
          child: secondTab,
          width: SizeConfig.screenWidth,
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
          transform: Matrix4.translationValues(
              tabIndex == 1 ? 0 : SizeConfig.screenWidth, 0, 0),
          duration: Duration(milliseconds: 300),
          curve: Curves.easeIn,
        )
      ],
    );
  }
}

P.S.大小配置与媒体查询相同。(上下文).大小.宽度。
希望这对像我这样的人有帮助!:)

pepwfjgg

pepwfjgg2#

我发现这对我很有效

import 'package:cdc/ui/shared/app_color.dart';
import 'package:flutter/material.dart';

class OrderDetails extends StatefulWidget {
  @override
  _OrderDetailsState createState() => _OrderDetailsState();
}

class _OrderDetailsState extends State<OrderDetails>
    with SingleTickerProviderStateMixin {
  final List<Widget> myTabs = [
    Tab(text: 'one'),
    Tab(text: 'two'),
  ];

  TabController _tabController;
  int _tabIndex = 0;

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  void initState() {
    _tabController = TabController(length: 2, vsync: this);
    _tabController.addListener(_handleTabSelection);
    super.initState();
  }

  _handleTabSelection() {
    if (_tabController.indexIsChanging) {
      setState(() {
        _tabIndex = _tabController.index;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Order Detials'),
        backgroundColor: kPrimaryColor,
      ),
      body: ListView(
        padding: EdgeInsets.all(15),
        children: <Widget>[
          Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(14.0),
              color: const Color(0xffffffff),
              boxShadow: [
                BoxShadow(
                  color: const Color(0x2e000000),
                  offset: Offset(0, 0),
                  blurRadius: 10,
                ),
              ],
            ),
            child: Column(
              children: <Widget>[
                TabBar(
                  controller: _tabController,
                  labelColor: Colors.redAccent,
                  tabs: myTabs,
                ),
                Container(
                  child: [
                    Text('First tab'),
                    Column(
                      children:
                          List.generate(20, (index) => Text('line: $index'))
                              .toList(),
                    ),
                  ][_tabIndex],
                ),
              ],
            ),
          ),
          Container(child: Text('another component')),
        ],
      ),
    );
  }
}
3vpjnl9f

3vpjnl9f3#

实际上,TabBarView是使用Viewport实现的,它不会“冒泡”它的高度。如果不实现完全定制的TabBarView/PageView,很难实现动态高度。
我这里有一个解决方案,它也不是完美的,但基本上是无故障的,它使用一个Stack和一个不可见的隐藏的当前标签小部件来测量和动画大小和实际的TabBarView填充Stack
它运行得很好,但是这种方法的实际缺点是当前表被构建和布局了两次,只要小部件不使用全局键或隐藏的副作用,这应该不会导致任何问题。

class ShrinkWrappingTabBarView extends StatelessWidget {
  const ShrinkWrappingTabBarView({
    super.key,
    required this.tabController,
    required this.children,
  });

  final TabController tabController;
  final List<Widget> children;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Opacity(
          opacity: 0.0,
          child: AnimatedSize(
            duration: const Duration(milliseconds: 250),
            curve: Curves.easeOutExpo,
            child: SizedBox(
              width: double.infinity, // always fill horizontally
              child: CurrentTabControllerWidget(
                tabController: tabController,
                children: children,
              ),
            ),
          ),
        ),
        Positioned.fill(
          child: TabBarView(
            controller: tabController,
            children: children
                .map(
                  (e) => OverflowBox(
                    alignment: Alignment.topCenter,
                    // avoid shrinkwrapping to animated height
                    minHeight: 0,
                    maxHeight: double.infinity,
                    child: e,
                  ),
                )
                .toList(),
          ),
        ),
      ],
    );
  }
}

class CurrentTabControllerWidget extends StatefulWidget {
  const CurrentTabControllerWidget({
    super.key,
    required this.tabController,
    required this.children,
  });

  final TabController tabController;
  final List<Widget> children;

  @override
  State<CurrentTabControllerWidget> createState() => _CurrentTabControllerWidgetState();
}

class _CurrentTabControllerWidgetState extends State<CurrentTabControllerWidget> {
  @override
  void initState() {
    super.initState();
    widget.tabController.addListener(_tabUpdated);
    widget.tabController.animation?.addListener(_tabUpdated);
  }

  @override
  void dispose() {
    super.dispose();
    widget.tabController.removeListener(_tabUpdated);
    widget.tabController.animation?.removeListener(_tabUpdated);
  }

  @override
  void didUpdateWidget(covariant CurrentTabControllerWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.tabController != widget.tabController) {
      oldWidget.tabController.removeListener(_tabUpdated);
      widget.tabController.addListener(_tabUpdated);
      oldWidget.tabController.animation?.removeListener(_tabUpdated);
      widget.tabController.animation?.addListener(_tabUpdated);
      setState(() {});
    }
  }

  void _tabUpdated() => setState(() {});

  @override
  Widget build(BuildContext context) =>
      widget.children[widget.tabController.animation?.value.round() ?? widget.tabController.index];
}

相关问题