Movatterモバイル変換


[0]ホーム

URL:


Skip to main content

docs.flutter.dev uses cookies from Google to deliver and enhance the quality of its services and to analyze traffic.

Learn more

Flutter 3.41 is live! Check out theFlutter 3.41 blog post!

User input & accessibility

A truly adaptive app also handles differences in how user input works and also programs to help folks with accessibility issues.

It isn't enough to just adapt how your app looks, you also have to support a variety of user inputs. The mouse and keyboard introduce input types beyond those found on a touch device, like scroll wheel, right-click, hover interactions, tab traversal, and keyboard shortcuts.

Some of these features work by default on Material widgets. But, if you've created a custom widget, you might need to implement them directly.

Some features that encompass a well-designed app, also help users who work with assistive technologies. For example, aside from beinggood app design, some features, like tab traversal and keyboard shortcuts, arecritical for users who work with assistive devices. In addition to the standard advice forcreating accessible apps, this page covers info for creating apps that are both adaptiveand accessible.

Scroll wheel for custom widgets

#

Scrolling widgets likeScrollView orListView support the scroll wheel by default, and because almost every scrollable custom widget is built using one of these, it works with those as well.

If you need to implement custom scroll behavior, you can use theListener widget, which lets you customize how your UI reacts to the scroll wheel.

dart
returnListener(onPointerSignal:(event){if(eventisPointerScrollEvent)print(event.scrollDelta.dy);},child:ListView(),);

Tab traversal and focus interactions

#

Users with physical keyboards expect that they can use the tab key to quickly navigate an application, and users with motor or vision differences often rely completely on keyboard navigation.

There are two considerations for tab interactions: how focus moves from widget to widget, known as traversal, and the visual highlight shown when a widget is focused.

Most built-in components, like buttons and text fields, support traversal and highlights by default. If you have your own widget that you want included in traversal, you can use theFocusableActionDetector widget to create your own controls. TheFocusableActionDetector widget is helpful for combining focus, mouse input, and shortcuts together in one widget. You can create a detector that defines actions and key bindings, and provides callbacks for handling focus and hover highlights.

dart
class_BasicActionDetectorStateextendsState<BasicActionDetector>{bool_hasFocus=false;@overrideWidgetbuild(BuildContextcontext){returnFocusableActionDetector(onFocusChange:(value)=>setState(()=>_hasFocus=value),actions:<Type,Action<Intent>>{ActivateIntent:CallbackAction<Intent>(onInvoke:(intent){print('Enter or Space was pressed!');returnnull;},),},child:Stack(clipBehavior:Clip.none,children:[constFlutterLogo(size:100),// Position focus in the negative margin for a cool effectif(_hasFocus)Positioned(left:-4,top:-4,bottom:-4,right:-4,child:_roundedBorder(),),],),);}}

Controlling traversal order

#

To get more control over the order that widgets are focused on when the user tabs through, you can useFocusTraversalGroup to define sections of the tree that should be treated as a group when tabbing.

For example, you might to tab through all the fields in a form before tabbing to the submit button:

dart
returnColumn(children:[FocusTraversalGroup(child:MyFormWithMultipleColumnsAndRows()),SubmitButton(),],);

Flutter has several built-in ways to traverse widgets and groups, defaulting to theReadingOrderTraversalPolicy class. This class usually works well, but it's possible to modify this using another predefinedTraversalPolicy class or by creating a custom policy.

Keyboard accelerators

#

In addition to tab traversal, desktop and web users are accustomed to having various keyboard shortcuts bound to specific actions. Whether it's theDelete key for quick deletions orControl+N for a new document, be sure to consider the different accelerators your users expect. The keyboard is a powerful input tool, so try to squeeze as much efficiency from it as you can. Your users will appreciate it!

Keyboard accelerators can be accomplished in a few ways in Flutter, depending on your goals.

If you have a single widget like aTextField or aButton that already has a focus node, you can wrap it in aKeyboardListener or aFocus widget and listen for keyboard events:

dart
@overrideWidgetbuild(BuildContextcontext){returnFocus(onKeyEvent:(node,event){if(eventisKeyDownEvent){print(event.logicalKey);}returnKeyEventResult.ignored;},child:ConstrainedBox(constraints:constBoxConstraints(maxWidth:400),child:constTextField(decoration:InputDecoration(border:OutlineInputBorder()),),),);}}

To apply a set of keyboard shortcuts to a large section of the tree, use theShortcuts widget:

dart
// Define a class for each type of shortcut action you wantclassCreateNewItemIntentextendsIntent{constCreateNewItemIntent();}Widgetbuild(BuildContextcontext){returnShortcuts(// Bind intents to key combinationsshortcuts:const<ShortcutActivator,Intent>{SingleActivator(LogicalKeyboardKey.keyN,control:true):CreateNewItemIntent(),},child:Actions(// Bind intents to an actual method in your codeactions:<Type,Action<Intent>>{CreateNewItemIntent:CallbackAction<CreateNewItemIntent>(onInvoke:(intent)=>_createNewItem(),),},// Your sub-tree must be wrapped in a focusNode, so it can take focus.child:Focus(autofocus:true,child:Container()),),);}

TheShortcuts widget is useful because it only allows shortcuts to be fired when this widget tree or one of its children has focus and is visible.

The final option is a global listener. This listener can be used for always-on, app-wide shortcuts or for panels that can accept shortcuts whenever they're visible (regardless of their focus state). Adding global listeners is easy withHardwareKeyboard:

dart
@overridevoidinitState(){super.initState();HardwareKeyboard.instance.addHandler(_handleKey);}@overridevoiddispose(){HardwareKeyboard.instance.removeHandler(_handleKey);super.dispose();}

To check key combinations with the global listener, you can use theHardwareKeyboard.instance.logicalKeysPressed set. For example, a method like the following can check whether any of the provided keys are being held down:

dart
staticboolisKeyDown(Set<LogicalKeyboardKey>keys){returnkeys.intersection(HardwareKeyboard.instance.logicalKeysPressed).isNotEmpty;}

Putting these two things together, you can fire an action whenShift+N is pressed:

dart
bool_handleKey(KeyEventevent){boolisShiftDown=isKeyDown({LogicalKeyboardKey.shiftLeft,LogicalKeyboardKey.shiftRight,});if(isShiftDown&&event.logicalKey==LogicalKeyboardKey.keyN){_createNewItem();returntrue;}returnfalse;}

One note of caution when using the static listener, is that you often need to disable it when the user is typing in a field or when the widget it's associated with is hidden from view. Unlike withShortcuts orKeyboardListener, this is your responsibility to manage. This can be especially important when you're binding a Delete/Backspace accelerator forDelete, but then have childTextFields that the user might be typing in.

Mouse enter, exit, and hover for custom widgets

#

On desktop, it's common to change the mouse cursor to indicate the functionality about the content the mouse is hovering over. For example, you typically see a hand cursor when you hover over a button, or anI cursor when you hover over text.

Flutter's Material buttons handle basic focus states for standard button and text cursors. (A notable exception is if you change the default styling of the Material buttons to set theoverlayColor to transparent.)

Implement a focus state for any custom buttons or gesture detectors in your app. If you change the default Material button styles, test for keyboard focus states and implement your own, if needed.

To change the cursor from within your custom widgets, useMouseRegion:

dart
// Show hand cursorreturnMouseRegion(cursor:SystemMouseCursors.click,// Request focus when clickedchild:GestureDetector(onTap:(){Focus.of(context).requestFocus();_submit();},child:Logo(showBorder:hasFocus),),);

MouseRegion is also useful for creating custom rollover and hover effects:

dart
returnMouseRegion(onEnter:(_)=>setState(()=>_isMouseOver=true),onExit:(_)=>setState(()=>_isMouseOver=false),onHover:(e)=>print(e.localPosition),child:Container(height:500,color:_isMouseOver?Colors.blue:Colors.black,),);

For an example that changes the button style to outline the button when it has focus, check out thebutton code for the Wonderous app. The app modifies theFocusNode.hasFocus property to check whether the button has focus and, if so, adds an outline.

Visual density

#

You might consider enlarging the "hit area" of a widget to accommodate a touch screen, for example.

Different input devices offer various levels of precision, which necessitate differently-sized hit areas. Flutter'sVisualDensity class makes it easy to adjust the density of your views across the entire application, for example, by making a button larger (and therefore easier to tap) on a touch device.

When you change theVisualDensity for yourMaterialApp,MaterialComponents that support it animate their densities to match. By default, both horizontal and vertical densities are set to 0.0, but you can set the densities to any negative or positive value that you want. By switching between different densities, you can easily adjust your UI.

Adaptive scaffold

To set a custom visual density, inject the density into yourMaterialApp theme:

dart
doubledensityAmt=touchMode?0.0:-1.0;VisualDensitydensity=VisualDensity(horizontal:densityAmt,vertical:densityAmt,);returnMaterialApp(theme:ThemeData(visualDensity:density),home:MainAppScaffold(),debugShowCheckedModeBanner:false,);

To useVisualDensity inside your own views, you can look it up:

dart
VisualDensitydensity=Theme.of(context).visualDensity;

Not only does the container react automatically to changes in density, it also animates when it changes. This ties together your custom components, along with the built-in components, for a smooth transition effect across the app.

As shown,VisualDensity is unit-less, so it can mean different things to different views. In the following example, 1 density unit equals 6 pixels, but this is totally up to you to decide. The fact that it is unit-less makes it quite versatile, and it should work in most contexts.

It's worth noting that the Material generally use a value of around 4 logical pixels for each visual density unit. For more information about the supported components, see theVisualDensity API. For more information about density principles in general, see theMaterial Design guide.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.38.6. Page last updated on 2025-10-30.View source orreport an issue.


[8]ページ先頭

©2009-2026 Movatter.jp