Android TV上控件的焦点修复

3
我花了很多时间去理解为什么它不起作用,但仍然不知道如何修复它。 我发现了这个解决方案https://github.com/flutter/flutter/issues/49783,但这并没有帮助我。 我的问题是在按下RC上的选择按钮时,开关无法更改(我正在使用模拟器)。 复现步骤:
  • 启动应用程序
  • 在第一个文本控件中输入一些文本 (在我的情况下,我需要按下和释放按钮才能显示键盘,这是另一个可以解决的错误)
  • 按下RC返回按钮以隐藏键盘
  • 按下RC向下按钮-焦点将移到开关
  • 然后尝试按下选择按钮(它对第一次有效)
  • 然后返回到文本字段,按下返回和然后按下RC按钮
  • 目前,开关在按下选择按钮时不起作用
  • 按下选择仍无法在按钮上工作
  • 按下向上箭头-选择正在切换开关,并且按钮再次起作用!如果您进入文本字段,则会重复此过程

我的示例代码:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent>{
        LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
      },
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool switchValue = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Column(
          // mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: EdgeInsets.symmetric(vertical: 10),
              child: Focus(
                canRequestFocus: false,
                onKey: (FocusNode node, RawKeyEvent event) {
                  if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
                    FocusManager.instance.primaryFocus!
                        .focusInDirection(TraversalDirection.left);
                  } else if (event.logicalKey ==
                      LogicalKeyboardKey.arrowRight) {
                    FocusManager.instance.primaryFocus!
                        .focusInDirection(TraversalDirection.right);
                  } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
                    FocusManager.instance.primaryFocus!
                        .focusInDirection(TraversalDirection.up);
                  } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
                    FocusManager.instance.primaryFocus!
                        .focusInDirection(TraversalDirection.down);
                  }
                  return KeyEventResult.handled;
                },
                child: TextField(
                  autofocus: true,
                ),
              ),
            ),
            Switch(
              value: switchValue,
              onChanged: (value) {
                setState(() {
                  switchValue = value;
                });
              },
            ),
            TextButton(
              onPressed: () => print('Button pressed'),
              style: TextButton.styleFrom(
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(18.0),
                ),
              ),
              child: const Text('Test'),
            ),
          ],
        ),
      ),
    );
  }
}

这是我的 AndroidManifest.xml 文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.testfocus.test_focus">
<application
     android:label="test_focus"
     android:name="${applicationName}"
     android:icon="@mipmap/ic_launcher">
    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:launchMode="singleTop"
        android:theme="@style/LaunchTheme"
        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
        android:hardwareAccelerated="true"
        android:windowSoftInputMode="adjustResize">
        <!-- Specifies an Android theme to apply to this Activity as soon as
             the Android process has started. This theme is visible to the user
             while the Flutter UI initializes. After that, this theme continues
             to determine the Window background behind the Flutter UI. -->
        <meta-data
          android:name="io.flutter.embedding.android.NormalTheme"
          android:resource="@style/NormalTheme"
          />
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>
    <!-- Don't delete the meta-data below.
         This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
    <meta-data
        android:name="flutterEmbedding"
        android:value="2" />
</application>
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
3个回答

3

我不确定是否来晚了,但这里是 Android TV D-pad 导航的修复解决方法。

请注意,在文本字段中,我们需要读取 keyUp 事件。这是因为 Android TV 上的键盘似乎会读取 keyUp 事件。因此,如果按下 keyDown,则键盘将出现,如果释放键,则会触发 keyUp 事件,这将从键盘接收输入。这就是为什么所有操作都在 keyUp 上完成的原因。(我花了一周时间为我的应用程序解决这个问题)

如果文本字段已经获得焦点,我们需要添加一个包装器,使其在选择键按下时保持文本字段聚焦。(这只是一个解决方法)

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent>{
        LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
      },
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool switchValue = false;
  final FocusScopeNode node = FocusScopeNode();
  final textFocus = FocusNode();
  final textWrapper = FocusNode();
  final switchFocus = FocusNode();
  final btnNode = FocusNode();

  @override
  void initState() {
    textFocus.addListener(_listener);
    btnNode.addListener(_listener);
    textWrapper.addListener(_listener);
    switchFocus.addListener(_listener);
    super.initState();
  }

  _listener() {
    if (textFocus.hasFocus ||
        textWrapper.hasFocus ||
        switchFocus.hasFocus ||
        btnNode.hasFocus) {
      setState(() {});
    }
  }

  @override
  void dispose() {
    textFocus.removeListener(_listener);
    btnNode.removeListener(_listener);
    textWrapper.removeListener(_listener);
    switchFocus.removeListener(_listener);
    node.dispose();
    textFocus.dispose();
    switchFocus.dispose();
    btnNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Demo Home Page'),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(vertical: 10),
        child: FocusScope(
          autofocus: true,
          onFocusChange: (val) {
            if (val) textFocus.requestFocus();
          },
          onKey: (FocusNode node, RawKeyEvent event) {
            if (event is RawKeyUpEvent &&
                event.data is RawKeyEventDataAndroid) {
              RawKeyUpEvent rawKeyDownEvent = event;
              RawKeyEventDataAndroid rawKeyEventDataAndroid =
                  rawKeyDownEvent.data as RawKeyEventDataAndroid;

              if (rawKeyEventDataAndroid.keyCode == KEY_CENTER) {
                if (textFocus.hasFocus) {
                  textFocus.unfocus();
                  Future.delayed(Duration.zero).then((value) {
                    textFocus.requestFocus();
                  });
                } else if (textWrapper.hasFocus) {
                  textFocus.requestFocus();
                }
              }

              if (rawKeyEventDataAndroid.keyCode == KEY_DOWN) {
                if (textWrapper.hasFocus) {
                  switchFocus.requestFocus();
                } else if (switchFocus.hasFocus) {
                  btnNode.requestFocus();
                } else {
                  textFocus.requestFocus();
                }
              }

              if (rawKeyEventDataAndroid.keyCode == KEY_UP) {
                if (btnNode.hasFocus) {
                  switchFocus.requestFocus();
                } else if (switchFocus.hasFocus) {
                  textFocus.requestFocus();
                } else {
                  btnNode.requestFocus();
                }
              }
            }
            return KeyEventResult.handled;
          },
          child: Column(
            children: [
              RawKeyboardListener(
                focusNode: textWrapper,
                child: TextField(
                  focusNode: textFocus,
                  autofocus: true,
                ),
              ),
              Shortcuts(
                shortcuts: <LogicalKeySet, Intent>{
                  LogicalKeySet(LogicalKeyboardKey.select):
                      const ActivateIntent(),
                  LogicalKeySet(LogicalKeyboardKey.enter):
                      const ActivateIntent(),
                },
                child: Switch(
                  value: switchValue,
                  focusNode: switchFocus,
                  onChanged: (value) {
                    setState(() {
                      switchValue = value;
                    });
                  },
                ),
              ),
              Shortcuts(
                shortcuts: <LogicalKeySet, Intent>{
                  LogicalKeySet(LogicalKeyboardKey.select):
                      const ActivateIntent(),
                  LogicalKeySet(LogicalKeyboardKey.enter):
                      const ActivateIntent(),
                },
                child: TextButton(
                  onPressed: () => print('Button pressed'),
                  focusNode: btnNode,
                  style: TextButton.styleFrom(
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(18.0),
                    ),
                  ),
                  child: const Text('Test'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

const int KEY_UP = 19;
const int KEY_DOWN = 20;
const int KEY_LEFT = 21;
const int KEY_RIGHT = 22;
const int KEY_CENTER = 23;
const int KEY_BACK = 4;

你的答案是stackoverflow上所有解决方案中最正确的。非常感谢,@sai9898。 - Wege

0

我在使用Flutter开发电视应用程序时遇到了类似的问题。

由于TextField有自己的焦点,我找到了一个解决方法。

我禁用了TextField的输入,并在屏幕上显示虚拟键盘以输入TextField中的值。

例如这个: enter image description here

根据按钮按下的字符分配值给TextField,这将是一个不错的解决方法,更加用户友好。 在使用Flutter开发电视应用程序时,尽量减少用户输入。


谢谢你的解决方案,但我正在寻找如何解决焦点问题以及原因是什么。 - Andrey Kucher

0
我在为 Android TV 制作 IPTV 应用时,也遇到了这个问题。每当焦点转移到文本字段后,我就无法使用电视遥控器的 Enter 按钮,但我注意到如果按下两次向下按钮和一次向上按钮,则 Enter 按钮会重新开始工作。
原因仍然未知,这是一个公开的问题。唯一的解决方法是通过创建屏幕键盘并映射点击值来填充文本字段。
希望这可以帮助到您。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接