GridView xml中设置android:focusable="false"无效的原因

GridView xml中设置android:focusable="false"无效的原因

最近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。