最近,对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:定义下一个获得焦点的控件当按右键时
:强制设置一个焦点到指定的view或它的一个子类,前提是android:focusable为true能够获得焦点
实例一:
实例二:
实践经验:如果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)
一般来说重写onKeyDown和onKeyUp就可以了,但需要在输入法之前做一些动作,便需要重写onKeyPreIme,但是在某些时候在事件分发中会被分发到其他的view的事件处理,导致不会调用到该Activity的onKeyDown和onKeyUp,这个时候就需要在分发函数dispatchKeyEvent和dispatchKeyEventPreIme做一些动作了;要显式调用某个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事件,这样能让系统自己走touch该view的功能流程,达到思路最简单,代码最少的目的
实例一:
//这里的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的排版功能不是一般的难用啊