Android中关于APK对遥控器支持的修改

本文介绍了在Android中为APK添加遥控器支持的方法。针对焦点控制失效的问题,提出了两种解决思路:一是通过设置View属性获取焦点,二是自管理焦点移动、重绘高亮和enter键响应。详细阐述了焦点移动、按键捕获、自定义高亮和功能响应的实现步骤,提供了多个实例代码作为参考。

        最近,对Andoid中APK做了一些修改,让其支持遥控器的支持,这把人弄得焦头烂额,好不郁闷,这里特别记录下思路,留给有需要的同志。

        其实Android本身是做了对遥控器的上下左右按键的焦点移动控制,以及对Enter键的响应,现在知道的原因就是1:View的子类在派生时对其动作做了从写,导致其焦点控制失效;2:不明原因导致焦点控制失败。该文只是从功能上总结了自己在APK的IR修改中摸索出的一些方法,来达到支持IR控制的效果。

    思路一:android提供了一些焦点相关的属性,在现有的框架层下通过设置View的属性来获得焦点

    android:focusable:设置一个控件能否获得焦点
    android:background:设置在作为背景的drawable
    android:nextFocusDown:定义下一个获得焦点的控件当按下键时
    android:nextFocusUp:定义下一个获得焦点的控件当按上键时
    android:nextFocusLeft:定义下一个获得焦点的控件当按左键时
    android:nextFocusRight:定义下一个获得焦点的控件当按右键时
    <requestFocus/>:强制设置一个焦点到指定的view或它的一个子类,前提是android:focusable为true能够获得焦点

实例一:

    <ImageButton
        android:id="@+id/close"
        android:focusable="true"
        android:nextFocusDown="@+id/url"
        android:background="?android:attr/selectableItemBackground"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/accessibility_button_closetab"
        android:src="@drawable/ic_tab_close" />

实例二:

  <EditText android:id="@+id/user_dictionary_add_word_text"
        ……
        ……
        android:imeOptions="flagNoFullscreen">
        <requestFocus />
  </EditText>

实践经验:如果XML设置无效的话,尝试下在代码中XXX.requestFocus()显式设置

         思路二:自己管理焦点的移动,自己进行高亮的重绘,自己进行enter键的响应,总之:一切都靠你自己

Step 1:焦点的移动,首先你就要做到IR按键的捕获与响应

         单独Activity的话,下面的两组函数都能够捕获按键消息

public boolean dispatchKeyEvent (KeyEvent event) 
public boolean onKeyDown (int keyCode, KeyEvent event)
public boolean onKeyUp(int keyCode, KeyEvent event)

public boolean dispatchKeyEventPreIme (KeyEvent event)
public boolean onKeyPreIme (int keyCode, KeyEvent event)

一般来说重写onKeyDownonKeyUp就可以了,但需要在输入法之前做一些动作,便需要重写onKeyPreIme,但是在某些时候在事件分发中会被分发到其他的view的事件处理,导致不会调用到该ActivityonKeyDownonKeyUp,这个时候就需要在分发函数dispatchKeyEventdispatchKeyEventPreIme做一些动作了;要显式调用某个view的事件处理函数或是自定义的事件处理函数,也可以在以上的几个函数中进行直接调用,相当于绕开系统自己来进行一些事件的分发(只是解决方案,做法并不规范);以上这一系列函数的返回值是boolean型,如果你已经处理了一些消息,记得返回true来通知系统已经做了处理,交由系统处理的消息直接父类super的同名函数进行处理;有些按键系统对其所做的关键响应有的是在onKeyUp或者onKeyDown中,因此在你拦截的时候需在恰当的函数里进行拦截。

实例一:

@Override
public boolean onKeyUp(int keyCode, KeyEvent event){
ActivityState topState = getStateManager().getTopState();
    if(topState == null){
     		return super.onKeyUp(keyCode, event);
    }

    /*显式调用ActivityState的onKeyUp事件处理,这个onKeyUp可以是重写View的消息处理,不是View
    的话也可以自定义这个函数来进行事件处理,当然在这里你可以直接switch keyCode来进行当前Activity
    事件处理*/
    if(topState.onKeyUp(keyCode, event)){
    	return true;
    }
    else{
    	return super.onKeyUp(keyCode, event);
    }
}

@Override
public boolean onTouchEvent(MotionEvent ev){
    ActivityState topState = getStateManager().getTopState();
    if(topState == null){
   	return super.onTouchEvent(ev);
    }
   
    if(topState.onTouchEvent(ev)){
        return true;
    }
    else{
        return super.onTouchEvent(ev);
    }
}

/*
在按键事件的分发时期进行拦截,做一些自己想做的处理
*/
@Override
public boolean dispatchKeyEvent (KeyEvent event){
    int keyCode = event.getKeyCode();
    ActivityState topState = getStateManager().getTopState();
    if(topState == null){
    	return super.dispatchKeyEvent(event);
    }
    //sometimes even the actionbar is hide,but the input focus is on the actionbar,so when the focus is on
    //the slot,we should intercept the enterkeyevent action from the system
    if(event.getAction() == KeyEvent.ACTION_UP){
        if(topState.getFocusOnSlotView() 
            && keyCode == KeyEvent.KEYCODE_ENTER){
         	return  topState.onKeyUp(keyCode, event);
    	    }
 	}

    return super.dispatchKeyEvent(event);
}

@Override
public boolean dispatchTouchEvent (MotionEvent ev){
    ActivityState topState = getStateManager().getTopState();
    if(topState == null){
    	return super.dispatchTouchEvent(ev);
    }
    
    if(ev.getAction() == MotionEvent.ACTION_DOWN){
        //if the user control the gallery by mouse,you should clear the focus rect
    	topState.setFocusOnSlotView(false);
    }

    return super.dispatchTouchEvent(ev);
}

实例二:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    final int uniChar = event.getUnicodeChar();
    final boolean handled = super.onKeyDown(keyCode, event);
    final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
    if (!handled && acceptFilter() && isKeyNotWhitespace) {
        boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
            keyCode, event);
            if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
                 return onSearchRequested();
            }
        }

        // Eat the long press event so the keyboard doesn't come up.
        if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
            return true;
        }

    return handled;
}

假如要控制的是一个View的话,你的这个View是顶层并能获得焦点,对你有帮助的函数如下

public void setOnKeyListener (View.OnKeyListener l)

public boolean dispatchKeyEvent (KeyEvent event) 
public boolean dispatchKeyEventPreIme (KeyEvent event)

第一组的作用在于你定义一个View.OnKeyListener,并重写其onClick函数,自己来处理按键消息,并将此listener注册进该view中;第二组的作用当然是使你在事件分发或输入法之前的时候进行自定义的一些操作

实例一:

OnKeyListener mCheckboxKeyListenner = new OnKeyListener(){
    public boolean onKey(View v, int keyCode, KeyEvent event){
        return onKeyDone(v, keyCode, event);
    }
};

public boolean onKeyDone(View v, int keyCode, KeyEvent event) {
    CollectCurrentSelected();
        
    if(event.getAction() == KeyEvent.ACTION_UP){
    	return moveIndexOnKeyUp(keyCode);
    }
        
    if(event.getAction() == KeyEvent.ACTION_DOWN){
        return moveIndexOnKeyDown(keyCode);
    }
    return false;
}

private boolean moveIndexOnKeyUp(int keyCode){
    if(keyCode == KeyEvent.KEYCODE_DPAD_RIGHT){
        if(mResponseIndex == INDEX_SETTINGBTN){
            return true;
        }
    }

    return false;
}
    
private boolean moveIndexOnKeyDown(int keyCode){
    if(keyCode == KeyEvent.KEYCODE_DPAD_LEFT
    	|| keyCode == KeyEvent.KEYCODE_DPAD_UP
        || keyCode == KeyEvent.KEYCODE_DPAD_DOWN){
        mResponseIndex = INDEX_CHECKBOX;
        resetDefaultBackground();
        return false;
    }
    else if(keyCode == KeyEvent.KEYCODE_DPAD_RIGHT){
        if(mResponseIndex == INDEX_SETTINGBTN){
    	    mResponseIndex = INDEX_CHECKBOX;
            resetDefaultBackground();
       	    return false;
     	}
    	else{
  	    mResponseIndex = INDEX_SETTINGBTN;
       	    mSettingBtn.setBackgroundResource(R.drawable.appwidget_item_bg_pressed);
            mSettingBtn.invalidate();
            mTextLayout.setBackgroundResource(R.drawable.appwidget_item_bg_normal);
            mTextLayout.invalidate();
            return true;
        }
    }
    else if(keyCode == KeyEvent.KEYCODE_ENTER){
        if(mResponseIndex == INDEX_CHECKBOX){
            checkBoxOption();
        }
        else if(mResponseIndex == INDEX_SETTINGBTN){
            startShowSetting();
        }
                
        return true;
    }
    else{
        return false;
    }
}

/*一般View的话,可以直接XXX. setOnKeyListener(XXX),在Setting中大量的用到PreferenceFragment,其
内部镶嵌了一个listview,因此对一个PreferenceFragment的操作,其实是对该listview的操作,所以需要设这
该listview的keyListener*/
ListView listView = getListView();
listView.setOnKeyListener(mCheckboxKeyListenner);

step 2:在能捕获按键之后,需要做的自己来做焦点的逻辑管理

实例一:

//收集自己打算管理的view的数组
private void colloctionViews(){
    mViews = new View[NEEDVIEWS_SIZE];
    mViews[0] = mYearSpinnerInput;
    mViews[1] = mMonthSpinnerInput;
    mViews[2] = mDaySpinnerInput;
}

//焦点的移动对View的逻辑来说其实就是数组的下标的管理
private void  increaseCurIndex(){
    mLastIndex = mCurrentIndex;
    mCurrentIndex = (mCurrentIndex + 1) % mViews.length;
}

private void decreaseCurIndex(){
    mLastIndex = mCurrentIndex;
    if(mCurrentIndex == 0){
        mCurrentIndex = mViews.length - 1;
    }
    else{
        mCurrentIndex--;
    }
}

//对按键的响应来改变的焦点主要是改变View的索引flag,然后对对应的view的高亮进行重绘
public boolean onKeyDown(int keyCode, KeyEvent event){
    if(KeyEvent.KEYCODE_DPAD_LEFT == keyCode){
        decreaseCurIndex();
	mViews[mCurrentIndex].requestFocus();
        updateBackground();
        return true;
    }
    else if(KeyEvent.KEYCODE_DPAD_RIGHT == keyCode){
     	increaseCurIndex();
	mViews[mCurrentIndex].requestFocus();
        updateBackground();
        return true;
    }
    else{
    }
        
    return false;
}

Step3:接下来是对当前焦点的高亮进行重绘

实例一:

//对响应的view调用invalidate(),便能触发其对焦点高亮进行重绘,当然这里需要mIsOnFocus来作为高亮
//打开和关闭的开关
@Override
protected void onDraw (Canvas canvas){
    super.onDraw(canvas);

    if(mIsOnFocus){
        // 或者使用drawBoundsOfKey(canvas);
        drawFocusBackground();
    }
}

在上面的drawFocusBackground其实会遇到两种情况

情况一为:直接对当前view进行高亮边框的绘制,你能直接拿到它的canvas进行绘制,那么重绘代码如下:

实例一:

//直接在外部绘制高亮矩形
private void drawBoundsOfKey(Canvas canvas){
    Paint p = new Paint();
    p.setColor(Color.CYAN);
    p.setStyle(p.getStyle().STROKE);
    p.setStrokeWidth(3.75f);
    focusedKey = mSoftKeyboard.getKeys().get(mLastKeyIndex);
    rect = new Rect(focusedKey.mLeft, focusedKey.mTop,
        focusedKey.mRight, focusedKey.mBottom);
    canvas.drawRect(rect, p);
}

情况二为:绘制的是内部一些子View的高亮边框进行重绘,这个时候你拿不到它canvas进行绘制,那么重绘可以通过设置背景色的方式来达到高亮提示的效果,重绘代码如下:

实例二:

//需要保存旧的drawable来做清除焦点的动作
private void drawFocusBackground(){
    View curView = mViews[mCurrentIndex];
    mOldDrawable = curView.getBackground();
    curView.setBackgroundColor(0xAA07FFFF);
    curView.invalidate();	
}

private void rebackOldBackground(){
    View oldView = mViews[mLastIndex];
    oldView.setBackgroundDrawable(mOldDrawable);
    oldView.invalidate();
    oldView.clearFocus();
}

Step4:最后一步便是按下enter键的时候的功能响应

思路一:对一个View的功能响应,其实可以对该view进行一次模拟的touch事件,这样能让系统自己走touchview的功能流程,达到思路最简单,代码最少的目的

实例一:

//这里的view需要给出你需要响应的View。而构造的点击中心点普通的view的话设定为view的中心便可
//以了,如果是edit类的view,或许你的点击中心点应当在它的focusRect()
private void emulateTouchEvent(int left, int top, int right, int bottom, View v){
    final int x = left + (right - left) / 2;
    final int y = top + (bottom - top) / 2;
    final long downTime = SystemClock.uptimeMillis();
    final MotionEvent downEvent = MotionEvent.obtain(
        downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0);
    final MotionEvent upEvent = MotionEvent.obtain(
        downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0);

     v.onTouchEvent(downEvent);
     v.onTouchEvent(upEvent);

     downEvent.recycle();
     upEvent.recycle();
}

思路二:查看代码,看这个view响应的功能函数是什么,直接对功能函数进行调用

PS:注释使用了两种风格,不好看,下次得注意了;吐个槽:这CSDN的排版功能不是一般的难用啊






<think>我们正在解决AndroidTV上使用遥控器操作虚拟键盘输入的问题,特别是关于SettingsAPK遥控器无法操作虚拟键盘的情况。可能的原因和解决方案:1.**焦点问题**:AndroidTV的界面导航依赖于焦点。虚拟键盘的输入框或按键可能没有正确获取焦点。-检查布局文件,确保相关视图(如EditText)设置了`focusable="true"`和`focusableInTouchMode="true"`(尽管是遥控器,但焦点模式类似)。-在代码中,可以尝试在适当的时候请求焦点,例如:`editText.requestFocus();`。2.**按键事件处理**:遥控器的按键事件可能没有被正确传递到虚拟键盘。-检查是否在Activity或Fragment中重写了`onKeyDown`或`dispatchKeyEvent`方法,并且没有消费掉按键事件(尤其是方向键和确认键)。确保当虚拟键盘显示时,事件被传递到键盘视图。-如果自定义了键盘视图,需要确保为每个按键设置OnKeyListener或者处理按键事件。3.**输入法(IME)问题**:Settings应用可能没有正确调用输入法。-确保在需要输入的地方调用输入法。通常,当EditText获得焦点时,系统会自动显示输入法。但有时可能需要手动调用:```javaInputMethodManagerimm=(InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);imm.showSoftInput(editText,InputMethodManager.SHOW_IMPLICIT);```-检查是否在AndroidManifest.xml中为Activity设置了`android:windowSoftInputMode`属性,例如`stateVisible`或`adjustResize`。4.**自定义键盘实现**:如果Settings应用使用了自定义的键盘视图(而不是系统默认的输入法),那么需要确保该自定义键盘能够响应遥控器的按键事件。-为键盘中的按钮设置OnClickListener是不够的,因为遥控器操作的是焦点和确认键。应该为每个按钮设置`setOnKeyListener`,并处理KEYCODE_DPAD_CENTER(确认键)和方向键(用于切换焦点)。-或者,使用`setOnFocusChangeListener`来处理焦点变化,并在确认键按下时触发点击事件。5.**系统权限或限制**:Settings应用是系统应用,但虚拟键盘可能涉及系统输入法,需要确保有必要的权限。-检查是否在AndroidManifest.xml中声明了必要的权限,例如`<uses-permissionandroid:name="android.permission.USE_INPUT_METHOD"/>`(如果需要直接控制输入法)。6.**测试不同输入法**:尝试在设备上切换为其他输入法(如Google的TV输入法),看是否是特定输入法的问题。7.**Log分析**:查看logcat输出,过滤关键字“InputMethod”、“Focus”、“InputEvent”等,以帮助定位问题。8.**系统版本兼容性**:不同AndroidTV版本(如Oreo,Pie,Android10等)在焦点管理和输入法处理上可能有差异,需要检查代码是否兼容。由于问题描述比较宽泛,以上是常见的解决方向。具体到SettingsAPK,可能需要查看源码中关于输入处理的部分。例如,在AOSP的Settings源码中,搜索EditText以及输入法相关的代码。注意:如果问题出现在自己修改的Settings应用中,请对照AOSP的原始代码检查修改部分。如果问题仍然存在,请提供更具体的错误信息或代码片段,以便进一步分析。相关问题:1.如何为AndroidTV应用正确设置焦点导航?2.在AndroidTV上如何自定义输入法?3.AndroidTV中如何处理遥控器的按键事件?4.如何调试AndroidTV的焦点问题?5.在系统应用中调用输入法有什么特殊要求?</think>### Android TV Settings APK 遥控器无法操作虚拟键盘的解决方案Android TV 上,遥控器无法操作虚拟键盘的问题通常由焦点管理、事件分发或输入法配置引起。以下是系统级解决方案: #### 1. **焦点管理优化** - **检查焦点链**:确保键盘按键可通过方向键导航 ```xml <!-- 键盘布局示例 --> <Button android:id="@+id/key_a" android:focusable="true" android:nextFocusRight="@id/key_b" android:nextFocusLeft="@id/key_del"/> ``` - **强制请求焦点**:在键盘显示时主动请求焦点 ```java keyboardView.post(() -> { keyboardView.requestFocus(View.FOCUS_DOWN); }); ``` #### 2. **按键事件处理** - **重写 `dispatchKeyEvent`**:在自定义键盘视图中处理遥控器按键 ```java @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) { handleKeyPress(getFocusedChild()); // 处理确认键 return true; } return super.dispatchKeyEvent(event); } ``` - **方向键灵敏度调整**:在 `res/values/config.xml` 添加 ```xml <integer name="config_keyRepeatTimeout">300</integer> <!-- 按键延迟 --> <integer name="config_keyRepeatDelay">50</integer> <!-- 重复间隔 --> ``` #### 3. **输入法配置** - **AndroidManifest.xml 声明**: ```xml <activity android:name=".SettingsActivity" android:imeOptions="actionDone" android:windowSoftInputMode="stateVisible|adjustPan" /> ``` - **检查输入法服务**:确认系统默认 IME 支持 TV 遥控器 ```bash adb shell ime list -a # 列出可用输入法 ``` #### 4. **系统级调试** - **焦点轨迹可视化**:启用开发人员选项中的指针位置 ```bash adb shell setprop debug.layout true ``` - **事件日志分析**: ```bash adb shell getevent -l # 查看原始输入事件 ``` #### 5. **特殊场景处理** - **无障碍模式兼容**:在 `onInitializeAccessibilityNodeInfo` 中设置节点信息 ```java @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.addAction(AccessibilityNodeInfo.ACTION_CLICK); } ``` - **低内存设备优化**:在 `onTrimMemory()` 中避免释放键盘资源 > **根本原因分析**:TV 遥控器使用 D-pad 导航而非触摸事件,Settings APK 可能未正确处理 `KeyEvent.KEYCODE_DPAD_CENTER` 或焦点链断裂[^1]。 ### 验证步骤 1. 在模拟器中测试焦点流: ```bash adb shell input keyevent KEYCODE_TAB # 切换焦点 ``` 2. 检查窗口层级: ```bash adb shell dumpsys window windows | grep -E 'Focus|InputMethod' ``` 3. 监控输入事件分发: ```bash adb shell getprop log.tag.InputMethod DEBUG ``` ### 替代方案 若系统修改不可行,可尝试: 1. **使用物理键盘**:通过 USB/BT 连接 2. **手机辅助输入**:启用 Android TV Remote Service 3. **语音输入**:集成 `VoiceInteractionService`
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值