目录
遥控器适配基本方法:
1.XML文件中
android:focusable:设置一个控件能否获得焦点
android:background:设置在作为背景的drawable
android:nextFocusDown:定义下一个获得焦点的控件当按下键时
android:nextFocusUp:定义下一个获得焦点的控件当按上键时
android:nextFocusLeft:定义下一个获得焦点的控件当按左键时
android:nextFocusRight:定义下一个获得焦点的控件当按右键时
<requestFocus/>:强制设置一个焦点到指定的view或它的一个子类,前提是android:focusable为true
2.activiy中
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。
3.view
public void setOnKeyListener (View.OnKeyListener l)
public boolean dispatchKeyEvent (KeyEvent event)
public boolean dispatchKeyEventPreIme (KeyEvent event)
焦点获取状态全局监听:
Android中提供了ViewTreeObserver来监听View树中一系列状态变化
ViewTreeObserver的解释:
这是一个注册监听视图树的观察者(observer),在视图树种全局事件改变时得到通知。这个全局事件不仅还包括整个树的布局,从绘画过程开始,触摸模式的改变等。ViewTreeObserver不能够被应用程序实例化,因为它是由view提供,通过getViewTreeObserver()可以获取到该实例。
该类中有以下接口
interface ViewTreeObserver.OnGlobalFocusChangeListener
当在一个视图树中的焦点状态发生改变时,所要调用的回调函数的接口类
interface ViewTreeObserver.OnGlobalLayoutListener
当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时,所要调用的回调函数的接口类
interface ViewTreeObserver.OnPreDrawListener
当一个视图树将要绘制时,所要调用的回调函数的接口类
interface ViewTreeObserver.OnScrollChangedListener
当一个视图树中的一些组件发生滚动时,所要调用的回调函数的接口类
interface ViewTreeObserver.OnTouchModeChangeListener
当一个视图树的触摸模式发生改变时,所要调用的回调函数的接口类
这里我们只用ViewTreeObserver.OnGlobalFocusChangeListener,通过调用ViewTreeObserver实例的addOnGlobalFocusChangeListener方法来给view树设置view的focus发生变化的监听。通过这个监听,可以全局设置一个焦点样式View,覆盖在获取焦点的View上,同时可以进行一些焦点样式View、失焦view和获焦view的动画。这个方法的缺陷是不是特别灵活,如果样式比较丰富,需要大量的if else逻辑支持。
自定义View以实现获焦样式:
刚才提到ViewTreeObserver.addOnGlobalFocusChangeListener这种方式设置获焦状态不够灵活,而且作为一个懒人,我并不愿意创建大量的selector,shape等文件去实现各种各样的样式,这样自定义view就自然而然的成为了我们的选择。
核心类:ColorStateList,StateListDrawable,以下是是ColorStateList的构造函数
public ColorStateList(int[][] states, @ColorInt int[] colors) {
mStateSpecs = states;
mColors = colors;
onColorsChanged();
}
可见他的内部维护了一个状态数组和一个相应的颜色数组,我们可以在代码里setBackgroundColor(ColorStateList);这样在状态改变时,就会相应的改变颜色,StateListDrawable的使用和ColorStateList基本一致:
int[][] states = new int[5][];
states[0] = new int[]{-android.R.attr.state_enabled};//unable
states[1] = new int[]{android.R.attr.state_focused};//focused
states[2] = new int[]{android.R.attr.state_pressed};//pressed
states[3] = new int[]{android.R.attr.state_checked};//checked
states[4] = new int[]{android.R.attr.state_selected};//selected
states[5] = new int[]{android.R.attr.state_enabled};//normal
int[] colors = new int[]{mTextColorUnable, mTextColorFocused, mTextColorPressed, mTextColorSelected, mTextColorNormal};
mTextColorStateList = new ColorStateList(states, colors);
mView.setTextColor(mTextColorStateList);
踩坑:
1.设置focusableInTouchMode = true后点击事件需要点击到第二次才会响应点击,这是因为View的onTouchEvent里面会先去判断当前view是否能获取焦点并focusableInTouchMode = true,如果成立,则requestFocus()并返回,不再往下执行。当前解决办法是自定义view中重写:
@Override
public boolean onTouchEvent(MotionEvent event) {
//解决设置focusableInTouchMode=true后第一次点击事件失效
if (hasFocusable()){
requestFocus();
}
return super.onTouchEvent(event);
}
2.focusable = false的时候focusableInTouchMode一定是false;focusableInTouchMode = false的时候focusable不一定是false。
3.获焦后view放大在linnerlayout和recyclerview中可能展示会有问题,解决方法为重写他们的getChildDrawingOrder:
@Override
protected int getChildDrawingOrder(int childCount, int i) {
if (childCount == 0) {
return super.getChildDrawingOrder(childCount, i);
}
int newOrder = i;
if (getChildAt(i).isFocused()) {
newOrder = childCount - 1;
} else {
if (i == childCount - 1) {
View focusChild = findFocus();
int indexOfChild = indexOfChild(focusChild);
newOrder = (indexOfChild >= 0 ? indexOfChild : i);
}
}
return super.getChildDrawingOrder(childCount, newOrder);
}