最近Tv项目中有个小问题,需要gridview展示内容,但是不可获取焦点,于是xml中设置android:focusable=“false”,设想会成功,但是实际操作,发现还是可以获取焦点,只有在代码中设置setFocusable(false)才起作用,这个问题当时有点搞不懂,带着这个问题,我去从源码中寻找答案。
思路分析:
普通的view在xml设置android:focusable=false,是不会获取焦点的,那gridview会不会在初始化构造的时候就设置成可以获取焦点的呢,带着这个思路去看下构造。
1、首先看GridView的构造函数:
public GridView(Context context) {
this(context, null);
}
public GridView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.gridViewStyle);
}
public GridView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public GridView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
看最有一个构造函数,调用了super父类的构造函数,GridView和ListView一样,都是AbsListView的子类,所以去看AbsListView的构造。
public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initAbsListView();
.....
}
看下 initAbsListView()方法。
private void initAbsListView() {
// Setting focusable in touch mode will set the focusable property to true
setClickable(true);
setFocusableInTouchMode(true);
//......
}
到这里好像看到了setFocusableInTouchMode,好像看到了希望,其实不完全是,这个是设置在触控模式下可获取焦点,但是上面注释说,在touch模式下设置focusable同样会设置可获取焦点的属性,看下其源码:
@Override
public void setFocusableInTouchMode(boolean focusable) {
final T adapter = getAdapter();
//判断adapter是否为空
final boolean empty = adapter == null || adapter.getCount() == 0;
mDesiredFocusableInTouchModeState = focusable;
//设置mDesiredFocusableState 为 FOCUSABLE,这个 mDesiredFocusableState 看意思是期望的获取焦点的状态
if (focusable) {
mDesiredFocusableState = FOCUSABLE;
}
super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
}
从这个方法中我们看到设置 mDesiredFocusableState 为可获取焦点的状态,但是并没有发现其直接设置setFocusable为true,我们再去源码中寻找。
再次整理思路:
从上面的方法中我们发现adapter的内容非常重要,那会不会是在setAdapter中改变了焦点状态呢?
@Override
public void setAdapter(ListAdapter adapter) {
//省略部分代码
super.setAdapter(adapter);
if (mAdapter != null) {
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
mDataChanged = true;
checkFocus();
}
发现了checkFocus(),开心了,找到了目标,继续探寻。该方法在
AdapterView父类中。
void checkFocus() {
final T adapter = getAdapter();
//设置了内容后,empty为false。
final boolean empty = adapter == null || adapter.getCount() == 0;
//focusable为true
final boolean focusable = !empty || isInFilterMode();
//刚才构造的时候,mDesiredFocusableInTouchModeState为true
super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
//重点来了,focusable为true,取 mDesiredFocusableState 的值
super.setFocusable(focusable ? mDesiredFocusableState : NOT_FOCUSABLE);
if (mEmptyView != null) {
updateEmptyStatus((adapter == null) || adapter.isEmpty());
}
}
倒数第三行调用了 super.setFocusable(),传入的是mDesiredFocusableState ,上面已经分析过了,经过构造函数,这个mDesiredFocusableState 为 FOCUSABLE,这样就找到原因了,为什么GridView是可以获取自动获取焦点了。
不过这里为什么用一个mDesiredFocusableState 的值而不是直接focusable传进呢?以及我项目中在setAdapter之前,构造方法调用之后,设置了setFocusable(false),生效了,这里就是源码的精髓,
看下该方法实现:
setFousbale(boolean)调用的是view的方法,最终调用了AdapterView的其override方法。
@Override
public void setFocusable(@Focusable int focusable) {
final T adapter = getAdapter();
final boolean empty = adapter == null || adapter.getCount() == 0;
//这里 mDesiredFocusableState 变成了我传入进来的NOT_FOCUSABLE
mDesiredFocusableState = focusable;
if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) {
mDesiredFocusableInTouchModeState = false;
}
//调用父类的方法,但是此时还是内容为空,所以设置不可获取焦点
super.setFocusable((!empty || isInFilterMode()) ? focusable : NOT_FOCUSABLE);
}
总结:
GridView以及ListView在是否获取焦点的问题上,最重要参考就是我们是否设置了Adapter,以及Adapter是否有内容,如果有内容了,就可以获取焦点,除非我们自己显式的调用了setFocusable为false。