Flutter web tabbar滚动问题与非主滚动控制器

Posted

技术标签:

【中文标题】Flutter web tabbar滚动问题与非主滚动控制器【英文标题】:Flutter web tabbar scroll issue with non primary scrollcontroller 【发布时间】:2021-08-19 16:54:27 【问题描述】:

继续question

上面提供的解决方案很好。但我很难在我的项目中实施。

预期结果:

我创建了两个选项卡。 在每个选项卡中,我都有使用滚动条包裹的 SingleChildScrollView。 我不能在两个选项卡中都有主滚动控制器,因为这会引发异常:“滚动控制器附加到多个滚动视图。” 对于 Tab ONE,我使用主滚动控制器,对于 Tab 2,我创建了 Scrollcontroller 并附加了它。 两个选项卡中的小部件都应该可以使用键盘和鼠标滚动。

实际结果:

对于带有主滚动控制器的 Tab ONE,我可以通过键盘和拖动滚动条滚动。 但是对于带有非主滚动控制器的 Tab TWO,我只能通过拖动滚动条来滚动。此选项卡不响应键盘向上/向下翻页键。 当在选项卡 2 中使用键盘键时,实际上选项卡 ONE 的内容会滚动。

校验码:

import 'package:flutter/material.dart';

void main() 
  runApp(MyApp());


class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: TabExample(),
    );
  


class TabExample extends StatefulWidget 
  const TabExample(Key key) : super(key: key);

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


class _TabExampleState extends State<TabExample> 

  @override
  Widget build(BuildContext context) 
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: [
              Tab(icon: Text('Tab ONE')),
              Tab(icon: Text('Tab TWO')),
            ],
          ),
          title: Text('Tabs Demo'),
        ),
        body: TabBarView(
          children: [
            WidgetC(),
            WidgetD(),
          ],
        ),
      ),
    );
  


class WidgetC extends StatefulWidget 
  const WidgetC(Key key) : super(key: key);

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


class _WidgetCState extends State<WidgetC>
    with AutomaticKeepAliveClientMixin<WidgetC> 
  List<Widget> children;
  @override
  void initState() 
    children = [];
    for (int i = 0; i < 20; i++) 
      children.add(
        Padding(
          padding: EdgeInsets.symmetric(vertical: 16),
          child: Container(
            height: 100,
            width: double.infinity,
            color: Colors.blue,
            child: Center(child: Text('$i')),
          ),
        ),
      );
    
    super.initState();
  

  @override
  Widget build(BuildContext context) 
    super.build(context);
    return Scrollbar(
      key: PageStorageKey('WidgetC'),
      isAlwaysShown: true,
      showTrackOnHover: true,
      child: SingleChildScrollView(
        child: Column(
          children: children,
        ),
      ),
    );
  

  @override
  bool get wantKeepAlive => true;


class WidgetD extends StatefulWidget 
  const WidgetD(Key key) : super(key: key);

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


class _WidgetDState extends State<WidgetD>
    with AutomaticKeepAliveClientMixin<WidgetD> 
  List<Widget> children;
  ScrollController _scrollController;

  @override
  void initState() 
    _scrollController = ScrollController();
    children = [];
    for (int i = 0; i < 20; i++) 
      children.add(
        Padding(
          padding: EdgeInsets.symmetric(vertical: 16),
          child: Container(
            height: 100,
            width: double.infinity,
            color: Colors.green,
            child: Center(child: Text('$i')),
          ),
        ),
      );
    
    super.initState();
  

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

  @override
  Widget build(BuildContext context) 
    super.build(context);
    return Scrollbar(
      key: PageStorageKey('WidgetD'),
      isAlwaysShown: true,
      showTrackOnHover: true,
      controller: _scrollController,
      child: SingleChildScrollView(
        controller: _scrollController,
        child: Column(
          children: children,
        ),
      ),
    );
  

  @override
  bool get wantKeepAlive => true;

【问题讨论】:

【参考方案1】:

这已被接受为颤振中的错误。 请在此处关注进度:https://github.com/flutter/flutter/issues/83711

注意其他面临相同问题的开发人员。 为了克服上述问题,我改变了我的设计布局。我使用的是 Navigationrail 小部件,而不是标签栏视图。这解决了我的问题。 NavigationRail 小部件允许我将主滚动控制器附加到多个小部件而不给我例外:“滚动控制器附加到多个滚动视图。” 示例代码。

import 'dart:math';

import 'package:flutter/material.dart';

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

/// This is the main application widget.
class MyApp extends StatelessWidget 
  const MyApp(Key key) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) 
    return const MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  


/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget 
  const MyStatefulWidget(Key key) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();


/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> 
  int _selectedIndex = 0;
  WidgetC _widgetC = WidgetC();
  WidgetD _widgetD = WidgetD();

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(title: Text('NavigationRail Demo'), centerTitle: true),
      body: Row(
        children: <Widget>[
          NavigationRail(
            elevation: 8.0,
            selectedIndex: _selectedIndex,
            onDestinationSelected: (int index) 
              setState(() 
                _selectedIndex = index;
              );
            ,
            labelType: NavigationRailLabelType.all,
            groupAlignment: 0.0,
            destinations: const <NavigationRailDestination>[
              NavigationRailDestination(
                icon: Icon(Icons.favorite_border),
                selectedIcon: Icon(Icons.favorite),
                label: Text('Tab ONE'),
              ),
              NavigationRailDestination(
                icon: Icon(Icons.bookmark_border),
                selectedIcon: Icon(Icons.book),
                label: Text('Tab TWO'),
              ),
            ],
          ),
          const VerticalDivider(thickness: 1, width: 1),
          // This is the main content.
          Expanded(
            child: _getPageAtIndex(_selectedIndex),
          )
        ],
      ),
    );
  

  Widget _getPageAtIndex(int index) 
    switch (index) 
      case 0:
        return _widgetC;
      case 1:
        return _widgetD;
    
    return Container();
  


class WidgetC extends StatefulWidget 
  const WidgetC(Key key) : super(key: key);

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


class _WidgetCState extends State<WidgetC>
    with AutomaticKeepAliveClientMixin<WidgetC> 
  List<Widget> children;
  @override
  void initState() 
    children = [];
    for (int i = 0; i < 20; i++) 
      children.add(
        Padding(
          padding: EdgeInsets.symmetric(vertical: 16),
          child: Container(
            height: 100,
            width: double.infinity,
            color: Colors.primaries[Random().nextInt(Colors.primaries.length)],
            child: Center(child: Text('$i')),
          ),
        ),
      );
    
    super.initState();
  

  @override
  Widget build(BuildContext context) 
    super.build(context);
    return Scrollbar(
      key: PageStorageKey('WidgetC'),
      isAlwaysShown: true,
      showTrackOnHover: true,
      child: SingleChildScrollView(
        child: Column(
          children: children,
        ),
      ),
    );
  

  @override
  bool get wantKeepAlive => true;


class WidgetD extends StatefulWidget 
  const WidgetD(Key key) : super(key: key);

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


class _WidgetDState extends State<WidgetD>
    with AutomaticKeepAliveClientMixin<WidgetD> 
  List<Widget> children;
//  ScrollController _scrollController;

  @override
  void initState() 
//    _scrollController = ScrollController();
    children = [];
    for (int i = 0; i < 20; i++) 
      children.add(
        Padding(
          padding: EdgeInsets.symmetric(vertical: 16),
          child: Container(
            height: 100,
            width: double.infinity,
            color: Colors.primaries[Random().nextInt(Colors.primaries.length)],
            child: Center(child: Text('$i')),
          ),
        ),
      );
    
    super.initState();
  

  @override
  void dispose() 
//    _scrollController.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 
    super.build(context);
    return Scrollbar(
      key: PageStorageKey('WidgetD'),
      isAlwaysShown: true,
      showTrackOnHover: true,
//      controller: _scrollController,
      child: SingleChildScrollView(
//        controller: _scrollController,
        child: Column(
          children: children,
        ),
      ),
    );
  

  @override
  bool get wantKeepAlive => true;

【讨论】:

以上是关于Flutter web tabbar滚动问题与非主滚动控制器的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 仿京东商品详情页嵌套滚动组件

@OneToMany 与非主键 Hibernate 的关系

设置实体框架与非主键字段的一对一映射

Flutter——TabBar组件(顶部Tab切换组件)

Flutter Google Maps 不会横向滚动

带有标签的Flutter SliverAppBar覆盖内容