You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 18:44:51