Android ViewGroup.setDescendantFocusability函数

本文详细解析了ViewGroup中用于控制子视图获取焦点能力的机制,包括FOCUS_BEFORE_DESCENDANTS、FOCUS_AFTER_DESCENDANTS和FOCUS_BLOCK_DESCENDANTS三种常量的用途及其实现细节。

这个函数是在ViewGroup里定义的,主要用于控制child View获取焦点的能力,比如是否阻止child View获取焦点。

 

他有三个常量可供设置

 

  1. FOCUS_BEFORE_DESCENDANTS ViewGroup本身先对焦点进行处理,如果没有处理则分发给child View进行处理
  2. FOCUS_AFTER_DESCENDANTS 先分发给Child View进行处理,如果所有的Child View都没有处理,则自己再处理
  3. FOCUS_BLOCK_DESCENDANTS ViewGroup本身进行处理,不管是否处理成功,都不会分发给ChildView进行处理

我们看下这个方法的实现
public void setDescendantFocusability(int focusability) {
        switch (focusability) {
            case FOCUS_BEFORE_DESCENDANTS:
            case FOCUS_AFTER_DESCENDANTS:
            case FOCUS_BLOCK_DESCENDANTS:
                break;
            default:
                throw new IllegalArgumentException("must be one of FOCUS_BEFORE_DESCENDANTS, "
                        + "FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS");
        }
        mGroupFlags &= ~FLAG_MASK_FOCUSABILITY;
        mGroupFlags |= (focusability & FLAG_MASK_FOCUSABILITY);
    }
 

 

可以看到,只有这三个常量可以设置,不是这三个常量会抛出异常的。

 

 

设置后,会在requestFocus(int direction, Rect previouslyFocusedRect) 方法里根据设置进行相应的处理。来看下实现

 

public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        if (DBG) {
            System.out.println(this + " ViewGroup.requestFocus direction="
                    + direction);
        }
        int descendantFocusability = getDescendantFocusability();

        switch (descendantFocusability) {
            case FOCUS_BLOCK_DESCENDANTS:
                return super.requestFocus(direction, previouslyFocusedRect);
            case FOCUS_BEFORE_DESCENDANTS: {
                final boolean took = super.requestFocus(direction, previouslyFocusedRect);
                return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
            }
            case FOCUS_AFTER_DESCENDANTS: {
                final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
                return took ? took : super.requestFocus(direction, previouslyFocusedRect);
            }
            default:
                throw new IllegalStateException("descendant focusability must be "
                        + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
                        + "but is " + descendantFocusability);
        }
    }

 

通过这里的实现可以看到上面定义的三个常量设置的意思。。

 

package com.st.launcher.util; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import androidx.viewpager.widget.ViewPager; public class ControlSlideViewPager extends ViewPager { public ControlSlideViewPager(Context context) { super(context); } public ControlSlideViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return false; } @Override public boolean onTouchEvent(MotionEvent ev) { return false; } } public ViewPager(@NonNull Context context) { super(context); initViewPager(); } public ViewPager(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); initViewPager(); } void initViewPager() { setWillNotDraw(false); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setFocusable(true); final Context context = getContext(); mScroller = new Scroller(context, sInterpolator); final ViewConfiguration configuration = ViewConfiguration.get(context); final float density = context.getResources().getDisplayMetrics().density; mTouchSlop = configuration.getScaledPagingTouchSlop(); mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mLeftEdge = new EdgeEffect(context); mRightEdge = new EdgeEffect(context); mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); mCloseEnough = (int) (CLOSE_ENOUGH * density); mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density); ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate()); if (ViewCompat.getImportantForAccessibility(this) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); } ViewCompat.setOnApplyWindowInsetsListener(this, new androidx.core.view.OnApplyWindowInsetsListener() { private final Rect mTempRect = new Rect(); @Override public WindowInsetsCompat onApplyWindowInsets(final View v, final WindowInsetsCompat originalInsets) { // First let the ViewPager itself try and consume them... final WindowInsetsCompat applied = ViewCompat.onApplyWindowInsets(v, originalInsets); if (applied.isConsumed()) { // If the ViewPager consumed all insets, return now return applied; } // Now we'll manually dispatch the insets to our children. Since ViewPager // children are always full-height, we do not want to use the standard // ViewGroup dispatchApplyWindowInsets since if child 0 consumes them, // the rest of the children will not receive any insets. To workaround this // we manually dispatch the applied insets, not allowing children to // consume them from each other. We do however keep track of any insets // which are consumed, returning the union of our children's consumption final Rect res = mTempRect; res.left = applied.getSystemWindowInsetLeft(); res.top = applied.getSystemWindowInsetTop(); res.right = applied.getSystemWindowInsetRight(); res.bottom = applied.getSystemWindowInsetBottom(); for (int i = 0, count = getChildCount(); i < count; i++) { final WindowInsetsCompat childInsets = ViewCompat .dispatchApplyWindowInsets(getChildAt(i), applied); // Now keep track of any consumed by tracking each dimension's min // value res.left = Math.min(childInsets.getSystemWindowInsetLeft(), res.left); res.top = Math.min(childInsets.getSystemWindowInsetTop(), res.top); res.right = Math.min(childInsets.getSystemWindowInsetRight(), res.right); res.bottom = Math.min(childInsets.getSystemWindowInsetBottom(), res.bottom); } // Now return a new WindowInsets, using the consumed window insets return applied.replaceSystemWindowInsets( res.left, res.top, res.right, res.bottom); } }); } ------------------------ 这是viewpager的代码 package com.st.launcher.util; import android.content.Context; import android.content.res.Resources; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.ViewConfiguration; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.contains; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; import androidx.viewpager.widget.ViewPager; import java.lang.reflect.Field; @RunWith(org.powermock.modules.junit4.PowerMockRunner.class) @org.powermock.core.classloader.annotations.PrepareForTest({ControlSlideViewPager.class, ViewConfiguration.class}) public class ControlSlideViewPagerTest { @Test public void testOnInterceptTouchEvent_ReturnsFalse() throws Exception{ mockStatic(ViewConfiguration.class); // 创建一个 mock 的 Context(虽然不会实际使用) Context mockContext = Mockito.mock(Context.class); when(ViewConfiguration.get(any())).thenReturn(mock(ViewConfiguration.class)); ViewPager mockViewPager = mock(ViewPager.class); Context mock = mock(Context.class); DisplayMetrics displayMetrics = mock(DisplayMetrics.class); Resources resources = mock(Resources.class); when(mockViewPager.getContext()).thenReturn(mock); when(mock.getResources()).thenReturn(resources); when(resources.getDisplayMetrics()).thenReturn(displayMetrics); Field declaredField = DisplayMetrics.class.getDeclaredField("density"); declaredField.setAccessible(true); declaredField.set(displayMetrics,1); // 构造被测试的对象 ControlSlideViewPager viewPager = new ControlSlideViewPager(mockContext); // 调用方法 MotionEvent mockEvent = Mockito.mock(MotionEvent.class); boolean result = viewPager.onInterceptTouchEvent(mockEvent); // 验证返回值 assertFalse(result); } @Test public void testOnTouchEvent_ReturnsFalse() { // 创建一个 mock 的 Context Context mockContext = Mockito.mock(Context.class); // 构造被测试的对象 ControlSlideViewPager viewPager = new ControlSlideViewPager(mockContext); // 调用方法 MotionEvent mockEvent = Mockito.mock(MotionEvent.class); boolean result = viewPager.onTouchEvent(mockEvent); // 验证返回值 assertFalse(result); } @Test public void testConstructor_TwoParams_DoesNotThrow() { // 创建 mocks Context mockContext = Mockito.mock(Context.class); AttributeSet mockAttrs = Mockito.mock(AttributeSet.class); // 构造对象 ControlSlideViewPager viewPager = new ControlSlideViewPager(mockContext, mockAttrs); // 如果没有抛出异常,则测试通过 } } 为什么这样mock还是会报错
最新发布
07-30
time E FATAL EXCEPTION: main Process: com.example.kucun2, PID: 27145 java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.view.GestureDetector.onTouchEvent(android.view.MotionEvent)' on a null object reference at com.example.kucun2.View.HorizontalScrollTextView.onTouchEvent(HorizontalScrollTextView.java:99) at android.view.View.performOnTouchCallback(View.java:16744) at android.view.View.dispatchTouchEvent(View.java:16620) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at android.view.ViewGroup.dispatchTransformedTouchEventInternal(ViewGroup.java:3198) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3141) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2803) at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:609) at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:2093) at android.app.Activity.dispatchTouchEvent(Activity.java:4790) 2025-06-06 18:18:26.085 12182-12182 AndroidRuntime E FATAL EXCEPTION: main Process: com.example.kucun2, PID: 12182 java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at com.example.kucun2.ui.dingdan.OrderDisplayFragment.addTableRow(OrderDisplayFragment.java:127) at com.example.kucun2.ui.dingdan.OrderDisplayFragment.fillTableData(OrderDisplayFragment.java:94) at com.example.kucun2.ui.dingdan.OrderDisplayFragment.onCreateView(OrderDisplayFragment.java:57)
06-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值