Search code examples
flutterdartbuttonfocustextfield

How to shift focus and undo focus from one widget to other in flutter?



I've been new to flutter, and I am learning right now how this focus works?
So far from ai chats and stackoverflow [links][1] (one of the best links I found) I have come to know about `focusnode` but I have some query of which I need a solution.

Just a little info about if this approach possible anyhow
I've been a developer in c# and vb.net winforms and there was a nice option of TabIndex in almost every component or widget in this case in which we just need to write numbers and the system automatically handles the tabs (focus to next)/shift+tabs(focus to previous).
I understand flutter is made to achieve cross device (majorly for mobile devices if I am not wrong.) but I found this approach no where and on further search I found that this is not available in flutter. I mean even HTML has this TabIndex feature.

I've also seen AI generated code about using focusnode and declaring each focusnode for each textfield. I find this approach helpful but won't that be a lot tedious if I have approx 25 textfield in a single form/page/window?

Also I found this line of code FocusScope.of(context).nextFocus() but how'd I let flutter know that I want to focus to 3rd textfield from 1st textfield and not 2nd textfield.
Also if I use shift + tab how can I get focus of previous focused tab?

So atlast a small summary,
What is want is

  1. How do I focus to specific textfield or button on enter/return or tab keypress?
  2. How do I get focus to previous textfield or button on shift + tab keypress?

Note I don't know why I can't attach link above but this is link: How to shift focus to the next TextField in Flutter?

I hope moderators will edit it.


Solution

  • You can manipulate the order in which fields, buttons, etc, are focused by wrapping them in a FocusTraversalGroup and supplying a FocusTraversalPolicy.

    FocusTraversalGroup(
      policy: policy, // your FocusTraversalPolicy here
      child: ..., // the objects you want to focus go in the child subtree
    ),
    

    There are a few different policies you can choose from:

    If you want to supply a specific numeric order for each focusable item, then you want OrderedTraversalPolicy. With this policy you must also wrap each item you want to focus with a FocusTraversalOrder widget.

    FocusTraversalOrder(
      order: NumericFocusOrder(order), // the focus order it should appear in the group
      child: ..., // button or field you want to focus
    ),
    

    I have put together a small demo that you can use to try out the different focus policies.

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MaterialApp(
        title: 'Focus Demo',
        home: FocusPage(),
      ));
    }
    
    class FocusPage extends StatefulWidget {
      const FocusPage({super.key});
    
      @override
      State<FocusPage> createState() => _FocusPageState();
    }
    
    FocusTraversalPolicy orderedTraversalPolicy = OrderedTraversalPolicy();
    FocusTraversalPolicy readingTraversalPolicy = ReadingOrderTraversalPolicy();
    FocusTraversalPolicy widgetTraversalPolicy = WidgetOrderTraversalPolicy();
    
    class _FocusPageState extends State<FocusPage> {
      List<List<({String name, double order})>> fields = [
        [
          (name: 'A', order: 4),
          (name: 'B', order: 2),
          (name: 'C', order: 6),
        ],
        [
          (name: 'D', order: 3),
          (name: 'E', order: 5),
          (name: 'F', order: 1),
        ],
      ];
    
      FocusTraversalPolicy? policy = orderedTraversalPolicy;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Focus Demo'),
            actions: [
              FocusTraversalGroup(
                descendantsAreTraversable: false,
                child: DropdownButton<FocusTraversalPolicy>(
                  value: policy,
                  items: [
                    DropdownMenuItem(
                      value: orderedTraversalPolicy,
                      child: const Text('Custom Order'),
                    ),
                    DropdownMenuItem(
                      value: readingTraversalPolicy,
                      child: const Text('Reading Order'),
                    ),
                    DropdownMenuItem(
                      value: widgetTraversalPolicy,
                      child: const Text('Widget Order'),
                    ),
                  ],
                  onChanged: (v) {
                    setState(() {
                      policy = v;
                    });
                  },
                ),
              ),
            ],
          ),
          body: FocusTraversalGroup(
            policy: policy,
            child: Row(
              children: [
                for (var row in fields)
                  Expanded(
                    child: Column(
                      children: [
                        for (var (:name, :order) in row)
                          Expanded(
                            child: Padding(
                              padding: const EdgeInsets.all(25),
                              child: FocusTraversalOrder(
                                order: NumericFocusOrder(order),
                                child: TextField(
                                  decoration: InputDecoration(
                                    labelText: name,
                                  ),
                                ),
                              ),
                            ),
                          ),
                      ],
                    ),
                  ),
              ],
            ),
          ),
        );
      }
    }