Flutter scrollbar that is in horizontal scrollview doesnt show correctly inner scrollview

1.2k views Asked by At

enter image description here

So, I have bigger scroll view that scrolls horizontally, and inside - little box (red color) and smaller scrollview (orange color) that scrolls vertically. There are two scrollbars on the bigger scrollview (1 - for horizontal), and second - for vertical inner.

And the problem - vertical scrollbar doesnt look right, because it can go only like blue arrow shows, and I want it to have either full height of the bigger scrollview, or be right near scrollable vertical part, but then dont hide itself if scrolled in horizontal direction.

Run on dartPad

import 'package:flutter/material.dart';

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: ScrollSizingWidget(),
        ),
      ),
    );
  }
}

class ScrollSizingWidget extends StatefulWidget {
  const ScrollSizingWidget({
    Key? key,
  }) : super(key: key);

  @override
  State<ScrollSizingWidget> createState() => _ScrollSizingWidgetState();
}

class _ScrollSizingWidgetState extends State<ScrollSizingWidget> {
  final ScrollController _horizontal = ScrollController();
  final ScrollController _vertical = ScrollController();

  @override
  void dispose() {
    _horizontal.dispose();
    _vertical.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scrollbar(
        controller: _vertical,
        notificationPredicate: (notification) => notification.depth == 1,
        child: Scrollbar(
          controller: _horizontal,
          scrollbarOrientation: ScrollbarOrientation.bottom,
          child: SingleChildScrollView(
            controller: _horizontal,
            scrollDirection: Axis.horizontal,
            child: SizedBox(
              height: 500,
              width: 1000,
              child: Column(
                children:[
                  Container(width: 1000, height: 200, color: Colors.green),
                  Flexible(
                    child: SingleChildScrollView(
                      controller: _vertical,
                      child: Container(
                        height: 700,
                        width: 1000,
                        color: Colors.yellow,
                      )
                    )
                  ),
                  
                ]
              )
            ),
 
        ),
      ),
    );
  }
}
1

There are 1 answers

4
BBK On BEST ANSWER

I have used your code to reproduce the issue. If I understood your needs right, here is the fix:

import 'package:flutter/material.dart';

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

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => MaterialApp(
        theme: ThemeData.dark().copyWith(
          scaffoldBackgroundColor: darkBlue,
        ),
        debugShowCheckedModeBanner: false,
        home: const Scaffold(
          body: Center(
            child: SafeArea(
              child: ScrollSizingWidget(),
            ),
          ),
        ),
      );
}

class ScrollSizingWidget extends StatefulWidget {
  const ScrollSizingWidget({Key? key}) : super(key: key);

  @override
  State<ScrollSizingWidget> createState() => _ScrollSizingWidgetState();
}

class _ScrollSizingWidgetState extends State<ScrollSizingWidget> {
  late final ScrollController _horizontal;
  late final ScrollController _vertical;

  @override
  void initState() {
    super.initState();
    _horizontal = ScrollController();
    _vertical = ScrollController();
  }

  @override
  void dispose() {
    _horizontal.dispose();
    _vertical.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scrollbar(
        controller: _horizontal,
        scrollbarOrientation: ScrollbarOrientation.bottom,
        child: SingleChildScrollView(
          controller: _horizontal,
          scrollDirection: Axis.horizontal,
          padding: EdgeInsets.zero,
          child: SizedBox(
            height: 500,
            width: 1000,
            child: Scrollbar(
              controller: _vertical,
              scrollbarOrientation: ScrollbarOrientation.right,
              child: SingleChildScrollView(
                controller: _vertical,
                scrollDirection: Axis.vertical,
                child: Column(
                  children: [
                    Container(
                      height: 200,
                      width: 1000,
                      color: Colors.red,
                    ),
                    Container(
                      height: 1000,
                      width: 1000,
                      color: Colors.orange,
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      );
}

Firstly, you say that your main SingleChildScrollView scrolls horizontally, but your widget tree starts with a Scrollbar which uses a vertical ScrollController. So you should create your widgets step by step, as you explained.

Also, since you want to see the vertical Scrollbar through your main SingleChildScrollView, I wrapped both(red and orange Containers) with Scrollbar and SingleChildScrollView to have the effect you want. Furthermore, I connected these Scrollbar and SingleChildScrollView with the same horizontal ScrollController. So now, not only the orange Container, but both are scrollable and stick together, not independent.

If you don't want the red Container being scrolled along with the orange Container, check this:

import 'package:flutter/material.dart';

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

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => MaterialApp(
        theme: ThemeData.dark().copyWith(
          scaffoldBackgroundColor: darkBlue,
        ),
        debugShowCheckedModeBanner: false,
        home: const Scaffold(
          body: Center(
            child: SafeArea(
              child: ScrollSizingWidget(),
            ),
          ),
        ),
      );
}

class ScrollSizingWidget extends StatefulWidget {
  const ScrollSizingWidget({Key? key}) : super(key: key);

  @override
  State<ScrollSizingWidget> createState() => _ScrollSizingWidgetState();
}

class _ScrollSizingWidgetState extends State<ScrollSizingWidget> {
  late final ScrollController _horizontal;
  late final ScrollController _vertical;

  @override
  void initState() {
    super.initState();
    _horizontal = ScrollController();
    _vertical = ScrollController();
  }

  @override
  void dispose() {
    _horizontal.dispose();
    _vertical.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scrollbar(
        controller: _horizontal,
        scrollbarOrientation: ScrollbarOrientation.bottom,
        child: SingleChildScrollView(
          controller: _horizontal,
          scrollDirection: Axis.horizontal,
          padding: EdgeInsets.zero,
          child: SizedBox(
            height: 500,
            width: 1000,
            child: Column(
              children: [
                Container(
                  height: 200,
                  width: 1000,
                  color: Colors.red,
                ),
                Expanded(
                  child: Scrollbar(
                    controller: _vertical,
                    scrollbarOrientation: ScrollbarOrientation.right,
                    child: SingleChildScrollView(
                      controller: _vertical,
                      scrollDirection: Axis.vertical,
                      child: Container(
                        height: 700,
                        width: 1000,
                        color: Colors.orange,
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      );
}

Lastly, Scrollbar's position in iOS is a bit buggy because of the notch, etc. So I wrapped your ScrollSizingWidget with SafeArea to fix the issue in iOS.

If these answers are not what you expect, please don't hesitate to write.

Edit: After your explanations in the comments below, I have created another fix. I believe CustomScrollView and Sliver widgets are fits here perfectly. The red Container, which you want to stay in its position, should be wrapped with the SliverAppBar. Lastly, the orange Container, which you want to be able to scroll vertically, could be wrapped with SliverFixedExtentList. Please check the code below:

import 'package:flutter/material.dart';

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

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => MaterialApp(
        theme: ThemeData.dark().copyWith(
          scaffoldBackgroundColor: darkBlue,
        ),
        debugShowCheckedModeBanner: false,
        home: const Scaffold(
          body: Center(
            child: SafeArea(
              child: ScrollSizingWidget(),
            ),
          ),
        ),
      );
}

class ScrollSizingWidget extends StatefulWidget {
  const ScrollSizingWidget({Key? key}) : super(key: key);

  @override
  State<ScrollSizingWidget> createState() => _ScrollSizingWidgetState();
}

class _ScrollSizingWidgetState extends State<ScrollSizingWidget> {
  late final ScrollController _horizontal;
  late final ScrollController _vertical;

  @override
  void initState() {
    super.initState();
    _horizontal = ScrollController();
    _vertical = ScrollController();
  }

  @override
  void dispose() {
    _horizontal.dispose();
    _vertical.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scrollbar(
        controller: _horizontal,
        scrollbarOrientation: ScrollbarOrientation.bottom,
        child: SingleChildScrollView(
          controller: _horizontal,
          scrollDirection: Axis.horizontal,
          padding: EdgeInsets.zero,
          child: SizedBox(
            height: 500,
            width: 1000,
            child: Scrollbar(
              controller: _vertical,
              scrollbarOrientation: ScrollbarOrientation.right,
              child: CustomScrollView(
                controller: _vertical,
                scrollDirection: Axis.vertical,
                slivers: [
                  SliverAppBar(
                    toolbarHeight: 200.0,
                    collapsedHeight: 200.0,
                    pinned: true,
                    stretch: true,
                    elevation: 0.0,
                    backgroundColor: Colors.transparent,
                    title: Container(
                      height: 200.0,
                      color: Colors.red,
                    ),
                    titleSpacing: 0,
                  ),
                  SliverFixedExtentList(
                    itemExtent: 1200.0,
                    delegate: SliverChildBuilderDelegate(
                      (_, __) => Container(
                        color: Colors.orange,
                      ),
                      childCount: 1,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      );
}