fluttercandies / extended_tabs

A powerful official extension library of Tab/TabBar/TabView, which support to scroll ancestor or child Tabs when current is overscroll, and set scroll direction and cache extent.
https://fluttercandies.github.io/extended_tabs/
MIT License
268 stars 49 forks source link

ExtendedTabBarView height problem? #11

Closed liudonghua123 closed 4 years ago

liudonghua123 commented 4 years ago

There is some existed issue in TabBarView about the height. see https://github.com/flutter/flutter/issues/55464, https://github.com/flutter/flutter/issues/54968. Can we fix this in ExtendedTabBarView?

zmtzawqlp commented 4 years ago

i don't think they are issues, you should understand more about silver. NestedScrollView(body:Column(tabbar,Expaned(tabBarView)) will be ok for you

liudonghua123 commented 4 years ago

@zmtzawqlp hi, Thanks your tips, I tried your suggestions and write the following demonstrate code, but it still not work. You can quickly test it in https://dartpad.dev/ or https://dartpad.cn/.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SamplePage(),
      ),
    );
  }
}

class SamplePage extends StatefulWidget {
  SamplePage({Key key}) : super(key: key);

  @override
  _SamplePageState createState() => _SamplePageState();
}

class _SamplePageState extends State<SamplePage> with TickerProviderStateMixin {
  TabController _tabController;
  var tabData = ['tab1', 'tab2'];

  @override
  void initState() {
    _tabController = TabController(length: tabData.length, vsync: this);
    _tabController.addListener(() {
      if (_tabController.indexIsChanging) {
        setState(() {});
      }
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return NestedScrollView(
      headerSliverBuilder: (context, value) {
        return [];
      },
      body: Column(
        children: [
          /// some widget above tab header
          Container(
            height: 200,
            alignment: Alignment.center,
            child: Text('Header'),
          ),

          /// tab header
          TabBar(
            controller: _tabController,
            tabs: [
              ...tabData.map(
                (item) => Tab(
                  child: Text(item),
                ),
              ),
            ],
          ),

          /// tab content
          Expanded(
            child: TabBarView(
              controller: _tabController,
              children: <Widget>[
                tabContent('tab1', 50),
                tabContent('tab2', 5),
              ],
            ),
          ),

          /// some widget below tab content
          Container(
            height: 100,
            alignment: Alignment.center,
            child: Text('Footer'),
          ),
        ],
      ),
    );
  }

  Widget tabContent(String title, int length) {
    return Column(
      children: [
        ...List.generate(
          length,
          (index) => ListTile(
            title: Text('${title} item ${index}'),
          ),
        ),
      ],
    );
  }
}

I have also tried the following code initial posted in stackoverflow (https://stackoverflow.com/questions/54642710/tabbarview-with-dynamic-container-height/57383014#57383014), but it still have some issue about the height and add header/footer around the tabbarview.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  final bodyGlobalKey = GlobalKey();
  final List<Widget> myTabs = [
    Tab(text: 'auto short'),
    Tab(text: 'auto long'),
    Tab(text: 'fixed'),
  ];
  TabController _tabController;
  ScrollController _scrollController;
  bool fixedScroll;

  Widget _buildCarousel() {
    return Stack(
      children: <Widget>[
        Placeholder(fallbackHeight: 100),
        Positioned.fill(
            child: Align(alignment: Alignment.center, child: Text('Slider'))),
      ],
    );
  }

  @override
  void initState() {
    _scrollController = ScrollController();
    _tabController = TabController(length: 3, vsync: this);
    _tabController.addListener(_smoothScrollToTop);

    super.initState();
  }

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

  _smoothScrollToTop() {
    _scrollController.animateTo(
      0,
      duration: Duration(microseconds: 300),
      curve: Curves.ease,
    );

  }

  _buildTabContext(int lineCount) => Container(
        child: ListView.builder(
          physics: const ClampingScrollPhysics(),
          itemCount: lineCount,
          itemBuilder: (BuildContext context, int index) {
            return Text('some content');
          },
        ),
      );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        controller: _scrollController,
        headerSliverBuilder: (context, value) {
          return [
            SliverToBoxAdapter(child: _buildCarousel()),
            SliverToBoxAdapter(
              child: TabBar(
                controller: _tabController,
                labelColor: Colors.redAccent,
                isScrollable: true,
                tabs: myTabs,
              ),
            ),
          ];
        },
        body: Container(
          child: TabBarView(
            controller: _tabController,
            children: [
              _buildTabContext(50),
              _buildTabContext(200),
              _buildTabContext(2)
            ],
          ),
        ),
      ),
    );
  }
}
liudonghua123 commented 4 years ago

Finally, I tried using AnimatedSwitcher with conditional rendering, and add some simulative tab page switch animation. But what is not perfect is the animation is not exact same as the TabBarView, and miss the switch gesture on tab page content currently (I will read more docs about how to implement this). If the TabBarView could fit its children's height, then no such hacks need.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SamplePage(),
      ),
    );
  }
}

class SamplePage extends StatefulWidget {
  SamplePage({Key key}) : super(key: key);

  @override
  _SamplePageState createState() => _SamplePageState();
}

class _SamplePageState extends State<SamplePage> with TickerProviderStateMixin {
  TabController _tabController;
  var tabData = ['tab1', 'tab2'];

  @override
  void initState() {
    _tabController = TabController(length: tabData.length, vsync: this);
    _tabController.addListener(() {
      if (_tabController.indexIsChanging) {
        setState(() {});
      }
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
        child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        /// some widget above tab header
        Container(
          height: 200,
          alignment: Alignment.center,
          child: Text('Header'),
        ),

        /// tab header
        TabBar(
          controller: _tabController,
          tabs: [
            ...tabData.map(
              (item) => Tab(
                child: Text(item),
              ),
            ),
          ],
        ),

        /// tab content
        AnimatedSwitcher(
          duration: Duration(milliseconds: 500),
          switchInCurve: Curves.easeInOut,
          switchOutCurve: Curves.easeInOut,
          transitionBuilder: (Widget child, Animation<double> animation) {
            var direction = _tabController.index > _tabController.previousIndex
                ? AxisDirection.left
                : AxisDirection.right;
            return SlideTransitionX(
              child: child,
              direction: direction,
              position: animation,
            );
          },
          child: _tabController.index == 0
              ? tabContent('tab1', 50)
              : tabContent('tab2', 10),
        ),
      ],
    ));
  }

  Widget tabContent(
    String title,
    int length,
  ) {
    return Column(
      key: ValueKey<int>(length),
      children: [
        ...List.generate(
          length,
          (index) => ListTile(
            title: Text('${title} item ${index}'),
            trailing: Icon(Icons.access_alarm),
          ),
        ),
      ],
    );
  }
}

/// code copied from https://book.flutterchina.club/chapter9/animated_switcher.html
class SlideTransitionX extends AnimatedWidget {
  SlideTransitionX({
    Key key,
    @required Animation<double> position,
    this.transformHitTests = true,
    this.direction = AxisDirection.down,
    this.child,
  })  : assert(position != null),
        super(key: key, listenable: position) {
    // 偏移在内部处理
    switch (direction) {
      case AxisDirection.up:
        _tween = Tween(begin: Offset(0, 1), end: Offset(0, 0));
        break;
      case AxisDirection.right:
        _tween = Tween(begin: Offset(-1, 0), end: Offset(0, 0));
        break;
      case AxisDirection.down:
        _tween = Tween(begin: Offset(0, -1), end: Offset(0, 0));
        break;
      case AxisDirection.left:
        _tween = Tween(begin: Offset(1, 0), end: Offset(0, 0));
        break;
    }
  }

  Animation<double> get position => listenable;

  final bool transformHitTests;

  final Widget child;

  //退场(出)方向
  final AxisDirection direction;

  Tween<Offset> _tween;

  @override
  Widget build(BuildContext context) {
    Offset offset = _tween.evaluate(position);
    if (position.status == AnimationStatus.reverse) {
      switch (direction) {
        case AxisDirection.up:
          offset = Offset(offset.dx, -offset.dy);
          break;
        case AxisDirection.right:
          offset = Offset(-offset.dx, offset.dy);
          break;
        case AxisDirection.down:
          offset = Offset(offset.dx, -offset.dy);
          break;
        case AxisDirection.left:
          offset = Offset(-offset.dx, offset.dy);
          break;
      }
    }
    return FractionalTranslation(
      translation: offset,
      transformHitTests: transformHitTests,
      child: child,
    );
  }
}
zmtzawqlp commented 4 years ago

加群问吧。完全不知道你的需求