1

In continuation with question

The solution provided above is good. But hard for me to implement in my project.

Expected results:

  • I've created two tabs.
  • In each tab I have SingleChildScrollView wrapped with Scrollbar.
  • I can not have the primary scrollcontroller in both the tabs, because that throws me exception: "ScrollController attached to multiple scroll views."
  • For Tab ONE I use primary scrollcontroller, for Tab TWO I created Scrollcontroller and attached it.
  • Widgets in both the tabs should be scrollabale using keyboard and mouse.

Actual results:

  • For Tab ONE with primary scrollcontroller I can scroll both by keyboard and dragging scrollbar.
  • But for Tab TWO with non primary scrollcontroller, I have to scroll only by dragging scrollbar. This tab doesn't respond to keyboard page up /down keys.
  • When keyboard keys are used in Tab TWO actually contents of tab ONE are getting scrolled.

Check code:

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 Answer 1

1

This has been accepted as a bug in flutter. Pl follow for progress here: https://github.com/flutter/flutter/issues/83711

Note for other developers facing same issue. To overcome the mentioned problem, I changed my design layout. Instead of tabbar view I used Navigationrail widget. This solved my problem. NavigationRail widget allowed me to attach primary scroll controller to multiple widgets without giving me exception: "ScrollController attached to multiple scroll views." Sample code.

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;
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.