diff --git a/examples/api/lib/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0.dart b/examples/api/lib/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0.dart new file mode 100644 index 0000000000..556c884ed6 --- /dev/null +++ b/examples/api/lib/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0.dart @@ -0,0 +1,144 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +/// Flutter code sample for [DraggableScrollableSheet]. + +void main() => runApp(const DraggableScrollableSheetExampleApp()); + +class DraggableScrollableSheetExampleApp extends StatelessWidget { + const DraggableScrollableSheetExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue.shade100), + ), + home: Scaffold( + appBar: AppBar( + title: const Text('DraggableScrollableSheet Sample'), + ), + body: const DraggableScrollableSheetExample(), + ), + ); + } +} + +class DraggableScrollableSheetExample extends StatefulWidget { + const DraggableScrollableSheetExample({super.key}); + + @override + State createState() => _DraggableScrollableSheetExampleState(); +} + +class _DraggableScrollableSheetExampleState extends State { + double _sheetPosition = 0.5; + final double _dragSensitivity = 600; + + @override + Widget build(BuildContext context) { + final ColorScheme colorScheme = Theme.of(context).colorScheme; + + return DraggableScrollableSheet( + initialChildSize: _sheetPosition, + builder: (BuildContext context, ScrollController scrollController) { + return ColoredBox( + color: colorScheme.primary, + child: Column( + children: [ + Grabber( + onVerticalDragUpdate: (DragUpdateDetails details) { + setState(() { + _sheetPosition -= details.delta.dy / _dragSensitivity; + if (_sheetPosition < 0.25) { + _sheetPosition = 0.25; + } + if (_sheetPosition > 1.0) { + _sheetPosition = 1.0; + } + }); + }, + isOnDesktopAndWeb: _isOnDesktopAndWeb, + ), + Flexible( + child: ListView.builder( + controller: _isOnDesktopAndWeb ? null : scrollController, + itemCount: 25, + itemBuilder: (BuildContext context, int index) { + return ListTile( + title: Text( + 'Item $index', + style: TextStyle(color: colorScheme.surface), + ), + ); + }, + ), + ), + ], + ), + ); + }, + ); + } + + bool get _isOnDesktopAndWeb { + if (kIsWeb) { + return true; + } + switch (defaultTargetPlatform) { + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + return true; + case TargetPlatform.android: + case TargetPlatform.iOS: + case TargetPlatform.fuchsia: + return false; + } + } +} + +/// A draggable widget that accepts vertical drag gestures +/// and this is only visible on desktop and web platforms. +class Grabber extends StatelessWidget { + const Grabber({ + super.key, + required this.onVerticalDragUpdate, + required this.isOnDesktopAndWeb, + }); + + final ValueChanged onVerticalDragUpdate; + final bool isOnDesktopAndWeb; + + @override + Widget build(BuildContext context) { + if (!isOnDesktopAndWeb) { + return const SizedBox.shrink(); + } + final ColorScheme colorScheme = Theme.of(context).colorScheme; + + return GestureDetector( + onVerticalDragUpdate: onVerticalDragUpdate, + child: Container( + width: double.infinity, + color: colorScheme.onSurface, + child: Align( + alignment: Alignment.topCenter, + child: Container( + margin: const EdgeInsets.symmetric(vertical: 8.0), + width: 32.0, + height: 4.0, + decoration: BoxDecoration( + color: colorScheme.surfaceVariant, + borderRadius: BorderRadius.circular(8.0), + ), + ), + ), + ), + ); + } +} diff --git a/examples/api/test/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0_test.dart b/examples/api/test/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0_test.dart new file mode 100644 index 0000000000..da365dc6cb --- /dev/null +++ b/examples/api/test/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0_test.dart @@ -0,0 +1,63 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Test DraggableScrollableSheet initial state', (WidgetTester tester) async { + await tester.pumpWidget( + const example.DraggableScrollableSheetExampleApp(), + ); + + final Finder sheetFinder = find.byType(DraggableScrollableSheet); + + // Verify that DraggableScrollableSheet is initially present + expect(sheetFinder, findsOneWidget); + + // Verify that DraggableScrollableSheet is shown initially at 50% height + final DraggableScrollableSheet draggableSheet = tester.widget(sheetFinder); + expect(draggableSheet.initialChildSize, 0.5); + }); + + testWidgets('Test DraggableScrollableSheet drag behavior on mobile platforms', (WidgetTester tester) async { + await tester.pumpWidget( + const example.DraggableScrollableSheetExampleApp(), + ); + + // Verify that ListView is visible + final Finder listViewFinder = find.byType(ListView); + expect(listViewFinder, findsOneWidget); + + // Get the initial size of the ListView + final Size listViewInitialSize = tester.getSize(listViewFinder); + + // Drag the the sheet from anywhere inside the sheet to change the sheet position + await tester.drag(listViewFinder, const Offset(0.0, -100.0)); + await tester.pump(); + + // Verify that the ListView is expanded + final Size listViewCurrentSize = tester.getSize(listViewFinder); + expect(listViewCurrentSize.height, greaterThan(listViewInitialSize.height)); + }, variant: TargetPlatformVariant.mobile()); + + testWidgets('Test DraggableScrollableSheet drag behavior on desktop platforms', (WidgetTester tester) async { + await tester.pumpWidget( + const example.DraggableScrollableSheetExampleApp(), + ); + + // Verify that Grabber is visible + final Finder grabberFinder = find.byType(example.Grabber); + expect(grabberFinder, findsOneWidget); + + // Drag the Grabber to change the sheet position + await tester.drag(grabberFinder, const Offset(0.0, -100.0)); + await tester.pump(); + + // Verify that the DraggableScrollableSheet's initialChildSize is updated + final DraggableScrollableSheet draggableSheet = tester.widget(find.byType(DraggableScrollableSheet)); + expect(draggableSheet.initialChildSize, isNot(0.5)); + }, variant: TargetPlatformVariant.desktop()); +} diff --git a/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart b/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart index 9c7f80b51a..b4543d2346 100644 --- a/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart +++ b/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart @@ -260,7 +260,7 @@ class DraggableScrollableController extends ChangeNotifier { /// to position sheet based on the space it is taking, the [expand] property /// may be set to false. /// -/// {@tool snippet} +/// {@tool dartpad} /// /// This is a sample widget which shows a [ListView] that has 25 [ListTile]s. /// It starts out as taking up half the body of the [Scaffold], and can be @@ -269,36 +269,16 @@ class DraggableScrollableController extends ChangeNotifier { /// scrolled up or down, until they reach the top of the list again and the user /// drags the sheet back down. /// -/// ```dart -/// class HomePage extends StatelessWidget { -/// const HomePage({super.key}); +/// On desktop and web running on desktop platforms, dragging to scroll with a mouse is disabled by default +/// to align with the natural behavior found in other desktop applications. /// -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// appBar: AppBar( -/// title: const Text('DraggableScrollableSheet'), -/// ), -/// body: SizedBox.expand( -/// child: DraggableScrollableSheet( -/// builder: (BuildContext context, ScrollController scrollController) { -/// return Container( -/// color: Colors.blue[100], -/// child: ListView.builder( -/// controller: scrollController, -/// itemCount: 25, -/// itemBuilder: (BuildContext context, int index) { -/// return ListTile(title: Text('Item $index')); -/// }, -/// ), -/// ); -/// }, -/// ), -/// ), -/// ); -/// } -/// } -/// ``` +/// This behavior is dictated by the [ScrollBehavior], and can be changed by adding +/// [PointerDeviceKind.mouse] to [ScrollBehavior.dragDevices]. +/// For more info on this, please refer to https://docs.flutter.dev/release/breaking-changes/default-scroll-behavior-drag +/// +/// Alternatively, this example illustrates how to add a drag handle for desktop applications. +/// +/// ** See code in examples/api/lib/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0.dart ** /// {@end-tool} class DraggableScrollableSheet extends StatefulWidget { /// Creates a widget that can be dragged and scrolled in a single gesture.