TabView在ListView中具有可变高度

时间:2018-09-28 03:09:23

标签: flutter

我有一个具有多种类型的项目的ListView,其中之一是TabBar和TabBarView的小部件。
问题是每个标签页的高度都不同,我希望ListView根据它是高度
Like this
但是TabBarView不接受无限的高度,并且ListView不能为其子代提供高度。
反正可以这样做吗?还是我必须将TabBar与可以包裹其内容(例如列)的东西一起使用,并牺牲在选项卡之间滑动的能力?

4 个答案:

答案 0 :(得分:5)

请勿使用TabBarView,而应将IndexedStackVisibility一起使用。

import 'package:flutter/material.dart';

final Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  TabController tabController;
  int selectedIndex = 0;

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

    tabController = TabController(
      initialIndex: selectedIndex,
      length: 2,
      vsync: this,
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: ListView(
          children: [
            Container(
              height: 128,
              color: Colors.blue,
            ),
            Container(
              height: 256,
              color: Colors.green,
            ),
            TabBar(
              tabs: <Tab>[
                Tab(text: 'Tab Left'),
                Tab(text: 'Tab Right'),
              ],
              controller: tabController,
              onTap: (int index) {
                setState(() {
                  selectedIndex = index;
                  tabController.animateTo(index);
                });
              },
            ),
            Divider(height: 0),
            IndexedStack(
              children: <Widget>[
                Visibility(
                  child: Container(
                    height: 200,
                    color: Colors.yellow,
                    child: Center(
                      child: Text('Content left'),
                    ),
                  ),
                  maintainState: true,
                  visible: selectedIndex == 0,
                ),
                Visibility(
                  child: Container(
                    height: 1000,
                    color: Colors.red,
                    child: Center(
                      child: Text('Content right'),
                    ),
                  ),
                  maintainState: true,
                  visible: selectedIndex == 1,
                ),
              ],
              index: selectedIndex,
            ),
          ],
        ),
      ),
    );
  }
}

IndexedStack将基于index显示一个孩子列表中的一个孩子,而Visibility将保持显示或隐藏视图。隐藏视图时,不会显示多余的空白(堆栈高度等于其子级的最大高度)。

这里是飞镖https://dartpad.dev/535f06aa01257b049c7f2f9c719c9881

答案 1 :(得分:2)

您无需具有TabView即可显示Tabs内容。 这种方法的缺点是您失去了动画和滑动,因此,如果您确实需要它,则需要自己进行。

enter image description here

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

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

  TabController _tabController;

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

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

  _handleTabSelection() {
    if (_tabController.indexIsChanging) {
      setState(() {});
    }
  }

  _listItem() {
    return Container(
      decoration: BoxDecoration(
        border: Border.all(
          width: 1,
          color: Colors.blueAccent,
        ),
      ),
      height: 120,
      child: Center(
        child: Text('List Item', style: TextStyle(fontSize: 20.0)),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: <Widget>[
          _listItem(),
          TabBar(
            controller: _tabController,
            labelColor: Colors.redAccent,
            tabs: myTabs,
          ),
          Center(
            child: [
              Text('first tab'),
              Column(
                children: [
                  Text('second tab'),
                  ...List.generate(10, (index) => Text('line: $index'))
                ],
              ),
              Column(
                children: [
                  Text('third tab'),
                  ...List.generate(20, (index) => Text('line: $index'))
                ],
              ),
            ][_tabController.index],
          ),
          _listItem(),
          _listItem(),
        ],
      ),
    );
  }
}

答案 2 :(得分:0)

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      home: Home(),
    );
  }
}

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return HomeState();
  }
}

class HomeState extends State<Home> with SingleTickerProviderStateMixin {
  TabController tabController;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    tabController = TabController(length: 3, vsync: this, initialIndex: 0);
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      body: ListView(
        children: <Widget>[
          DummySection(height: 100.0,color: Colors.red,),
          DummySection(height: 150.0,color: Colors.yellow,),
          Container(
            height: 350.0,
            color: Colors.blue,
            child: Column(
              children: <Widget>[
                TabBar(
                  unselectedLabelColor: Colors.blue[100],
                  indicator: BoxDecoration(
                    color: Colors.lightBlue
                  ),
                  controller: tabController,
                  tabs: <Widget>[
                    Tab(text: "Home",),
                    Tab(text: "Fav",),
                    Tab(text: "Star",)
                  ],
                ),
                Expanded(
                  child: TabBarView(
                    controller: tabController,
                      children: [
                        DummyList(),
                        DummyList(),
                        DummyList()
                      ]
                  ),
                )
              ],
            ),
          ),
          DummySection(height: 100.0,color: Colors.red,),
          DummySection(height: 100.0,color: Colors.pink,)
        ],
      ),
    );
  }
}

// Dummy List Container
class DummySection extends StatelessWidget{
  Color color;
  double height;
  DummySection({this.color,this.height});

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: color,
      height: height,
    );
  }
}
// Dummy Listing for tab
class DummyList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView(
        children: <Widget>[
          Card(
            child: Container(
              height: 200.0,
              alignment: Alignment.center,
              child: Text("hello"),
            ),
          ),
          Card(
            child: Container(
              height: 200.0,
              alignment: Alignment.center,
              child: Text("hello"),
            ),
          ),
        ],
    );
  }
}

答案 3 :(得分:0)

这是我使用AnimatedSwitcher小部件(带有3个标签)在某种程度上模仿标签动画过渡的方法:

 TabController _tabController;
 ValueNotifier<int> _currentScreen = ValueNotifier<int>(0);
 int _previousScreen = 0;

  @override
  void initState() {
    _tabController = new TabController(length: 3, vsync: this);
    super.initState();
  }

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

   @override
   Widget build(BuildContext context) {
        return CustomScrollBar.Scrollbar(
          child: ListView(
            physics: BouncingScrollPhysics(),
            children: <Widget>[
    
              /// Tabbar
              TabBar(
                controller: _tabController,
                indicatorColor: ColorData.accentColor,
                labelColor: Colors.black,
                labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w700),
                indicatorPadding: EdgeInsets.symmetric(horizontal: 21),
                labelPadding: EdgeInsets.all(6),
                onTap: (i) {
                  _previousScreen = _currentScreen.value;
                  _currentScreen.value = i;
                },
                tabs: <Widget>[
                  Text('Screen 1'),
                  Text('Screen 2'),
                  Text('Screen 3'),
                ],
              ),
    
              /// Tab View
              ValueListenableBuilder<int>(
                  valueListenable: _currentScreen,
                  builder: (context, screen, child) {
                    return AnimatedSwitcher(
                      duration: Duration(milliseconds: 300),
                      switchInCurve: Curves.easeOut,
                      switchOutCurve: Curves.easeIn,
                      transitionBuilder: (Widget child, Animation<double> animation) {
                        final inAnimation =
                            Tween<Offset>(begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0)).animate(animation);
                        final outAnimation =
                            Tween<Offset>(begin: Offset(-1.0, 0.0), end: Offset(0.0, 0.0)).animate(animation);
    
                        final Widget inTransition = ClipRect(
                          child: SlideTransition(
                            position: inAnimation,
                            child: Padding(
                              padding: const EdgeInsets.all(8.0),
                              child: child,
                            ),
                          ),
                        );
    
                        final Widget outTransition = ClipRect(
                          child: SlideTransition(
                            position: outAnimation,
                            child: Padding(
                              padding: const EdgeInsets.all(8.0),
                              child: child,
                            ),
                          ),
                        );
    
                        //// Transition for two screens
    //                    if (child.key == ValueKey<int>(1)) {
    //                      print(_previousScreen);
    ////                    if (_previousScreen < screen) {
    //                      return ClipRect(
    //                        child: SlideTransition(
    //                          position: inAnimation,
    //                          child: Padding(
    //                            padding: const EdgeInsets.all(8.0),
    //                            child: child,
    //                          ),
    //                        ),
    //                      );
    //                    } else {
    //                      return ClipRect(
    //                        child: SlideTransition(
    //                          position: outAnimation,
    //                          child: Padding(
    //                            padding: const EdgeInsets.all(8.0),
    //                            child: child,
    //                          ),
    //                        ),
    //                      );
    //                    }

                        //// Transition for three screens
                        if (child.key == ValueKey<int>(1)) {
                          if (_previousScreen == 0 || _previousScreen == 1 && screen != 2) return inTransition;
                          return outTransition;
                        } else if (child.key == ValueKey<int>(2)) {
                          return inTransition;
                        } else {
                          return outTransition;
                        }
                      },
                      child: _returnTab(screen),
                    );
                  }),
            ],
          ),
        );
      }
    
      _returnTab(int screen) {
        switch (screen) {
          case 0:
            return ScreenOne(
              key: ValueKey<int>(0),
            );
            break;
          case 1:
            return ScreenTwo(
              key: ValueKey<int>(1),
            );
            break;
          case 2:
            return ScreenThree(
              key: ValueKey<int>(2),
            );
            break;
        }

对我很好:)