Search code examples
flutterpositionscrollbar

How to align scroll bar outside of its ListView?


enter image description here

So, the problem is in my RawScrollBar. There's no problem to place it inside ListView, but I need it to be outside of all containers. I can place it outside like on picture, but it seems like there's no way to place it to bottom where I need it to be. So, there's any way to change position for my RawScrollBar?

Additional info: on picture numbers 1, 2 and 3 are containers, and ListView itself is a table inside a container. It's easily scrollable, and scrollbar does show scrolling for it. Problem is a position only. I underlined with red the position where RawScrollBar just stucks, but I need it to take question mark position instead.

Here's my short main code, I'll try to provide more if necessary:

Widget build(BuildContext context) {
    return MyScrollbar (
      controller: scrollCtrl,
      child: GetBuilder<...>(
        tag: controller!.tag,
        init: controller!,
        builder: (c) => Container(
          margin: const EdgeInsets.all(24),
          child: Column(
            children: [
              Container(...),
              const SizedBox(height: 24),
              Expanded(
                child: _Body(..., scrollController), //another container inside this Body, and ListView inside its container
              ),
            ],
          ),
        ),
      ),
    );
  }

MyScrollbar widget:

class MyScrollbar extends StatelessWidget {
  final ScrollController controller;
  final Widget child;

  const MyScrollbar ({
    required this.controller,
    required this.child,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
            margin: const EdgeInsets.only(right: 6),
            child: RawScrollbar(
              controller: controller,
              radius: const Radius.circular(12),
              trackRadius: const Radius.circular(12),
              thumbVisibility: true,
              trackVisibility: true,
              scrollbarOrientation: ScrollbarOrientation.right,
              child: child,
          );
  }
}

Solution

  • You can override the ScrollConfiguration and set scrollBar to false. Then use NotificationListener + AnimatedBuilder or CustomPaint for the scrollBar.

    FIXME: currently I am falling to get the ratio of (MaxScrollExtent/viewPortHeight)

    If I made any update in future, you can find it in gist 46f502e70de001e159b6dcf2e7a0a39a

    class MyListView extends StatefulWidget {
      const MyListView({super.key});
    
      @override
      State<MyListView> createState() => _MyListViewState();
    }
    
    class _MyListViewState extends State<MyListView> {
      final ScrollController controller = ScrollController();
      @override
      void dispose() {
        controller.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Row(
          children: [
            Expanded(
              child: ScrollConfiguration(
                behavior: ScrollBehavior().copyWith(scrollbars: false),
                child: Column(
                  children: [
                    Container(
                      height: 200,
                      width: double.infinity,
                      color: Colors.cyan,
                    ),
                    Expanded(
                      child: ListView.builder(
                        controller: controller,
                        itemCount: 23,
                        itemBuilder: (context, index) => ListTile(
                          tileColor: index.isEven ? Colors.red : Colors.white,
                        ),
                      ),
                    )
                  ],
                ),
              ),
            ),
            const SizedBox(width: 48),
            SizedBox(
              height: double.infinity,
              width: 48,
              child: CustomPaint(
                painter: MyScrollBarPainter(controller),
              ),
            )
          ],
        );
      }
    }
    
    class MyScrollBarPainter extends CustomPainter {
      const MyScrollBarPainter(this.controller) : super(repaint: controller);
      final ScrollController controller;
    
      @override
      void paint(Canvas canvas, Size size) {
        if (controller.hasClients == false) return;
    
        ///FIXME:  currently I am falling to get the ratio of (MaxScrollExtent/viewPortHeight)
        final barHeight = controller.position.maxScrollExtent / 10;
    
        final t = controller.offset / controller.position.maxScrollExtent;
        final double top = lerpDouble(0, size.height - barHeight, t) ?? 1;
        debugPrint("t $t height $barHeight");
    
        final rrect = RRect.fromRectAndRadius(
          Rect.fromLTWH(0, top, 10, barHeight),
          Radius.circular(12),
        );
        canvas.drawRRect(rrect, Paint()..color = Colors.green);
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
    }