flutterfocusandroid-tvd-pad

Flutter how to make a selectable / focusable widget


I am creating an android tv app. I was trying to work out for a long time why when I clicked the arrow up and down buttons on the remote it appeared to do nothing and it wasn't selecting any of the list item.

Eventually I was able to work out that if I used an elevated button or other focusable widget on the list i could use the arrow keys and it would work fine. Previously I was using a card widget wrapped in a gesture detector.

So I am wondering what the difference between a button and card with gesture detector is that stops the arrow keys from being able to select the item. I suspect it is the focus.

This is what I was using that doesn't allow the up, down keys on the remote to select it:

GestureDetector(
    child: Card(
      color: color,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(15.0),
      ),
      elevation: 10,
      child: SizedBox(
          width: (width / numberOfCards) - padding * (numberOfCards - 1),
          height: (height / 2) - padding * 2,
          child: Center(child: Text(cardTitle, style: Theme.of(context).textTheme.bodyText1?.copyWith(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),))),
    ),
    onTap: () => onCardTap(),
  ),

And this is the button I replaced it with that then makes the up down keys and selection to work:

ElevatedButton(
                  onPressed: () {},
                  child: Text('Test 1', style: Theme.of(context).textTheme.bodyText1?.copyWith(color: Colors.white, fontSize: 18, fontWeight: FontWeight.normal)),
                  style: ButtonStyle(
                      backgroundColor: MaterialStateProperty.all(Colors.grey.withOpacity(0.3)),
                      minimumSize: MaterialStateProperty.all(Size(60, 60)),
                      elevation: MaterialStateProperty.all(10),
                      shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: new BorderRadius.circular(50)),)),
                ),

In case its needed this is what I am using to pick up the key presses:

Shortcuts(
  shortcuts: <LogicalKeySet, Intent>{
    LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
  },

Thanks


Solution

  • The difference between your card with gesture detector and the ElevatedButton is that you don't have a FocusNode.

    If you dig into the implementation details of the ElevatedButton you will find that it uses an InkWell with a FocusNode

    final Widget result = ConstrainedBox(
      constraints: effectiveConstraints,
      child: Material(
        // ...
        child: InkWell(
          // ...
          focusNode: widget.focusNode,
          canRequestFocus: widget.enabled,
          onFocusChange: updateMaterialState(MaterialState.focused),
          autofocus: widget.autofocus,
          // ...
          child: IconTheme.merge(
            // ....
            child: Padding(
              padding: padding,
              child: // ...
              ),
            ),
          ),
        ),
      ),
    );
    

    So, if you replace GestureDetector with Inkwell, then the keyboard navigation would work.

    InkWell(
      child: Card(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(15.0),
        ),
        elevation: 10,
        child: const SizedBox(
          width: 200,
          height: 60,
          child: Center(
            child: Text(
              'Test 1',
            ),
          ),
        ),
      ),
      onTap: () {},
    )
    

    (Tested on Android TV emulator API 30, with keyboard an d-pad.)

    References