Kivy转Android TV应用遥控/DPAD菜单滚动失效求助
Kivy Android TV 遥控器/DPAD 无响应问题解决方案
问题背景
将Kivy应用打包为Android APK部署到Android TV后,遥控器或DPAD无法滚动菜单/按钮。已尝试on_button_press、自定义TVKeyHandler及安卓按键监听器,通过adb发送命令时菜单可正常工作,但遥控器操作无响应,疑似按键信号未从安卓系统传递至应用。
核心解决方案
1. 修复AndroidManifest.xml的TV适配配置
Android TV应用必须声明正确的分类和特性,否则系统不会传递遥控器按键事件:
- 在
AndroidManifest.xml中添加TV相关配置:
<uses-feature android:name="android.software.leanback" android:required="false" /> <uses-feature android:name="android.hardware.touchscreen" android:required="false" /> <activity android:name="org.kivy.android.PythonActivity" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden|screenSize" android:launchMode="singleTop" android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LEANBACK_LAUNCHER" /> </intent-filter> </activity>
- 关键:必须包含
LEANBACK_LAUNCHER分类,否则应用不会被识别为TV应用,系统会拦截遥控器事件。
2. 确保Kivy窗口获取焦点
Android TV系统会将按键事件发送到当前获取焦点的窗口,需确保Kivy窗口在启动后立即获取焦点:
在App的on_start方法中添加:
from kivy.core.window import Window def on_start(self): Window.focus = True # 初始化焦点到第一个可交互控件 screen = self.root.get_screen('MainScreen') screen.ids.today_button.focus = True
3. 修正按键事件监听方式
Kivy在Android上的按键事件传递需要正确绑定窗口的on_key_down事件,之前的TVKeyHandler需要正确挂载:
class YourApp(App): def build(self): self.key_handler = TVKeyHandler(self) # 绑定窗口按键事件 Window.bind(on_key_down=self.on_key_down) return YourRootWidget() def on_key_down(self, window, keycode, scancode, codepoint, modifier): # Android上keycode是元组,第一个元素是安卓keycode,第二个是字符 android_keycode = keycode[0] return self.key_handler.handle_key(android_keycode)
- 注意:Android平台下,
on_key_down的keycode参数是(安卓keycode, 字符)的元组,需要取第一个元素。
4. 验证控件的焦点属性
所有需要通过DPAD控制的控件必须开启focusable属性:
在KV语言中确保按钮等控件设置:
Button: id: today_button focusable: True focus_next: some_other_button # 手动指定焦点链(可选,增强稳定性) focus_previous: another_button
5. 测试按键事件传递
使用adb命令验证系统是否传递按键事件到应用:
adb shell input keyevent KEYCODE_DPAD_UP
如果此命令有效但遥控器无效,可能是遥控器的按键映射与标准安卓keycode不一致,可通过以下命令查看遥控器实际发送的keycode:
adb shell getevent -l
执行后按遥控器按键,会输出对应的事件信息,将新的keycode添加到KEYCODE_MAP中。
调整后的完整TVKeyHandler代码
from kivy.app import App from kivy.core.window import Window from kivy.uix.button import Button try: from android import log except ImportError: def log(tag, msg): print(f"[{tag}] {msg}") # 桌面环境降级处理 # 安卓遥控器按键映射字典 KEYCODE_MAP = { 19: 'up', # KEYCODE_DPAD_UP 20: 'down', # KEYCODE_DPAD_DOWN 21: 'left', # KEYCODE_DPAD_LEFT 22: 'right', # KEYCODE_DPAD_RIGHT 23: 'select', # KEYCODE_DPAD_CENTER 273: 'up', 274: 'down', 275: 'right', 276: 'left', 13: 'select', 96: 'select', 97: 'back', 99: 'select', 100: 'menu', 109: 'back', 110: 'back', 108: 'select', 106: 'right', 105: 'left', 66: 'select', 160: 'select', 4: 'back', 82: 'menu', 111: 'escape', 7: 'select', 8: 'select', 9: 'select', 10: 'select', 11: 'select', 12: 'select', 13: 'select', 14: 'select', 15: 'select', 16: 'select', 85: 'pause', 126: 'play', 127: 'pause', 86: 'stop', 87: 'next', 88: 'prev', 165: 'info', } class TVKeyHandler: def __init__(self, app): self.app = app def handle_key(self, keycode): action = KEYCODE_MAP.get(keycode) log("remote-event", f"收到按键: {keycode} → {action}") focused = self.get_focused() if not focused: log("remote-event", "⚠️ 无焦点控件,尝试恢复焦点") try: screen = self.app.root.get_screen('MainScreen') screen.ids.today_button.focus = True log("remote-event", "✅ 焦点已恢复到today_button") return True except Exception as e: log("remote-event", f"❌ 焦点恢复失败: {e}") return False if not action: log("remote-event", f"未处理的keycode: {keycode}") return False if action == 'up': focused.focus_previous('up') elif action == 'down': focused.focus_next('down') elif action == 'right': focused.focus_next('right') elif action == 'left': focused.focus_previous('left') elif action == 'select': if isinstance(focused, Button): focused.trigger_action(duration=0.1) elif action == 'back' or action == 'escape': App.get_running_app().stop() elif action == 'menu': log("remote-event", "菜单键被按下 — 可实现弹窗或设置界面") else: log("remote-event", f"未处理的动作: {action}") return False return True def get_focused(self): return Window.focused_widget or None
内容的提问来源于stack exchange,提问作者Nemzzy Builds
相关产品推荐
相关产品推荐

