I have 2 issues/queries about achieving a particular effect.
I want to achieve this effect, and i have implemented a solution (Two to be honest) but both of them don't work. details are as follows:
Here is the code for the first solution I implemented, here you can see both the scrollbars, but when horizontal one is scrolled(Not by trackpad, as trackpad horizontal scrolling doesn't work), the vertical gets scrolled away too.
class ScrollPanWidget extends StatefulWidget {
final Widget child;
const ScrollPanWidget({Key? key, required this.child}) : super(key: key);
@override
State<ScrollPanWidget> createState() => _ScrollPanWidgetState();
}
class _ScrollPanWidgetState extends State<ScrollPanWidget> {
late ScrollController verticalController, horizontalController;
@override
void initState() {
super.initState();
verticalController = ScrollController();
horizontalController = ScrollController();
}
@override
Widget build(BuildContext context) {
return _HorizontalPart(
horizontalController: horizontalController,
child: _VerticalPart(
verticalController: verticalController, child: widget.child),
);
}
}
class _HorizontalPart extends StatelessWidget {
final ScrollController horizontalController;
final Widget child;
const _HorizontalPart(
{Key? key, required this.horizontalController, required this.child})
: super(key: key);
@override
Widget build(BuildContext context) {
return RawScrollbar(
thumbVisibility: true,
trackColor: Colors.grey,
thumbColor: Theme.of(context).colorScheme.primary.withOpacity(0.8),
trackBorderColor: Colors.blueGrey,
controller: horizontalController,
scrollbarOrientation: ScrollbarOrientation.top,
child: SingleChildScrollView(
controller: horizontalController,
scrollDirection: Axis.horizontal,
child: child,
),
);
}
}
class _VerticalPart extends StatelessWidget {
final ScrollController verticalController;
final Widget child;
const _VerticalPart(
{Key? key, required this.verticalController, required this.child})
: super(key: key);
@override
Widget build(BuildContext context) {
return RawScrollbar(
thumbVisibility: true,
trackColor: Colors.grey,
thumbColor: Theme.of(context).colorScheme.primary.withOpacity(0.8),
trackBorderColor: Colors.blueGrey,
scrollbarOrientation: ScrollbarOrientation.left,
controller: verticalController,
child: SingleChildScrollView(
controller: verticalController,
scrollDirection: Axis.vertical,
child: child,
),
);
}
}
Stack(
children: [
SingleChildScrollView(
controller: verticalController,
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
controller: horizontalController,
scrollDirection: Axis.horizontal,
child: widget.child,
),
),
RawScrollbar(
thumbVisibility: true,
trackColor: Colors.grey,
thumbColor: Theme.of(context).colorScheme.primary.withOpacity(0.8),
trackBorderColor: Colors.blueGrey,
controller: verticalController,
scrollbarOrientation: ScrollbarOrientation.right,
child: RawScrollbar(
thumbVisibility: true,
trackColor: Colors.grey,
thumbColor: Theme.of(context).colorScheme.primary.withOpacity(0.8),
trackBorderColor: Colors.blueGrey,
scrollbarOrientation: ScrollbarOrientation.bottom,
controller: horizontalController,
child: Column(
mainAxisSize: MainAxisSize.max,
children: const [],
),
),
),
],
);
If my solution can't work, can you point to a direction in which I should work for achieving the same (not asking for actual code, hence this is not a broad question as per guidelines).
On windows, when scrolling horizontally, Flutter changes focus of the widgets instead of passing the scroll events to Horizontal ScrollViews. Is there anything which can be done in this respect? This behavior is not observed on Flutter Web.
Also, I do want to make the widget respond to touch and mouse inputs for panning(mouse inputs aren't necessary as they can be ignored in favour of scrolling widget acc to Point no 2). So do you recommend using a combination of GestureDetector
and Stack
with child widget being put in a positioned widget for panning? Or is it better to use the GestureDetector and ScrollController to pan the child Widget?
Thank you for the help in advance!
Full Code:
import 'package:flutter/material.dart';
class NestedScrollbarsNotShowingApp extends StatefulWidget {
const NestedScrollbarsNotShowingApp({Key? key}) : super(key: key);
@override
State<NestedScrollbarsNotShowingApp> createState() =>
_NestedScrollbarsNotShowingAppState();
}
class _NestedScrollbarsNotShowingAppState
extends State<NestedScrollbarsNotShowingApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Scaffold(
appBar: AppBar(
title: const Text('Classic Material App Bar'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: ScrollPanWidget(
child: Container(
color: Colors.red,
height: 1200,
width: 1500,
),
),
)),
),
);
}
}
class ScrollPanWidget extends StatefulWidget {
final Widget child;
const ScrollPanWidget({Key? key, required this.child}) : super(key: key);
@override
State<ScrollPanWidget> createState() => _ScrollPanWidgetState();
}
class _ScrollPanWidgetState extends State<ScrollPanWidget> {
late ScrollController verticalController, horizontalController;
@override
void initState() {
super.initState();
verticalController = ScrollController();
horizontalController = ScrollController();
}
@override
Widget build(BuildContext context) {
return _HorizontalPart(
horizontalController: horizontalController,
child: _VerticalPart(
verticalController: verticalController, child: widget.child),
);
}
solutionThatDoesntWork() {
return Stack(
children: [
SingleChildScrollView(
controller: verticalController,
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
controller: horizontalController,
scrollDirection: Axis.horizontal,
child: widget.child,
),
),
RawScrollbar(
thumbVisibility: true,
trackColor: Colors.grey,
thumbColor: Theme.of(context).colorScheme.primary.withOpacity(0.8),
trackBorderColor: Colors.blueGrey,
controller: verticalController,
scrollbarOrientation: ScrollbarOrientation.right,
child: RawScrollbar(
thumbVisibility: true,
trackColor: Colors.grey,
thumbColor: Theme.of(context).colorScheme.primary.withOpacity(0.8),
trackBorderColor: Colors.blueGrey,
scrollbarOrientation: ScrollbarOrientation.bottom,
controller: horizontalController,
child: Column(
mainAxisSize: MainAxisSize.max,
children: const [],
),
),
),
],
);
}
}
class _HorizontalPart extends StatelessWidget {
final ScrollController horizontalController;
final Widget child;
const _HorizontalPart(
{Key? key, required this.horizontalController, required this.child})
: super(key: key);
@override
Widget build(BuildContext context) {
return RawScrollbar(
thumbVisibility: true,
trackColor: Colors.grey,
thumbColor: Theme.of(context).colorScheme.primary.withOpacity(0.8),
trackBorderColor: Colors.blueGrey,
controller: horizontalController,
scrollbarOrientation: ScrollbarOrientation.top,
child: SingleChildScrollView(
controller: horizontalController,
scrollDirection: Axis.horizontal,
child: child,
),
);
}
}
class _VerticalPart extends StatelessWidget {
final ScrollController verticalController;
final Widget child;
const _VerticalPart(
{Key? key, required this.verticalController, required this.child})
: super(key: key);
@override
Widget build(BuildContext context) {
return RawScrollbar(
thumbVisibility: true,
trackColor: Colors.grey,
thumbColor: Theme.of(context).colorScheme.primary.withOpacity(0.8),
trackBorderColor: Colors.blueGrey,
scrollbarOrientation: ScrollbarOrientation.left,
controller: verticalController,
child: SingleChildScrollView(
controller: verticalController,
scrollDirection: Axis.vertical,
child: child,
),
);
}
}
So, I actually implemented the solution myself and have published it as a pub package, dual_scroll. You can directly use it in the following way:
Start by importing the package
import 'package:dual_scroll/dual_scroll.dart';
Use by wrapping the Widget You want to be scrollable in the following way:
return DualScroll(
verticalScrollbar: ScrollBar.defaultScrollBar(),
horizontalScrollbar: ScrollBar.defaultScrollBar(),
child: Container(), /* Your child widget here*/
);
For advanced usage, see pub.dev