skip to Main Content

There is a Wrap widget in the app with a list of ElevatedButtons, the autofocus on the first one is set to true. All the buttons have the same overlayColor. The autofocus doest seem to work. I’ve verified this by adding a listener to the FocusManager.

[log] focus: FocusNode#b5f2b([PRIMARY FOCUS])(context: Focus, PRIMARY FOCUS)
[log] focus: FocusNode#e4580([PRIMARY FOCUS])(context: Focus, PRIMARY FOCUS)
[log] focus: FocusNode#b5f2b([PRIMARY FOCUS])(context: Focus, PRIMARY FOCUS)

The first focus event is the autofocus, but the widget has no overlayColor or any focus effect. Going, right and then left brings the focus back to the only autofocus widget and this time with all focus effects.

Internally, the overlayEffect comes from the WidgetStateProperty<Color?> object. I setup a custom implementation to track the state changes.

@immutable
class StateDependentColor extends WidgetStateProperty<Color?> {
  StateDependentColor(this.color);

  final Color color;

  @override
  Color? resolve(Set<WidgetState> states) {
    if (states.contains(WidgetState.pressed)) {
      return color.withOpacity(0.1);
    }
    if (states.contains(WidgetState.hovered)) {
      return color.withOpacity(0.08);
    }
    if (states.contains(WidgetState.focused)) {
      return color.withOpacity(0.5);
    }
    return null;
  }
}

This is applied to the button like.

    child: ElevatedButton(
      autofocus: true,
      style: ElevatedButton.styleFrom(
        alignment: Alignment.center,
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ).copyWith(
        overlayColor: StateDependentColor(Colors.white),
      ),
      onPressed: () {},
      child: Text("Click")
    )

The resolve method is not getting called at startup. Some people have recommended using a focusNode instead of autofocus. Tried that and TBH it worked just once (strange), Never got it to work again.

I’m really out of ideas at this point. Any help/hints would be very helpful.

2

Answers


  1. Chosen as BEST ANSWER

    It turns out the issue is related to the highlight strategy used by flutter. First the fix:

    FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    

    The root cause can be traced to this method in flutter/lib/src/widgets/focus_manager.dart

    static FocusHighlightMode get _defaultModeForPlatform {
      // Assume that if we're on one of the mobile platforms, and there's no mouse
      // connected, that the initial interaction will be touch-based, and that
      // it's traditional mouse and keyboard on all other platforms.
      //
      // This only affects the initial value: the ongoing value is updated to a
      // known correct value as soon as any pointer/keyboard events are received.
      switch (defaultTargetPlatform) {
        case TargetPlatform.android:
        case TargetPlatform.fuchsia:
        case TargetPlatform.iOS:
          if (WidgetsBinding.instance.mouseTracker.mouseIsConnected) {
            return FocusHighlightMode.traditional;
          }
          return FocusHighlightMode.touch;
        case TargetPlatform.linux:
        case TargetPlatform.macOS:
        case TargetPlatform.windows:
          return FocusHighlightMode.traditional;
      }
    }
    

    It turns out by default the highlight mode is assumed to be touch, which doesnt hightlight anything. Once an interaction takes place, TAB/Arrow keys the mode is adjusted to traditional. In traditional mode all focus effects are visible.

    Now, I think, the code for _defaultModeForPlatform should include a way to determine Android TV platform, which it doesnt do right now.

    The fix is for the application to do it itself. Here is the working snippet

    final isTv = await NativeChannel.hasLeanbackFeature();
    if (isTv) {
      log("setting highlight traditional on Android TV");
      FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    }
    

    Hope this helps anyone else down the rabbit hole.


  2. Try overlaycolor like below… Hope it helps

     overlayColor: MaterialStateProperty.resolveWith<Color>(
            (Set<MaterialState> states) {
              if (states.contains(MaterialState.focused)) {
              return Colors.white; 
              }
              return Colors.white.withOpacity(0.08);
            },
          ),
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search