之前一直在手机端做开发,换了份工作,要接触车机端,车机和手机端最大的区别就是焦点态的处理,参考了很多开源项目对于焦点态的处理,但都不太符合我的需求。
1.解决的需求
我们的app界面划分几个区域,由6个按键控制,其中300、301主要是在区域里面移动焦点,上下左右是在区域与区域之间切换焦点,之前我们的解决方案是把每个焦点手动加到指定区域,可以实现我们大部分需求,但是过于繁琐,代码量也比较多,设计一套自动扫描布局文件里面的控件到指定区域里面,并且重写ListView、GridView等特殊控件来满足item里面焦点态切换符合需求。
2.方法
首先需要了解的几个知识点:
a.按键事件分发传递的几个方法
activity onkeydown()->view diapatchEvent()->子view dispatchEvent()
b.父控件设置的一个属性对焦点移动影响
android:descendantFocusability
beforeDescendants:父容器会比其子控件率先获得焦点。
afterDescendants:如果没有任何子控件要获得焦点的话,那么父容器才会获得焦点。
blocksDescendants:父容器会阻止其子控件获得焦点(也就是说焦点会由父容器获得)。
c.遍历布局文件下面控件的顺序
这个遍历先后顺序主要是在xml里面编写控件代码先后决定的,这个主要影响后面查找获取焦点控件的顺序。
d.ListView、GridView的特殊处理
针对普通控件焦点态的处理逻辑在activity的onkeyDown方法里面就可以,像ListView、GridView之类设计到item上下切换并且item里面可能还有子控件的焦点态,所以需要在重写这些view的ondispatchEvent方法。
针对第一点自动化扫描将相应的控件添加到指定的焦点区域,定义一个自定义的view继承RelativeLayout、LinearLayout等普通布局代替系统原生的布局,这样我们就可以遍历将不同的控件划分到指定区域。
对于设置父控件android:descendantFocusability主要是用于ListView、GridView等控件item里面焦点的切换使用。
3.目前存在的问题和优化的地方
1.划分焦点区域的焦点态自定义布局不能嵌套
2.对于ListView、GridView等焦点态需要重写点击事件,不能用本身的onItemClick事件
4.部分关键代码
/**
* 初始化焦点态数据
*
* @param mContentView
*/
public void initFocusData(View mContentView) {
if (!isOpenFocus) {
return;
}
this.curItemPosition = 0;
this.lastFocusedView = null;
if (focusBeanList != null && focusBeanList.size() > 0) {
focusBeanList.clear();
}
if (focusViewList != null && focusViewList.size() > 0) {
focusViewList.clear();
}
getFocusViews((ViewGroup) mContentView);
// 根据Tag来区分焦点态大区并排序
Collections.sort(focusViewList, new Comparator<View>() {
@Override
public int compare(View lhs, View rhs) {
if (lhs.getTag() != null && rhs.getTag() != null) {
return (lhs.getTag().toString()).compareTo(rhs.getTag().toString());
} else {
return 0;
}
}
});
for (int i = 0; i < focusViewList.size(); i++) {
getFocusData(focusViewList.get(i), ((FocusValueCallback) focusViewList.get(i)).getFocusData());
}
}
/**
* 添加可以获取焦点view到集合中
*
* @param viewGroup
*/
private void getFocusViews(ViewGroup viewGroup) {
if (viewGroup == null) {
return;
}
if (viewGroup instanceof FocusValueCallback) {
focusViewList.add(viewGroup);
return;
}
int count = viewGroup.getChildCount();
for (int i = 0; i < count; i++) {
View view = viewGroup.getChildAt(i);
if (view instanceof FocusValueCallback) {
focusViewList.add(view);
} else if (view instanceof ViewGroup) {
getFocusViews((ViewGroup) view);
}
}
}
/**
* 获取上个有效的焦点view(循环查找)
*
* @param index
* @return
*/
private View getBeforeFocusView(int index) {
for (int i = index; i < focusBeanList.size(); i--) {
View view = focusBeanList.get((i - 1 + focusBeanList.size()) % focusBeanList.size()).getView();
if (view.isShown() && view.isFocusable()) {
return view;
}
}
return null;
}
/**
* 获取下个有效的焦点view(循环查找)
*
* @param index
* @return
*/
private View getNextFocusView(int index) {
for (int i = index; i < focusBeanList.size(); i++) {
View view = focusBeanList.get((i + 1) % focusBeanList.size()).getView();
if (view.isShown() && view.isFocusable()) {
return view;
}
}
return null;
}