源码看CoordinatorLayout.Behavior原理

2023-11-09

http://blog.csdn.net/qibin0506/article/details/50377592

在上一篇博客CoordinatorLayout高级用法-自定义Behavior中,我们介绍了如何去自定义一个CoordinatorLayout的Behavior,通过文章也可以看出Behavior在CoordinatorLayout中地位是相当高的,那么今天我们就来接着上篇博客来从源码分析一下Behavior的实现思路,如果你对CoordinatorLayout和Behavior还不熟悉的话,建议先去看看上篇博客《CoordinatorLayout高级用法-自定义Behavior》

这篇文章我们要分析的内容有:

  1. Behavior的实例化
  2. layoutDependsOn和onDependentViewChanged调用过程
  3. onStartNestedScroll和onNestedPreScroll实现原理
  4. Behavior的事件分发过程

Behavior的实例化

大家都知道,我们在view中可以通过app:layout_behavior然后指定一个字符串来表示使用哪个behavior,稍微去想一下,在CoordinatorLayout中肯定是利用反射机制来完成的behavior的实例化,现在就让我们从CoordinatorLayout的源码中找寻答案,来验证我们的猜想。首先,我们来看看CoordinatorLayout的一个内部类,也是大家熟悉的LayoutParams

public static class LayoutParams extends ViewGroup.MarginLayoutParams {
       /**
        * A {@link Behavior} that the child view should obey.
        */
       Behavior mBehavior;
       ...
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里我们确实看到了behavior的影子,那它是在什么时候被初始化的呢?继续看代码,

LayoutParams(Context context, AttributeSet attrs) {
    super(context, attrs);

    final TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.CoordinatorLayout_LayoutParams);
    ...
    mBehaviorResolved = a.hasValue(
            R.styleable.CoordinatorLayout_LayoutParams_layout_behavior);
    if (mBehaviorResolved) {
        mBehavior = parseBehavior(context, attrs, a.getString(
                R.styleable.CoordinatorLayout_LayoutParams_layout_behavior));
    }

    a.recycle();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在LayoutParams的构造方法中,首先是去检查了是不是有layout_behavior,这里很容易理解,接下来调用了parseBehavior方法,返回了Behavior的实例,我们非常有理由去看看parseBehavior到底干了嘛,或许我们要的答案就在里面!

// 这里是指定的Behavior的参数类型
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
        Context.class,
        AttributeSet.class
};

...

static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
    if (TextUtils.isEmpty(name)) {
        return null;
    }

    // 代表了我们指定的那个behavior的完整路径
    final String fullName;
    // 如果是".MyBehavior"
    // 则在前面加上程序的包名
    if (name.startsWith(".")) {
        // Relative to the app package. Prepend the app package name.
        fullName = context.getPackageName() + name;
    } else if (name.indexOf('.') >= 0) {
        // 这里我们指定了全名
        // Fully qualified package name.
        fullName = name;
    } else {
        // Assume stock behavior in this package (if we have one)
        fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
                ? (WIDGET_PACKAGE_NAME + '.' + name)
                : name;
    }

    try {
        Map<String, Constructor<Behavior>> constructors = sConstructors.get();
        if (constructors == null) {
            constructors = new HashMap<>();
            sConstructors.set(constructors);
        }
        Constructor<Behavior> c = constructors.get(fullName);
        // 这里利用反射去实例化了指定的Behavior
        // 并且值得注意到是,这里指定了构造的参数类型
        // 也就是说我们在自定义Behavior的时候,必须要有这种类型的构造方法
        if (c == null) {
            final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
                    context.getClassLoader());
            c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
            c.setAccessible(true);
            constructors.put(fullName, c);
        }
        return c.newInstance(context, attrs);
    } catch (Exception e) {
        throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

上面的代码很容易理解,就是利用反射机制去实例化了Behavior,调用的是两个参数的那个构造方法,这也就是我们在自定义Behavior的时候为什么一定要去重写,

public Behavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

这个构造的原因。看来获取一个Behavior的实例还是很简单的,那么,下面就让我们开始分析Behavior中常用方法调用的机制吧。

layoutDependsOn和onDependentViewChanged调用过程

在上一篇博客中我们学会了自定义两种形式的Behavior,其中第一种就是去观察一个view的状态变化,也就是涉及到layoutDependsOnonDependentViewChanged两个方法的调用,现在我们从源码的角度来分析一下这两个方法调用的时机和调用的过程,在前一篇博客中我们提到过onDependentViewChanged这个方法会在view的状态发生变化后去调用,那在状态发生变化时必定会执行什么操作呢?重绘,是的,状态变化了,那肯定重绘是避免不了的,在CoordinatorLayout中注册了一个ViewTreeObserver,我们可以从这里入手,因为它可以监听到view的各种状态变化,

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    resetTouchBehaviors();
    if (mNeedsPreDrawListener) {
        if (mOnPreDrawListener == null) {
            // 实例化了OnPreDrawListener
            // 并在下面注册到了ViewTreeObserver中
            mOnPreDrawListener = new OnPreDrawListener();
        }
        final ViewTreeObserver vto = getViewTreeObserver();
        vto.addOnPreDrawListener(mOnPreDrawListener);
    }
    if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {
        // We're set to fitSystemWindows but we haven't had any insets yet...
        // We should request a new dispatch of window insets
        ViewCompat.requestApplyInsets(this);
    }
    mIsAttachedToWindow = true;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

onAttachedToWindow向ViewTreeObserver注册了一个监听draw变化的Observer,那在这里Observer中到底干了嘛呢?

class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
    @Override
    public boolean onPreDraw() {
        dispatchOnDependentViewChanged(false);
        return true;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

就两行代码,调用了dispatchOnDependentViewChanged方法,看方法名我们就知道这次找对对象了,怀着激动的心情来看看dispatchOnDependentViewChanged

void dispatchOnDependentViewChanged(final boolean fromNestedScroll) {
    final int layoutDirection = ViewCompat.getLayoutDirection(this);
    final int childCount = mDependencySortedChildren.size();
    // 遍历所有的子view
    for (int i = 0; i < childCount; i++) {
        final View child = mDependencySortedChildren.get(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        ...

        // Did it change? if not continue
        // 检查是否变化了,没有变化直接下一次循环
        final Rect oldRect = mTempRect1;
        final Rect newRect = mTempRect2;
        getLastChildRect(child, oldRect);
        getChildRect(child, true, newRect);
        if (oldRect.equals(newRect)) {
          continue;
        }

        // Update any behavior-dependent views for the change
        // 这里从下一个子view开始
        //mDependencySortedChildren有一个排序规则
        // selectionSort
        // 感兴趣的可以看一下mDependencySortedChildren部分。
        for (int j = i + 1; j < childCount; j++) {
            final View checkChild = mDependencySortedChildren.get(j);
            final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
            // 获取到Behavior
            final Behavior b = checkLp.getBehavior();
            // 这里调用Behavior的layoutDependsOn来判断我们的带有behavior的view是不是依赖这个view
            if (b != null && b.layoutDependsOn(this, checkChild, child)) {
                if (!fromNestedScroll && checkLp.getChangedAfterNestedScroll()) {
                    // If this is not from a nested scroll and we have already been changed
                    // from a nested scroll, skip the dispatch and reset the flag
                    checkLp.resetChangedAfterNestedScroll();
                    continue;
                }

                // 这里调用了Behavior的onDependentViewChanged
                final boolean handled = b.onDependentViewChanged(this, checkChild, child);
                ...
            }
        }
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

dispatchOnDependentViewChanged方法有一个布尔类型的参数,上面我们传递的是false, 这里主要是区分是view引起的状态变化还是布局引起的,在一些的scroll中也会调用dispatchOnDependentViewChanged这个方法。

好了,现在我们终于搞懂了onDependentViewChanged调用机制了,下面我们来看看关于滑动监听的部分。

onStartNestedScroll和onNestedPreScroll实现原理

在开始源码之前,我们先来思考个问题,现在有一个view是可以上下滑动的,那这个view的滑动对于父view来说是不是可见的?或者说是可预知的?显然不是,一个view的滑动对于父布局来说是透明的?所以现在我们不能简简单单的从CoordinatorLayout入手了,而是要从那个可以滑动的view入手,我们选择NestedScrollView来进行分析。NestedScrollView有一个NestedScrollingChildHelper类型的变量mChildHelper引起了我们的注意,因为很多看名字很像关于滑动部分的代码都调用了这个类的一些方法,来看看有哪些吧?

mChildHelper = new NestedScrollingChildHelper(this);

...

@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
    mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
    startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
}

@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
        int dyUnconsumed) {
    final int oldScrollY = getScrollY();
    scrollBy(0, dyUnconsumed);
    final int myConsumed = getScrollY() - oldScrollY;
    final int myUnconsumed = dyUnconsumed - myConsumed;
    dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
}

@Override
public boolean startNestedScroll(int axes) {
    return mChildHelper.startNestedScroll(axes);
}

@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
        int dyUnconsumed, int[] offsetInWindow) {
    return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
            offsetInWindow);
}

@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
    return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

很简单,不过我们好像发现了一点眉目,这些方法何时调用我们还是不是很清楚,滑动必然和事件有关,我们就来从事件的部分入手吧,毕竟是我们熟悉的地方。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  ...
  switch (action & MotionEventCompat.ACTION_MASK) {
      ...
     case MotionEvent.ACTION_DOWN: {
       ...
       startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
     }
     ...
  }
  ...
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在down的时候我们调用了startNestedScroll方法,那我们就顺着这条线往下看mChildHelper.startNestedScroll(axes)

public boolean startNestedScroll(int axes) {
    if (hasNestedScrollingParent()) {
        // Already in progress
        return true;
    }
    if (isNestedScrollingEnabled()) {
       // 获取当前view的parent
        ViewParent p = mView.getParent();
        View child = mView;
        // 一个循环,不断的往上层去获取parent
        // 直到条件成立,或者没有parent了 退出
        while (p != null) {
            // 这里是关键代码,猜测这里肯定肯定去调用了CoordinatorLayout的对应方法。
            if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
                mNestedScrollingParent = p;
                ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
                return true;
            }
            if (p instanceof View) {
                child = (View) p;
            }
            // 替换,继续循环
            p = p.getParent();
        }
    }
    return false;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

在这个方法中一个while循环,不断的去获取view的的parent,然后一个ViewParentCompat.onStartNestedScroll作为条件成立了就return true了,我们有理由猜测ViewParentCompat.onStartNestedScroll里去调用了CoordinatorLayout的相应方法。注意参数,p是我们遍历到父view,我们先认为是CoordinatorLayout吧,child是CoordinatorLayout的直接嵌套着目标view的子view,mView在这里就是NestedScrollView了。

public class ViewParentCompat {
   static class ViewParentCompatStubImpl implements ViewParentCompatImpl {
     @Override
      public boolean onStartNestedScroll(ViewParent parent, View child, View target,
             int nestedScrollAxes) {
           if (parent instanceof NestedScrollingParent) {
               return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
                       nestedScrollAxes);
           }
           return false;
      }
   }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这里面很简单,看看parent是不是NestedScrollingParent类型的,如果是,则调用了onStartNestedScroll这个方法,而我们的CoordinatorLayout肯定是实现了NestedScrollingParent接口的,

public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent { }
 
 
  • 1
  • 1

好了,现在我们终于回到CoordinatorLayout了,来看看他的onStartNestedScroll方法,

public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
    boolean handled = false;

    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View view = getChildAt(i);
        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
        final Behavior viewBehavior = lp.getBehavior();
        if (viewBehavior != null) {
            // 调用遍历出来的这个子view的onStartNestedScroll方法
            final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
                    nestedScrollAxes);
            handled |= accepted;

            lp.acceptNestedScroll(accepted);
        } else {
            lp.acceptNestedScroll(false);
        }
    }
    return handled;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这里还是去遍历了所有子view,然后去调用它的onStartNestedScroll方法,它的返回值,决定了NestedScrollingChildHelper.onStartNestedScroll是不是要继续遍历,如果我们的子view对这个view的滑动感兴趣,就返回true,它的遍历就会结束掉。

好了,现在start的过程我们分析完了,大体的流程就是:

NestedScrollView.onInterceptTouchEvent->NestedScrollingChildHelper.onStartNestedScroll->CoordinatorLayout.onStartNestedScroll

下面的各种滑动调用流程也是一样的,这里我们就不再重复分析了,感兴趣的可以自己去看一下源码。

Behavior的事件分发过程

上面的分析其实已经将我们自定义Behavior中使用到的方法的调用流程分析完了,不过我们还是要拓展一下,其实Behavior也是支持事件的传递的,在这方面,Behavior好像是一个代理一样,在CoordinatorLayout的各种事件处理的方法中去调用Behavior的事件处理方法,返回值决定了CoordinatorLayout对事件的消费情况。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    MotionEvent cancelEvent = null;

    final int action = MotionEventCompat.getActionMasked(ev);

    // Make sure we reset in case we had missed a previous important event.
    if (action == MotionEvent.ACTION_DOWN) {
        resetTouchBehaviors();
    }

    // 去看看子view中behavior是有要拦截
    // 如果要拦截,则我们要拦截
    // 在这里Behavior类似一个代理
    final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);

    if (cancelEvent != null) {
        cancelEvent.recycle();
    }

    if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
        resetTouchBehaviors();
    }

    return intercepted;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这里面调用了performIntercept方法,而且指定了个常量TYPE_ON_INTERCEPT代表了我们在拦截阶段调用的,既然有区分,肯定在别的地方也有调用,答案是肯定的,在onTouch里也有对performIntercept的调用,

@Override
public boolean onTouchEvent(MotionEvent ev) {
    boolean handled = false;
    boolean cancelSuper = false;
    MotionEvent cancelEvent = null;

    final int action = MotionEventCompat.getActionMasked(ev);

    // 这里要说道说道
    // 两个条件:1 如果behavior想要拦截
    // 2 behavior的onTouchEvent返回true
    // 为什么会有两个条件呢?
    // 解答:第一个条件是正常的分发流程, 很容易理解
    //
    // 第二个条件是在没有子view消费事件,所以事件会冒泡到此
    // 这时,我们还要继续询问behavior是否要消费该事件
    // 这里在performIntercept中执行的是:
    //  case TYPE_ON_TOUCH: // 从onTouchEvent调用的
    // intercepted = b.onTouchEvent(this, child, ev);
    // break;
    // 当intercepted为true时,表示我们对该down事件感兴趣
    // 此时 mBehaviorTouchView也有了赋值
    if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
        // Safe since performIntercept guarantees that
        // mBehaviorTouchView != null if it returns true
        final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
        final Behavior b = lp.getBehavior();
        if (b != null) {
            // 这里同样的事件会继续执行一遍onTouchEvent?
            handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
        }
    }

    // 如果behavior不感兴趣
    // 轮到自己了,问问自己干不感兴趣
    // Keep the super implementation correct
    if (mBehaviorTouchView == null) {
        handled |= super.onTouchEvent(ev);
    } else if (cancelSuper) {
        // 如果behavior执行了事件(并不是拦截了事件,上面的第一个if的第一个条件不成立,第二个条件成立)
        // 能执行到这,说明behavior没有拦截事件,但在事件冒泡的过程中消费了事件
        // mBehaviorTouchView是在performIntercept(ev, TYPE_ON_TOUCH)赋值的
        // 则给自己执行一个cancel事件
        if (cancelEvent == null) {
            final long now = SystemClock.uptimeMillis();
            cancelEvent = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
        }
        super.onTouchEvent(cancelEvent);
    }

    if (!handled && action == MotionEvent.ACTION_DOWN) {

    }

    if (cancelEvent != null) {
        cancelEvent.recycle();
    }

    if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
        resetTouchBehaviors();
    }

    return handled;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

恩,这里面的代码注释已经写的很明白了,但是需要注意的一点,这一点我很长时间没有相通,就是为什么还要在onTouch里还要调用一遍performIntercept,是这样的,假如现在事件没有任何子view去消费,那么事件会冒泡到此,本着把Behavior看作是一个代理的原则,这里肯定还是要去询问一下Behavior是不是要执行这个事件,注意这里说的是执行而不是拦截,这是因为performIntercept不仅仅会调用Behavior的拦截部分的代码,也会调用执行的代码,就是通过第二个参数区分的。可以看到,这里我们使用了TYPE_ON_TOUCH。 
好了,说了这么多performIntercept,是时候来看看performIntercept的代码了。

private boolean performIntercept(MotionEvent ev, final int type) {
    boolean intercepted = false;
    boolean newBlock = false;

    MotionEvent cancelEvent = null;

    final int action = MotionEventCompat.getActionMasked(ev);

    final List<View> topmostChildList = mTempList1;
    getTopSortedChildren(topmostChildList);

    // Let topmost child views inspect first
    final int childCount = topmostChildList.size();
    for (int i = 0; i < childCount; i++) {
        final View child = topmostChildList.get(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        final Behavior b = lp.getBehavior();

        // 如果现在已经有拦截了的
        // 并且现在是down
        // 则 所有的behavior会受到一个cancel事件
        if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
            // Cancel all behaviors beneath the one that intercepted.
            // If the event is "down" then we don't have anything to cancel yet.
            if (b != null) {
                if (cancelEvent == null) {
                    final long now = SystemClock.uptimeMillis();
                    cancelEvent = MotionEvent.obtain(now, now,
                            MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                }
                switch (type) {
                    case TYPE_ON_INTERCEPT: // 从onInterceptTouchEvent调用的
                        b.onInterceptTouchEvent(this, child, cancelEvent);
                        break;
                    case TYPE_ON_TOUCH: // 从onTouch调用的
                        b.onTouchEvent(this, child, cancelEvent);
                        break;
                }
            }
            continue;
        }

        // 如果现在还没有拦截 并且具有behavior
        if (!intercepted && b != null) {
            switch (type) {
                case TYPE_ON_INTERCEPT: // 从onInterceptTouchEvent调用的
                    intercepted = b.onInterceptTouchEvent(this, child, ev);
                    break;
                case TYPE_ON_TOUCH: // 从onTouchEvent调用的
                    intercepted = b.onTouchEvent(this, child, ev);
                    break;
            }
            if (intercepted) {
                mBehaviorTouchView = child;
            }
        }

        // Don't keep going if we're not allowing interaction below this.
        // Setting newBlock will make sure we cancel the rest of the behaviors.
        final boolean wasBlocking = lp.didBlockInteraction();
        final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);
        newBlock = isBlocking && !wasBlocking;
        // 如果不允许继续分发,则直接退出
        if (isBlocking && !newBlock) {
            // Stop here since we don't have anything more to cancel - we already did
            // when the behavior first started blocking things below this point.
            break;
        }
    }

    topmostChildList.clear();

    return intercepted;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

这里面的代码也很容易理解,就是去遍历所有的view,在不同的情景下调用Behavior的onInterceptTouchEvent或onTouch方法。

好了关于Behavior的源码我们就分析到这里,相信大家在看完之后会对Behavior有一个全新的认识,而且google已经建议我们使用support design的东西了(没发现现在的项目默认模板文件就是一个标准的support design布局吗),所以我们还是有必要对新东西有个更加深入的认识,而且这样也会有助于我们理解google工程师的思路,在解决一些问题的时候我们完全可以参考一下这些思路。 
ok,不扯了,今天就到这里吧,拜拜。

提问:
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final Behavior viewBehavior = lp.getBehavior();

这是View的类型为什么为:AppBarLayout而不是设备NestedScrollView

回答:

这里的类型确实是AppBarLayout
CoordinatorLayout-->onMeasure-->prepareChildren-->getResolvedLayoutParams
在getResolvedLayoutParams中通过注解给AppBarLayout设置了Behavior.
而CoordinatorLayout中onStartNestedScroll的View view = getChildAt(0);就是AppBarLayout。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

源码看CoordinatorLayout.Behavior原理 的相关文章

  • 【长文预警】美团联合创始人王慧文清华产品课

    前言 一 成功和失败的产品 一般来说在一个领域里一款产品的成功对应着无数产品的失败 根据老王个人的经验 成功和失败的比例大约是1 30 失败的原因多种多样 有些啥都没做对 有些作对了一部分 这里列举的失败案例主要讲做对了一部分的 准确说算是
  • sonar.java.binaries的配置

    从sonarQube 4 12开始 sonar将会进行程序的动态检查 不配置sonar java binaries属性将会出错 From SonarJava version 4 12 binary files are required fo
  • 在 Mac 上使用 VMware 安装 Windows 11

    因为项目原因 需要在 windows 环境下测试一下 electron 的表现 于是就记录一下在 mac 虚拟机上安装 windows 的体验 总体来说难度不大 我电脑的情况 2020 款 macbook pro 16g 512g 前期准备
  • golang版本管理gvm

    今天小土带来一篇关于Go版本管理器gvm的小短文 废话不多说 开始安装 安装 如果你使用的mac mac 需要先安装xcode select 没安装过的同学可以按照如下命令进行执行安装 这里不做太多说明了 xcode select inst
  • 地震逃生【最大流模板题】

    题目链接 P1343 地震逃生 简单的最大流的模板 小心 0 的RE情况 读题 另外 写的是ISAP include
  • 微信小程序授权登录页面(有提示窗)

    微信小程序授权登录 有弹窗提示 1 效果显示 1 1 授权登录页面 1 2 授权登录提示弹窗 1 3 拒绝授权登录 1 4 允许登录后 跳转到小程序首页 2 代码 2 1 wxml 文件
  • 日志审计功能实现

    1 前言 日志审计功能就是将用户进行的增加 修改和删除操作内容 操作方法 操作人以及操作时间等统一格式后集中放入数据库存储 这样做是为了提高系统的安全性 方便系统发生事故后的溯源和恢复 2 日志审计实现 2 1 设计数据库 下图为数据库中的
  • react基础06--react综合案例-电商网站导航

    react基础06 react综合案例 电商网站导航 1 介绍 2 案例设计模块 2 1 分类导航数据模型设计 2 2 一级分类导航切换高亮效果 2 3 显示二级分类导航 2 4 路由跳转到二级导航的商品列表 2 5 商品搜索 3 注意事项
  • Go语言面试题--基础语法(29)

    文章目录 1 下面的代码有什么问题 2 下面代码最后一行输出什么 请说明原因 3 下面代码有什么问题 4 下面的代码输出什么 1 下面的代码有什么问题 func main data int 1 2 3 i 0 i fmt Println d
  • U-Boot启动流程详解

    参考 U Boot顶层目录链接脚本文件 u boot lds 介绍 作者 一只青木呀 发布时间 2020 10 23 13 52 23 网址 https blog csdn net weixin 45309916 article detai
  • VAR模型

    文章目录 一 VAR是什么 1 引入库 2 读入数据 3 执行程序 总结 一 VAR是什么 以金融价格为例 传统的时间序列模型比如ARIMA ARIMA GARCH等 只分析价格自身的变化 模型的形式为 其中称为自身的滞后项 但是VAR模型
  • Navicat安装教程

    众所周知 Navicat是一款轻量级的用于MySQL连接和管理的工具 非常好用 使用起来方便快捷 简洁 下面我会简单的讲一下其安装以及使用的方法 并且会附带相关的永久安装教程 简介 一般我们在开发过程中是离不开数据库的 Navicat是一款
  • svn: Can't find a temporary directory 问题解决

    Error Can t find temporary directory internal error 然后试了下其他的SVN源 发现均无法提交 并且update时也出现上面的错误信息 对比项目文件时出现 不能创建目录或文件 公司网站首页正
  • Nginx集群+websocket 获取websocket客户端的真实IP

    Nginx增加如下配置 proxy set header Host host proxy set header X Real IP remote addr proxy set header REMOTE HOST remote addr p
  • UMI多环境配置

    一般来说项目不止有dev和prod两个环境 umi可以通过环境变量 UMI ENV 区分不同环境来指定配置 需求 三套环境 gt 本地测试环境local 测试发布环境testbuild 正式环境probuild 1 在根目录新建 umirc
  • 云计算基础——云计算与移动互联网、物联网

    8 1 云计算与移动互联网 8 1 1 移动互联网的发展概况 移动互联网的发展概况 移动互联网是指以宽带IP为技术核心 可同时提供语音 数据 多媒体等业务服务的开什么是移动互联网 放式基础电信网络 从用户行为角度来看 移动互联网广义上是指用
  • shell命令以及运行原理(详解)

    Linux严格意义上说的是一个操作系统 我们称之为 核心 kernel 但我们一般用户 不能直接使用kernel 而是通过kernel的 外壳 程序 也就是所谓的shell 来与kernel沟通 1 从技术角度 Shell的最简单定义 命令
  • Redis实践(一):安装和部署

    memcache redis mongodb 是目前常用的内存数据库 他们应用的场景大致如下 redis 数据量较小的更性能操作和运算上 memcache 用于在动态系统中减少数据库负载 提升性能 做缓存 提高性能 适合读多写少 对于数据量

随机推荐

  • __builtin_expect, __builtin_unreachable和__builtin_prefetch

    builtin expect 该指令是gcc引入的 就是允许代码编写者把最有可能执行的分支告诉编译器 标准写法是 bultin expect exp n 意思是exp n的概率很大 这样编译器可以对代码进行优化 减少指令跳转带来的性能下降
  • elasticsearch bulk批量增删改(超详细)

    一 bulk的操作类型 1 1批量增 语法一 index操作 可以是创建文档 也可以是全量替换文档 类似于普通的put操作 POST bulk index index test index type test type id 12 scor
  • java调用串口,开箱即用

    作者 刘东标 撰写日期 2022 08 02 开发遇到问题 1 供应商提供的动态库各种底层代码有问题 指针也不对 参数也乱 加上长年不维护动态库 2 解决多个动态库问题 不同供应商提供动态库也不同 32位动态库和64位动态库 还有是供应商的
  • Python -BS4详细介绍

    Python BS4详细介绍 Python 在处理html方面有很多的优势 一般情况下是要先学习正则表达式的 在应用过程中有很多模块是非常方便的 先尝试使用BeautifulSoup和Urllib进行网页的处理 仅供学习 首先列举所需要导入
  • flutter 边框_Flutter作息定时器 app

    背景知识视频教程 学习Flutter Dart构建iOS和Android应用 国外课栈 viadean com Flutter Dart 完整的Flutter应用开发课程 国外课栈 viadean com Flutter的实际项目 国外课栈
  • 【OSATE学习笔记】失效模式与影响分析,FMEA(failure mode and effects analysis)

    目录 参考文献 简介 FMEA显著的作用案例 案例一 案例二 案例三 FMEA目标 FMEA进程 风险 Risk FMEA的特点及作用 FMEA的特点 FMEA的分类 专业术语 DFMEA与PFMEA的差别 六西格玛 SIX SIGMA 嵌
  • PHP内核探索:Apache运行与钩子函数

    Apache是目前世界上使用最为广泛的一种Web Server 它以跨平台 高效和稳定而闻名 按照去年官方统计的数据 Apache服务器的装机量占该市场60 以上的份额 尤其是在X Unix Linux 平台上 Apache是最常见的选择
  • 已解决(from docx import Document导包报错)ModuleNotFoundError: No module named ‘exceptions‘

    已解决 from docx import Document导包报错 ModuleNotFoundError No module named exceptions 文章目录 报错代码 报错翻译 报错原因 解决方法 千人全栈VIP答疑群联系博主
  • 1. R语言中grep函数和gsub()函数的使用

    1 grep 函数 1 语法结构 grep pattern x ignore case FALSE perl FALSE value FALSE fixed FALSE useBytes FALSE invert FALSE 各参数的含义如
  • linux内核分析:进程通讯方式

    信号 一旦有信号产生 我们就有下面这几种 用户进程对信号的处理方式 1 执行默认操作 Linux 对每种信号都规定了默认操作 例如 上面列表中的 Term 就是终止进程的意思 Core 的意思是 Core Dump 也即终止进程后 通过 C
  • 解决M1处理器安装PS闪退问题Photoshop 2021 fo mac(支持最新M1芯片处理器款mac)

    去年苹果在2020年11月11日突然发布了搭载自研M1芯片处理器的最新款Mac 由于这次新版mac系列史无前例的采用arm架构的芯片 导致很多之前为旧版mac开发的软件安装后不兼容无法使用 这其中就包括著名的Adobe系列软件 之前很多刚买
  • ppocrlabel简单教学

    前言 给我们小白成员的快速上手ppocrlabel的指南 1 ppocr环境配置 建议是先创建一个虚拟环境 直接参考 https blog csdn net weixin 42708301 article details 119864744
  • HDMI的DDC是什么

    DDC 是什么 DDC Display Data Channel 显示数据通道 在 HDMI 协议中用于 Source 和 Sink 两端进行数据交换 通常是基于 I2C 标准的一套通讯机制 在实际使用过程中 Source 端的 HDMI
  • 前端自动化测试之葵花宝典

    作者 京东零售 杜兴文 首先聊一下概念 Web 前端自动化测试是一种通过编写代码来自动化执行 Web 应用程序的测试任务的方法 它通常使用 JavaScript 和测试框架 如 Selenium Appium 等 来实现 Web 前端自动化
  • IRQL 和 分页内存

    IRQL是Interrupt ReQuest Level 中断请求级别 一个由windows虚拟出来的概念 划分在windows下中断的优先级 这里中断包括了硬中断和软中断 硬中断是由硬件产生 而软中断则是完全虚拟出来的 处理器在一个IRQ
  • python中把list列表所有或者部分的数变成整数,或者浮点数,字符串等等

    第一种 简单形式列表中是数字型 list x 1624865249825 0 316 0 351 0 32 0 107 0 4 0 1 7187 2970 0 1 0 list y 5249825 4 0 925 0 3903 1 7187
  • STM32HAL库 (cubemx) 两个HC05蓝牙模块相互通信相关问题的解决 数组串口发送与接受的方法

    主要问题 1 蓝牙模块的连接问题 2 蓝牙模块的工作模式 3 CUBEMX 配置串口注意事项 4 两个模块数据传输异常 前言 因为最近都在做基于STM32 MPU6050的手势控制机器人 遇到了无线数据传输的问题 正好手上有几个蓝牙模块 就
  • Latex系列2---段落编写+标题编写+目录生成

    接着上一节的简单中文文本 这节阐述的是一篇小规模文章的编写 段落编写 分段 写文章少不了分段的情况 latex中如何分段 先看一段代码和效果图 在这里我们看到代码中对于文章的分段有两种方式 1 空行 2 使用 par 空格 的形式 对于空行
  • blender基础笔记

    1 下载与安装 官网下载 官网下载 setam下载 steam下载 个人推荐这个 方便 修改语言 左上角 edit preferences Interface Transtation Langlish 疲了 看图吧 懒得写了 2 基础操作
  • 源码看CoordinatorLayout.Behavior原理

    http blog csdn net qibin0506 article details 50377592 在上一篇博客CoordinatorLayout高级用法 自定义Behavior中 我们介绍了如何去自定义一个CoordinatorL