Unverified Commit 2003432c authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

Drawer edge drag width improvements (#37492)

* Added customizable drawer edge drag width parameter to Drawer and Scaffold

* Fix Drawer drag area width for notched devices

* Update Drawer tests to reflect necessary LTR and RTL Drawer edge widths
parent 232dce96
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:math';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/gestures.dart' show DragStartBehavior;
...@@ -182,6 +180,7 @@ class DrawerController extends StatefulWidget { ...@@ -182,6 +180,7 @@ class DrawerController extends StatefulWidget {
this.drawerCallback, this.drawerCallback,
this.dragStartBehavior = DragStartBehavior.start, this.dragStartBehavior = DragStartBehavior.start,
this.scrimColor, this.scrimColor,
this.edgeDragWidth,
}) : assert(child != null), }) : assert(child != null),
assert(dragStartBehavior != null), assert(dragStartBehavior != null),
assert(alignment != null), assert(alignment != null),
...@@ -228,6 +227,17 @@ class DrawerController extends StatefulWidget { ...@@ -228,6 +227,17 @@ class DrawerController extends StatefulWidget {
/// By default, the color used is [Colors.black54] /// By default, the color used is [Colors.black54]
final Color scrimColor; final Color scrimColor;
/// The width of the area within which a horizontal swipe will open the
/// drawer.
///
/// By default, the value used is 20.0 added to the padding edge of
/// `MediaQuery.of(context).padding` that corresponds to [alignment].
/// This ensures that the drag area for notched devices is not obscured. For
/// example, if [alignment] is set to [DrawerAlignment.start] and
/// `TextDirection.of(context)` is set to [TextDirection.ltr],
/// 20.0 will be added to `MediaQuery.of(context).padding.left`.
final double edgeDragWidth;
@override @override
DrawerControllerState createState() => DrawerControllerState(); DrawerControllerState createState() => DrawerControllerState();
} }
...@@ -427,12 +437,24 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro ...@@ -427,12 +437,24 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro
Widget _buildDrawer(BuildContext context) { Widget _buildDrawer(BuildContext context) {
final bool drawerIsStart = widget.alignment == DrawerAlignment.start; final bool drawerIsStart = widget.alignment == DrawerAlignment.start;
final EdgeInsets padding = MediaQuery.of(context).padding; final EdgeInsets padding = MediaQuery.of(context).padding;
double dragAreaWidth = drawerIsStart ? padding.left : padding.right; final TextDirection textDirection = Directionality.of(context);
if (Directionality.of(context) == TextDirection.rtl) double dragAreaWidth = widget.edgeDragWidth;
dragAreaWidth = drawerIsStart ? padding.right : padding.left; if (widget.edgeDragWidth == null) {
switch (textDirection) {
case TextDirection.ltr: {
dragAreaWidth = _kEdgeDragWidth +
(drawerIsStart ? padding.left : padding.right);
}
break;
case TextDirection.rtl: {
dragAreaWidth = _kEdgeDragWidth +
(drawerIsStart ? padding.right : padding.left);
}
break;
}
}
dragAreaWidth = max(dragAreaWidth, _kEdgeDragWidth);
if (_controller.status == AnimationStatus.dismissed) { if (_controller.status == AnimationStatus.dismissed) {
return Align( return Align(
alignment: _drawerOuterAlignment, alignment: _drawerOuterAlignment,
......
...@@ -968,6 +968,7 @@ class Scaffold extends StatefulWidget { ...@@ -968,6 +968,7 @@ class Scaffold extends StatefulWidget {
this.drawerDragStartBehavior = DragStartBehavior.start, this.drawerDragStartBehavior = DragStartBehavior.start,
this.extendBody = false, this.extendBody = false,
this.drawerScrimColor, this.drawerScrimColor,
this.drawerEdgeDragWidth,
}) : assert(primary != null), }) : assert(primary != null),
assert(extendBody != null), assert(extendBody != null),
assert(drawerDragStartBehavior != null), assert(drawerDragStartBehavior != null),
...@@ -1140,6 +1141,16 @@ class Scaffold extends StatefulWidget { ...@@ -1140,6 +1141,16 @@ class Scaffold extends StatefulWidget {
/// {@macro flutter.material.drawer.dragStartBehavior} /// {@macro flutter.material.drawer.dragStartBehavior}
final DragStartBehavior drawerDragStartBehavior; final DragStartBehavior drawerDragStartBehavior;
/// The width of the area within which a horizontal swipe will open the
/// drawer.
///
/// By default, the value used is 20.0 added to the padding edge of
/// `MediaQuery.of(context).padding` that corresponds to [alignment].
/// This ensures that the drag area for notched devices is not obscured. For
/// example, if `TextDirection.of(context)` is set to [TextDirection.ltr],
/// 20.0 will be added to `MediaQuery.of(context).padding.left`.
final double drawerEdgeDragWidth;
/// The state from the closest instance of this class that encloses the given context. /// The state from the closest instance of this class that encloses the given context.
/// ///
/// {@tool snippet --template=freeform} /// {@tool snippet --template=freeform}
...@@ -1984,6 +1995,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -1984,6 +1995,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
drawerCallback: _endDrawerOpenedCallback, drawerCallback: _endDrawerOpenedCallback,
dragStartBehavior: widget.drawerDragStartBehavior, dragStartBehavior: widget.drawerDragStartBehavior,
scrimColor: widget.drawerScrimColor, scrimColor: widget.drawerScrimColor,
edgeDragWidth: widget.drawerEdgeDragWidth,
), ),
_ScaffoldSlot.endDrawer, _ScaffoldSlot.endDrawer,
// remove the side padding from the side we're not touching // remove the side padding from the side we're not touching
...@@ -2007,6 +2019,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -2007,6 +2019,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
drawerCallback: _drawerOpenedCallback, drawerCallback: _drawerOpenedCallback,
dragStartBehavior: widget.drawerDragStartBehavior, dragStartBehavior: widget.drawerDragStartBehavior,
scrimColor: widget.drawerScrimColor, scrimColor: widget.drawerScrimColor,
edgeDragWidth: widget.drawerEdgeDragWidth,
), ),
_ScaffoldSlot.drawer, _ScaffoldSlot.drawer,
// remove the side padding from the side we're not touching // remove the side padding from the side we're not touching
......
...@@ -1345,16 +1345,15 @@ void main() { ...@@ -1345,16 +1345,15 @@ void main() {
expect(find.text('drawer'), findsOneWidget); expect(find.text('drawer'), findsOneWidget);
}); });
testWidgets('Drawer opens correctly with padding from MediaQuery', (WidgetTester tester) async { testWidgets('Drawer opens correctly with padding from MediaQuery (LTR)', (WidgetTester tester) async {
// The padding described by MediaQuery is larger than the default const double simulatedNotchSize = 40.0;
// drawer drag zone width which is 20.
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Scaffold( home: Scaffold(
drawer: const Drawer( drawer: const Drawer(
child: Text('drawer'), child: Text('Drawer'),
), ),
body: const Text('scaffold body'), body: const Text('Scaffold Body'),
appBar: AppBar( appBar: AppBar(
centerTitle: true, centerTitle: true,
title: const Text('Title'), title: const Text('Title'),
...@@ -1364,25 +1363,23 @@ void main() { ...@@ -1364,25 +1363,23 @@ void main() {
); );
ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));
expect(scaffoldState.isDrawerOpen, false); expect(scaffoldState.isDrawerOpen, false);
await tester.dragFrom(const Offset(35, 100), const Offset(300, 0)); await tester.dragFrom(const Offset(simulatedNotchSize + 15.0, 100), const Offset(300, 0));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(scaffoldState.isDrawerOpen, false); expect(scaffoldState.isDrawerOpen, false);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: MediaQuery( home: MediaQuery(
data: const MediaQueryData( data: const MediaQueryData(
padding: EdgeInsets.fromLTRB(40, 0, 0, 0) padding: EdgeInsets.fromLTRB(simulatedNotchSize, 0, 0, 0),
), ),
child: Scaffold( child: Scaffold(
drawer: const Drawer( drawer: const Drawer(
child: Text('drawer'), child: Text('Drawer'),
), ),
body: const Text('scaffold body'), body: const Text('Scaffold Body'),
appBar: AppBar( appBar: AppBar(
centerTitle: true, centerTitle: true,
title: const Text('Title'), title: const Text('Title'),
...@@ -1391,33 +1388,60 @@ void main() { ...@@ -1391,33 +1388,60 @@ void main() {
), ),
), ),
); );
scaffoldState = tester.state(find.byType(Scaffold)); scaffoldState = tester.state(find.byType(Scaffold));
expect(scaffoldState.isDrawerOpen, false); expect(scaffoldState.isDrawerOpen, false);
await tester.dragFrom(const Offset(35, 100), const Offset(300, 0)); await tester.dragFrom(
const Offset(simulatedNotchSize + 15.0, 100),
const Offset(300, 0),
);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(scaffoldState.isDrawerOpen, true); expect(scaffoldState.isDrawerOpen, true);
}); });
testWidgets('Drawer opens correctly with padding from MediaQuer (RTL)', (WidgetTester tester) async { testWidgets('Drawer opens correctly with padding from MediaQuery (RTL)', (WidgetTester tester) async {
// The padding described by MediaQuery is larger than the default const double simulatedNotchSize = 40.0;
// drawer drag zone width which is 20. await tester.pumpWidget(
MaterialApp(
home: Scaffold(
drawer: const Drawer(
child: Text('Drawer'),
),
body: const Text('Scaffold Body'),
appBar: AppBar(
centerTitle: true,
title: const Text('Title'),
),
),
),
);
final double scaffoldWidth = tester.renderObject<RenderBox>(
find.byType(Scaffold),
).size.width;
ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));
expect(scaffoldState.isDrawerOpen, false);
await tester.dragFrom(
Offset(scaffoldWidth - simulatedNotchSize - 15.0, 100),
const Offset(-300, 0),
);
await tester.pumpAndSettle();
expect(scaffoldState.isDrawerOpen, false);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: MediaQuery( home: MediaQuery(
data: const MediaQueryData( data: const MediaQueryData(
padding: EdgeInsets.fromLTRB(0, 0, 40, 0) padding: EdgeInsets.fromLTRB(0, 0, simulatedNotchSize, 0),
), ),
child: Directionality( child: Directionality(
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
child: Scaffold( child: Scaffold(
drawer: const Drawer( drawer: const Drawer(
child: Text('drawer'), child: Text('Drawer'),
), ),
body: const Text('scaffold body'), body: const Text('Scaffold body'),
appBar: AppBar( appBar: AppBar(
centerTitle: true, centerTitle: true,
title: const Text('Title'), title: const Text('Title'),
...@@ -1427,21 +1451,66 @@ void main() { ...@@ -1427,21 +1451,66 @@ void main() {
), ),
), ),
); );
scaffoldState = tester.state(find.byType(Scaffold));
expect(scaffoldState.isDrawerOpen, false);
final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); await tester.dragFrom(
Offset(scaffoldWidth - simulatedNotchSize - 15.0, 100),
const Offset(-300, 0),
);
await tester.pumpAndSettle();
expect(scaffoldState.isDrawerOpen, true);
});
});
testWidgets('Drawer opens correctly with custom edgeDragWidth', (WidgetTester tester) async {
// The default edge drag width is 20.0.
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
drawer: const Drawer(
child: Text('Drawer'),
),
body: const Text('Scaffold body'),
appBar: AppBar(
centerTitle: true,
title: const Text('Title'),
),
),
),
);
ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));
expect(scaffoldState.isDrawerOpen, false); expect(scaffoldState.isDrawerOpen, false);
await tester.dragFrom(const Offset(765, 100), const Offset(-300, 0)); await tester.dragFrom(const Offset(35, 100), const Offset(300, 0));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(scaffoldState.isDrawerOpen, false);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
drawer: const Drawer(
child: Text('Drawer'),
),
drawerEdgeDragWidth: 40.0,
body: const Text('Scaffold Body'),
appBar: AppBar(
centerTitle: true,
title: const Text('Title'),
),
),
),
);
scaffoldState = tester.state(find.byType(Scaffold));
expect(scaffoldState.isDrawerOpen, false);
await tester.dragFrom(const Offset(35, 100), const Offset(300, 0));
await tester.pumpAndSettle();
expect(scaffoldState.isDrawerOpen, true); expect(scaffoldState.isDrawerOpen, true);
}); });
});
testWidgets('Nested scaffold body insets', (WidgetTester tester) async { testWidgets('Nested scaffold body insets', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/20295 // Regression test for https://github.com/flutter/flutter/issues/20295
final Key bodyKey = UniqueKey(); final Key bodyKey = UniqueKey();
Widget buildFrame(bool innerResizeToAvoidBottomInset, bool outerResizeToAvoidBottomInset) { Widget buildFrame(bool innerResizeToAvoidBottomInset, bool outerResizeToAvoidBottomInset) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment