Skip to content

Commit 16d6f24

Browse files
authored
Show arrow indicator on toolbar (singerdmx#245)
1 parent aba8032 commit 16d6f24

File tree

2 files changed

+127
-13
lines changed

2 files changed

+127
-13
lines changed

lib/src/widgets/toolbar.dart

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:image_picker/image_picker.dart';
66

77
import '../models/documents/attribute.dart';
88
import 'controller.dart';
9+
import 'toolbar/arrow_indicated_button_list.dart';
910
import 'toolbar/clear_format_button.dart';
1011
import 'toolbar/color_button.dart';
1112
import 'toolbar/history_button.dart';
@@ -316,21 +317,9 @@ class _QuillToolbarState extends State<QuillToolbar> {
316317
@override
317318
Widget build(BuildContext context) {
318319
return Container(
319-
padding: const EdgeInsets.symmetric(horizontal: 8),
320320
constraints: BoxConstraints.tightFor(height: widget.preferredSize.height),
321321
color: Theme.of(context).canvasColor,
322-
child: CustomScrollView(
323-
scrollDirection: Axis.horizontal,
324-
slivers: [
325-
SliverFillRemaining(
326-
hasScrollBody: false,
327-
child: Row(
328-
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
329-
children: widget.children,
330-
),
331-
),
332-
],
333-
),
322+
child: ArrowIndicatedButtonList(buttons: widget.children),
334323
);
335324
}
336325
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter/material.dart';
4+
5+
/// Scrollable list with arrow indicators.
6+
///
7+
/// The arrow indicators are automatically hidden if the list is not
8+
/// scrollable in the direction of the respective arrow.
9+
class ArrowIndicatedButtonList extends StatefulWidget {
10+
const ArrowIndicatedButtonList({required this.buttons, Key? key})
11+
: super(key: key);
12+
13+
final List<Widget> buttons;
14+
15+
@override
16+
_ArrowIndicatedButtonListState createState() =>
17+
_ArrowIndicatedButtonListState();
18+
}
19+
20+
class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
21+
with WidgetsBindingObserver {
22+
final ScrollController _controller = ScrollController();
23+
bool _showLeftArrow = false;
24+
bool _showRightArrow = false;
25+
26+
@override
27+
void initState() {
28+
super.initState();
29+
_controller.addListener(_handleScroll);
30+
31+
// Listening to the WidgetsBinding instance is necessary so that we can
32+
// hide the arrows when the window gets a new size and thus the toolbar
33+
// becomes scrollable/unscrollable.
34+
WidgetsBinding.instance!.addObserver(this);
35+
36+
// Workaround to allow the scroll controller attach to our ListView so that
37+
// we can detect if overflow arrows need to be shown on init.
38+
Timer.run(_handleScroll);
39+
}
40+
41+
@override
42+
Widget build(BuildContext context) {
43+
return Row(
44+
children: <Widget>[
45+
_buildLeftArrow(),
46+
_buildScrollableList(),
47+
_buildRightColor(),
48+
],
49+
);
50+
}
51+
52+
@override
53+
void didChangeMetrics() => _handleScroll();
54+
55+
@override
56+
void dispose() {
57+
_controller.dispose();
58+
WidgetsBinding.instance!.removeObserver(this);
59+
super.dispose();
60+
}
61+
62+
void _handleScroll() {
63+
setState(() {
64+
_showLeftArrow =
65+
_controller.position.minScrollExtent != _controller.position.pixels;
66+
_showRightArrow =
67+
_controller.position.maxScrollExtent != _controller.position.pixels;
68+
});
69+
}
70+
71+
Widget _buildLeftArrow() {
72+
return SizedBox(
73+
width: 8,
74+
child: Transform.translate(
75+
// Move the icon a few pixels to center it
76+
offset: const Offset(-5, 0),
77+
child: _showLeftArrow ? const Icon(Icons.arrow_left, size: 18) : null,
78+
),
79+
);
80+
}
81+
82+
Widget _buildScrollableList() {
83+
return Expanded(
84+
child: ScrollConfiguration(
85+
// Remove the glowing effect, as we already have the arrow indicators
86+
behavior: _NoGlowBehavior(),
87+
// The CustomScrollView is necessary so that the children are not
88+
// stretched to the height of the toolbar, https://bit.ly/3uC3bjI
89+
child: CustomScrollView(
90+
scrollDirection: Axis.horizontal,
91+
controller: _controller,
92+
physics: const ClampingScrollPhysics(),
93+
slivers: [
94+
SliverFillRemaining(
95+
hasScrollBody: false,
96+
child: Row(
97+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
98+
children: widget.buttons,
99+
),
100+
)
101+
],
102+
),
103+
),
104+
);
105+
}
106+
107+
Widget _buildRightColor() {
108+
return SizedBox(
109+
width: 8,
110+
child: Transform.translate(
111+
// Move the icon a few pixels to center it
112+
offset: const Offset(-5, 0),
113+
child: _showRightArrow ? const Icon(Icons.arrow_right, size: 18) : null,
114+
),
115+
);
116+
}
117+
}
118+
119+
/// ScrollBehavior without the Material glow effect.
120+
class _NoGlowBehavior extends ScrollBehavior {
121+
@override
122+
Widget buildViewportChrome(BuildContext _, Widget child, AxisDirection __) {
123+
return child;
124+
}
125+
}

0 commit comments

Comments
 (0)