View 和 ViewGroup 的 hasFocusable

本文深入探讨了Android中View和ViewGroup的焦点获取机制,重点讲解了descendantFocusability属性的三种模式以及如何判断View和ViewGroup是否能够获取焦点。内容包括焦点分发的层级优先级,hasFocusable方法的实现逻辑,以及在实际应用中的例子,如AbsListView的OnItemClickListener。理解这一机制对于优化用户交互和事件处理至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在 android 中,焦点的获取和事件差不多,有一个分发机制,一般来说View 树上上层节点的 ViewGroup 比底层节点的 View 有更高的优先级获取焦点,这体现在 ViewGroup 有一个属性 descendantFocusability 可以用来控制焦点获取的优先级。 该属性的值有三种:

  • beforeDescendants:ViewGroup 会优先其子类控件而获取到焦点,如果父控件不获取焦点,子控件才可能会获得焦点
  • afterDescendants:只有当其子类控件不需要获取焦点时,ViewGroup 才可能获取焦点
  • blocksDescendants:viewgroup 会阻塞子类控件获得焦点,无论 ViewGroup 是否获取焦点,子控件都不能够获得焦点

如何判断 View 是否能够获取焦点

因为上面的原因,判断一个 View 是否有可能获得焦点,就与它所有的父节点有关系了,所以 View 的 hasFocusable 方法实现如下:

public boolean hasFocusable() {
    if (!isFocusableInTouchMode()) {
        for (ViewParent p = mParent; p instanceof ViewGroup; p = p.getParent()) {
            final ViewGroup g = (ViewGroup) p;
            if (g.shouldBlockFocusForTouchscreen()) {
                return false;
            }
        }
    }
    return (mViewFlags & VISIBILITY_MASK) == VISIBLE && isFocusable();
}
  1. 在触摸模式下如果不可获取焦点,先遍历 View 的所有父节点,如果有一个父节点设置了阻塞子 View 获取焦点,那么该 View 就不可能获取焦点
  2. 在触摸模式下如果不可获取焦点,并且没有父节点设置阻塞子 View 获取焦点,和在触摸模式下如果可以获取焦点,那么才判断 View 自身的 visiable 和 focusable 属性,来决定是否可以获取焦点,不可见的 View 当然也不能获取焦点,所以只有 visiable 和 focusable 同时为 true,该View 才可能获取焦点

如何判断 ViewGroup 是否能够获取焦点

判断一个 ViewGroup 是否可能获取到焦点,也与它的子 View 有关系了,如果子 View 可以获取焦点,辣么,就可以算作这个 ViewGroup 也可能获取(消耗)焦点,所以 ViewGroup 的 hasFocusable 方法实现如下:

public boolean hasFocusable() {
   if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
        return false;
    }

    if (isFocusable()) {
        return true;
    }

    final int descendantFocusability = getDescendantFocusability();
    if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
        final int count = mChildrenCount;
        final View[] children = mChildren;

        for (int i = 0; i < count; i++) {
            final View child = children[i];
            if (child.hasFocusable()) {
                return true;
            }
        }
    }

    return false;
}

根据上面的源码可知,判断 ViewGroup 是否能够获取焦点就简单多了

  1. 如果 ViewGroup visiable 和 focusable 都为 true,就算能够获取焦点
  2. 如果不满足第 1 点,如果子 View 节点能够获取焦点,该 ViewGroup 也算能获取焦点(通过递归实现)
  3. 否则,就算作不能够获取到焦点
  4. ViewGroup 自己能够获取焦点,完全没有考虑是否在触摸模式的情况,不晓得是不是 google 工程师的 bug,快到 google groups 提问 ^-^!

View 是否能够获取焦点的实际应用

View 是否能够获取焦点,通常在 AbsListView 的 OnItemClickListener,EditText,Button 的使用和 key 事件分发中会应用到。比如 ListView 的 OnItemClickListener,只有在 ListView 的 ItemView 不能获取到 focus 的情况下,才会调用 OnItemClickListener 的 onItemClick 方法。具体代码可以参考 AbsListView 的 onTouchUp 方法:

private void onTouchUp(MotionEvent ev) {
    switch (mTouchMode) {
    case TOUCH_MODE_DOWN:
    case TOUCH_MODE_TAP:
    case TOUCH_MODE_DONE_WAITING:
       ...... 
       该省就省,我是省略代码分割符 ^_^ 
       ......
            if (inList && !child.hasFocusable()) {
                if (mPerformClick == null) {
                    mPerformClick = new PerformClick();
                }

                final AbsListView.PerformClick performClick = mPerformClick;
                performClick.mClickMotionPosition = motionPosition;
                performClick.rememberWindowAttachCount();

                 ......
                 该省就省,我是省略代码分割符 ^_^ 
                 ......
                        mTouchModeReset = new Runnable() {
                            @Override
                            public void run() {
                                mTouchModeReset = null;
                                mTouchMode = TOUCH_MODE_REST;
                                child.setPressed(false);
                                setPressed(false);
                                if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
                                    performClick.run();
                                }
                            }
                        };
                        postDelayed(mTouchModeReset,
                                ViewConfiguration.getPressedStateDuration());
                    } else {
                        mTouchMode = TOUCH_MODE_REST;
                        updateSelectorState();
                    }
                    return;
                } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
                    performClick.run();
                }
            }
        }
         ...... 
         该省就省,我是省略代码分割符 ^_^ 
         ......
}

从上面代码的第 9 行可知,ListView 的 ItemView 的 hasFocusable 方法必须返回 fasle,才会执行 performClick.run(),才会执行到 OnItemClickListener 的 onItemClick 方法。这也就是为什么通常我们在使用 ListView 的 OnItemClickListener 方法的时候要把 Item 布局文件的根节点的 descendantFocusability 属性设置为 blocksDescendants,并且该节点的 focusable 属性不能为 true。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值