-
Notifications
You must be signed in to change notification settings - Fork 29.4k
Closed
Labels
cp: approvedApproved cherry-pick requestApproved cherry-pick requestcp: reviewCherry-picks in the review queueCherry-picks in the review queue
Description
Issue Link
Target
beta
PR Link
Changelog Description
Fixes SelectionArea/SelectableRegion scrolling jank when a SelectionArea/SelectableRegion is used as a child of a Scrollable like ListView or PageView.
Impacted Users
Flutter Android, and iOS developers using SelectionArea/SelectableRegion
Impact Description
Issue causes visual scrolling jank for users when using a Scrollable like a ListView along with a child SelectionArea/SelectableRegion.
Workaround
No workaround, issue is with internal implementation of SelectableRegion.
Risk
low
Test Coverage
yes
Validation Steps
Run the example below on Android and iOS, with PageView.scrollDirection set to either Axis.vertical to test vertical scrolling, or Axis.horizontal to test horizontal scrolling. Neither should cause any scrolling jank.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
/// Flutter code sample for [PageView].
void main() => runApp(const PageViewExampleApp());
class PageViewExampleApp extends StatelessWidget {
const PageViewExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('PageView Sample')),
body: const PageViewExample(),
),
);
}
}
class PageViewExample extends StatefulWidget {
const PageViewExample({super.key});
@override
State<PageViewExample> createState() => _PageViewExampleState();
}
class _PageViewExampleState extends State<PageViewExample>
with TickerProviderStateMixin {
late PageController _pageViewController;
late TabController _tabController;
int _currentPageIndex = 0;
@override
void initState() {
super.initState();
_pageViewController = PageController();
_tabController = TabController(length: 3, vsync: this);
}
@override
void dispose() {
super.dispose();
_pageViewController.dispose();
_tabController.dispose();
}
@override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
return Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
PageView(
/// Change [PageView.scrollDirection] to [Axis.horizontal],
/// to test that scrolling does not conflict vertically either.
scrollDirection: Axis.vertical,
controller: _pageViewController,
onPageChanged: _handlePageViewChanged,
children: <Widget>[
Center(
child: SelectionArea(child: Text('First Page', style: textTheme.titleLarge)),
),
Center(
child: SelectionArea(child: Text('Second Page', style: textTheme.titleLarge)),
),
Center(
child: SelectionArea(child: Text('Third Page', style: textTheme.titleLarge)),
),
],
),
PageIndicator(
tabController: _tabController,
currentPageIndex: _currentPageIndex,
onUpdateCurrentPageIndex: _updateCurrentPageIndex,
isOnDesktopAndWeb: _isOnDesktopAndWeb,
),
],
);
}
void _handlePageViewChanged(int currentPageIndex) {
if (!_isOnDesktopAndWeb) {
return;
}
_tabController.index = currentPageIndex;
setState(() {
_currentPageIndex = currentPageIndex;
});
}
void _updateCurrentPageIndex(int index) {
_tabController.index = index;
_pageViewController.animateToPage(
index,
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
);
}
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;
}
}
}
/// Page indicator for desktop and web platforms.
///
/// On Desktop and Web, drag gesture for horizontal scrolling in a PageView is disabled by default.
/// You can defined a custom scroll behavior to activate drag gestures,
/// see https://docs.flutter.dev/release/breaking-changes/default-scroll-behavior-drag.
///
/// In this sample, we use a TabPageSelector to navigate between pages,
/// in order to build natural behavior similar to other desktop applications.
class PageIndicator extends StatelessWidget {
const PageIndicator({
super.key,
required this.tabController,
required this.currentPageIndex,
required this.onUpdateCurrentPageIndex,
required this.isOnDesktopAndWeb,
});
final int currentPageIndex;
final TabController tabController;
final void Function(int) onUpdateCurrentPageIndex;
final bool isOnDesktopAndWeb;
@override
Widget build(BuildContext context) {
if (!isOnDesktopAndWeb) {
return const SizedBox.shrink();
}
final ColorScheme colorScheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
splashRadius: 16.0,
padding: EdgeInsets.zero,
onPressed: () {
if (currentPageIndex == 0) {
return;
}
onUpdateCurrentPageIndex(currentPageIndex - 1);
},
icon: const Icon(
Icons.arrow_left_rounded,
size: 32.0,
),
),
TabPageSelector(
controller: tabController,
color: colorScheme.surface,
selectedColor: colorScheme.primary,
),
IconButton(
splashRadius: 16.0,
padding: EdgeInsets.zero,
onPressed: () {
if (currentPageIndex == 2) {
return;
}
onUpdateCurrentPageIndex(currentPageIndex + 1);
},
icon: const Icon(
Icons.arrow_right_rounded,
size: 32.0,
),
),
],
),
);
}
}Metadata
Metadata
Assignees
Labels
cp: approvedApproved cherry-pick requestApproved cherry-pick requestcp: reviewCherry-picks in the review queueCherry-picks in the review queue