-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Steps to reproduce
- Copy the sample below
- With keyboard down arrow, go focus a widget in the last rows
- Try to focus back on the first row widgets using up arrow
or
- Place a focusable widget (eg: button) in a column
- Add a vertical scrollable widget in the column
- In this vertical scrollable widget, add n horizontal scrollable widgets with focusable widgets inside
- With keyboard down arrow, go focus a widget in the last rows
- Try to focus back on the first row widgets using up arrow
Pseudo tree :
Column
-Button
--Vertical scroll
---Horizontal scroll
----Button | Button | Button ...
---Horizontal scroll
----Button | Button | Button ...
....
Expected results
When using up arrow to move focus, if there is any focusable widget inside the vertical scrollable and above the current focused widget, we should scroll up and focus it.
Actual results
The top sticky button is focused because it is closer from focused child in term of absolute distance. There is already a system to handle scrollable when moving focus in the framework but it doesn't take account scrollable axis.
Code sample
Code sample
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
ServicesBinding.instance.keyboard.addHandler((event) {
if (event is KeyDownEvent) {
print('KeyDownEvent: ${event.logicalKey.keyLabel}');
}
return true;
});
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: Scaffold(
body: FocusTraversalGroup(
policy: ReadingOrderTraversalPolicy(
requestFocusCallback:
(node, {alignment, alignmentPolicy, curve, duration}) =>
FocusTraversalPolicy.defaultTraversalRequestFocusCallback(
node,
alignment: alignment,
alignmentPolicy: alignmentPolicy,
curve: curve ?? Curves.easeInOut,
duration: duration ?? Duration(milliseconds: 250),
),
),
child: Column(
spacing: 32,
children: [
SizedBox(
width: double.infinity,
child: FocusFilledButton(debugLabel: 'sticky button'),
),
Expanded(
child: SingleChildScrollView(
child: Column(
spacing: 32,
children: [
for (var i in List.generate(10, (i) => i))
SizedBox(
height: 200,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 10,
itemBuilder: (context, index) => AspectRatio(
aspectRatio: 1,
child: FocusFilledButton(
debugLabel: 'button_${i}_$index',
color: Colors.primaries[i % Colors.primaries.length],
),
),
separatorBuilder: (_, _) => SizedBox(width: 32),
),
),
],
),
),
),
],
),
),
),
);
}
}
class FocusFilledButton extends StatefulWidget {
const FocusFilledButton({super.key, required this.debugLabel, this.color});
final String debugLabel;
final Color? color;
@override
State<FocusFilledButton> createState() => _FocusFilledButtonState();
}
class _FocusFilledButtonState extends State<FocusFilledButton> {
late final FocusNode _focusNode = FocusNode(debugLabel: widget.debugLabel);
bool _isFocused = false;
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FilledButton(
style: FilledButton.styleFrom(
backgroundColor: widget.color,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(32)),
),
onPressed: () {},
onFocusChange: (value) {
setState(() => _isFocused = value);
},
focusNode: _focusNode,
child: Text(_isFocused ? 'Focused' : widget.debugLabel),
);
}
}Screenshots or Video
Actual behavior
Here you can see that after the scroll, when I press up arrow to go back to top, the sticky button takes focus while there are buttons above in the scroll view.
Enregistrement.de.l.ecran.2025-07-28.a.17.02.41.mov
Expected behavior
Now the sticky button only takes focus when the vertical scrollable is at edge.
Enregistrement.de.l.ecran.2025-07-28.a.17.00.50.mov
Logs
Logs
[Paste your logs here]Flutter Doctor output
Doctor output
[✓] Flutter (Channel stable, 3.32.8, on macOS 15.5 24F74 darwin-arm64, locale fr-FR) [588ms]
• Flutter version 3.32.8 on channel stable at /Users/romanojw10/Documents/developpement/flutter_arm
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision edada7c56e (3 days ago), 2025-07-25 14:08:03 +0000
• Engine revision ef0cd00091
• Dart version 3.8.1
• DevTools version 2.45.1
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0) [1 900ms]
• Android SDK at /Users/romanojw10/Library/Android/sdk
• Platform android-36, build-tools 35.0.0
• ANDROID_HOME = /Users/romanojw10/Library/Android/sdk
• Java binary at: /Users/romanojw10/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
This is the JDK bundled with the latest Android Studio installation on this machine.
To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
• Java version OpenJDK Runtime Environment (build 21.0.5+-13047016-b750.29)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 16.4) [1 199ms]
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 16F6
• CocoaPods version 1.16.2
[✗] Chrome - develop for the web (Cannot find Chrome executable at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome) [33ms]
! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.
[✓] Android Studio (version 2024.3) [32ms]
• Android Studio at /Users/romanojw10/Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 21.0.5+-13047016-b750.29)
[✓] IntelliJ IDEA Ultimate Edition (version 2024.3.5) [30ms]
• IntelliJ at /Users/romanojw10/Applications/IntelliJ IDEA Ultimate.app
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
[✓] VS Code (version 1.101.2) [11ms]
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.114.0
[✓] Connected device (2 available) [6,2s]
• sdk google atv64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 14 (API 34) (emulator)
• macOS (desktop) • macos • darwin-arm64 • macOS 15.5 24F74 darwin-arm64
[✓] Network resources [258ms]
• All expected network resources are available.
! Doctor found issues in 1 category.