android MD 进阶[五] CoordinatorLayout 从源码到实战..

Posted android超级兵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android MD 进阶[五] CoordinatorLayout 从源码到实战..相关的知识,希望对你有一定的参考价值。

android MD进阶(五) coordinatorLayout从源码到实战

前言: 上一篇:android View生命周期 介绍了view的生命周期,上上一篇:android MD进阶[四] NestedScrollView 从源码到实战…因为和本篇息息相关,没有了解过的同学可以先看看这两篇哦 ~

CoordinatorLayout源码版本: 1.1.0

废话不多说,先来看看今天要完成的效果:

效果一效果二

什么是coordinatorLayout?

coordinatorLayout意为协调者布局, 每个 ViewGroup 都有相应的特征例如:

  • LinearLayout 线性布局 常用来水平/垂直摆放childView
  • RelativeLayout 相对布局 常用来 AView 在 BView 的某个位置,来摆放 childView
  • ConstraintLayout 约束布局 常用来 CView 在 DView 的某个约束位置,很好的解决了布局嵌套层级过深以及布局困难的问题

所以coordinatorLayout的特征是什么呢?

它的特征为: 可以配合每个 childView 来协调使用,比如,在移动 AView 的过程中我想让 BView 和 CView 发生改变(例如效果一),那么就可以用它,它的缺点也非常明显,布局起来稍稍优点麻烦…

难道说我用coordinatorLayout就可以让他协调起来? 用意念吗? 那当然不行.

单指coordinatorLayout没有太大的作用,重要的是coordinatorLayout 配合 behavior 来使用!

那什么是 behavior 呢 ?

behavior 为行为,假设 AView 移动过程中 BView 想要跟随者 AView 移动,那么 BView 直接在 xml 中添加一个 app:layout_behavior="XXX"属性,然后自定义CoordinatorLayout.Behavior即可

最后再来看一张代码图,先有个初步的了解!

这个效果是如果 MoveView 发生移动,那么ImageView就发生颜色的变化

所以本篇的重点就是CoordinatorLayout.Behavior源码分析,以及使用到实战!

CoordinatorLayout.Behavior初步认识

# CoordinatorLayout.java
  
public static abstract class Behavior<V extends View> 
  
        /*
         * TODO 当解析layout完成时候调用 View#onAttachedToWindow() 然后紧接着调用该方法 
         */
        public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) 
        

        /*
         * TODO 当 view销毁的时候调用
         */
        public void onDetachedFromLayoutParams() 
        

        /**
         *  TODO: 当 CoordinatorLayout#onInterceptTouchEvent() 事件的时候调用
         */
        public boolean onInterceptTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child,
                @NonNull MotionEvent ev) 
            return false;
        

        /**
         *  TODO: 当 CoordinatorLayout#onTouchEvent() 事件的时候调用
         */
        public boolean onTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child,
                @NonNull MotionEvent ev) 
            return false;
        

        /*
         * TODO  设置背景色
         *
         * 需要配合 getScrimOpacity() 使用 因为 getScrimOpacity() 默认 = 0f
         */
        @ColorInt
        public int getScrimColor(@NonNull CoordinatorLayout parent, @NonNull V child) 
            return Color.BLACK;
        

        /*
         * TODO 设置不透明度
         */
        @FloatRange(from = 0, to = 1)
        public float getScrimOpacity(@NonNull CoordinatorLayout parent, @NonNull V child) 
            return 0.f;
        

       /*
        * TODO 需要依赖的view
        *
        * @param child: 当前view
        * @param dependency: 需要依赖的View
        */
        public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull V child,
                @NonNull View dependency) 
            return false;
        

        /*
         * TODO 需要依赖的view发生变化的时候调用
         */
        public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull V child,
                @NonNull View dependency) 
            return false;
        

        /*
         * TODO 当被依赖的view移除view的时候调用
         */
        public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull V child,
                @NonNull View dependency) 
        

        /*
         * TODO 调用 CoordinatorLayout#onMeasureChild() 的时候调用
         */
        public boolean onMeasureChild(@NonNull CoordinatorLayout parent, @NonNull V child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) 
            return false;
        

        /*
         * TODO 调用CoordinatorLayout$onLayout() 的时候调用
         */
        public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull V child,
                int layoutDirection) 
            return false;
        

        /*
         * TODO 当 NestedScrollingChild#startNestedScroll() 的时候调用
         */
        @Deprecated
        public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
                @ScrollAxis int axes) 
            return false;
        

       /*
        * TODO  当 NestedScrollingChild2#startNestedScroll() 的时候调用
        */
        public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
                @ScrollAxis int axes, @NestedScrollType int type) 
            if (type == ViewCompat.TYPE_TOUCH) 
                return onStartNestedScroll(coordinatorLayout, child, directTargetChild,
                        target, axes);
            
            return false;
        

        /*
         * TODO 当 NestedScrollingChild#startNestedScroll() = true的时候调用
         */
        @Deprecated
        public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
                @ScrollAxis int axes) 
        

        /*
         * TODO 当 NestedScrollingChild2#startNestedScroll() = true的时候调用
         */
        public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
                @ScrollAxis int axes, @NestedScrollType int type) 
            if (type == ViewCompat.TYPE_TOUCH) 
                onNestedScrollAccepted(coordinatorLayout, child, directTargetChild,
                        target, axes);
            
        

        /*
         * TODO  当 NestedScrollingChild#stopNestedScroll() 调用的时候执行
         */
        @Deprecated
        public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target) 
            // Do nothing
        

        /*
         * TODO 当 NestedScrollingChild2#stopNestedScroll() 调用的时候执行
         */
        public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, @NestedScrollType int type) 
            ...
        

        /*
         * TODO 当 NestedScrollingChild#dispatchNestedScroll() 调用的时候执行
         */
        @Deprecated
        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
                @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
                int dyUnconsumed) 
            // Do nothing
        


        /*
         * TODO 当 NestedScrollingChild2#dispatchNestedScroll() 调用的时候执行
         */
        @Deprecated
        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
                @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
                int dyUnconsumed, @NestedScrollType int type) 
           ...
        


        /*
         * TODO 当 NestedScrollingChild3#dispatchNestedScroll() 调用的时候执行
         */
        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
                @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
                int dyUnconsumed, @NestedScrollType int type, @NonNull int[] consumed) 
            ...
        

        /*
         * TODO  当 NestedScrollingChild#dispatchNestedPreScroll() 调用的时候执行
         */
        @Deprecated
        public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) 
            // Do nothing
        

       /*
        * TODO 当 NestedScrollingChild2#dispatchNestedPreScroll() 调用的时候执行
        */
        public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed,
                @NestedScrollType int type) 
            if (type == ViewCompat.TYPE_TOUCH) 
                onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
            
        

       /*
        * TODO 当 NestedScrollingChild#dispatchNestedFling() 调用的时候执行
        */
        public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, float velocityX, float velocityY,
                boolean consumed) 
            return false;
        

        /*
         * TODO 当 NestedScrollingChild2#dispatchNestedFling() 调用的时候执行
         */
        public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, float velocityX, float velocityY) 
            return false;
        

        /*
        * TODO 恢复状态
        */
        public void onRestoreInstanceState(@NonNull CoordinatorLayout parent, @NonNull V child,
                @NonNull Parcelable state) 
          
        

        /*
          * TODO  保存状态 和V iew / Activity 保存状态一样
          *
          * tips: 1. 必须保证View在xml中设置了id (android:id="@+id/XXX")
          *       2. 必须保证view参数中有behavior属性 (app:behavior="www.com.XXX")
          */
        @Nullable
        public Parcelable onSaveInstanceState(@NonNull CoordinatorLayout parent, @NonNull V child) 
            return BaseSavedState.EMPTY_STATE;
        
    

搞这么多,谁能看懂啊… 别着急,本篇换个思路,先来1 个简单的 demo,然后再步入源码分析,由俭入奢.

我的第一个自定义 Behavior

先来自定义一个跟随手指滑动的 View

MoveView.kt

class MoveView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) 

    private var lastOffset = OffSet(0f, 0f)

    override fun onTouchEvent(event: MotionEvent): Boolean 
        when (event.action) 
            MotionEvent.ACTION_DOWN -> 
                lastOffset = OffSet(event.x, event.y)
            
            MotionEvent.ACTION_MOVE -> 
                val dx = event.x - lastOffset.x
                val dy = event.y - lastOffset.y

                ViewCompat.offsetLeftAndRight(this, dx.toInt())
                ViewCompat.offsetTopAndBottom(this, dy.toInt())
            
        
        return true
    

这段代码有手就行,不用多讲

现在要做的就是,当 View 移动的时候,另一个 View 颜色跟随变化

来看一眼布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.meterialproject.view.behavior.demo1.MoveView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:background="@color/purple_700" />


    <androidx.appcompat.widget.AppCompatImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/green"
        app:layout_behavior=".view.behavior.demo1.ColorBehavior" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

自定义 behavior

# ColorBehavior.kt

class ColorBehavior(val context: Context, attrs: AttributeSet?) :
    CoordinatorLayout.Behavior<AppCompatImageView>(context, attrs) 

    companion object 
        const val TAG = "szjColorBehavior"
    

    /*
     * TODO 判断跟随变化的 View
     * @param parent: CoordinatorLayout
     * @param child: 当前的 view
     * @param dependency: 需要依赖的 View  
     */
    override fun layoutDependsOn(
        parent: CoordinatorLayout,
        child: AppCompatImageView,
        dependency: View
    ): Boolean 
        return dependency is MoveView
    

    // 改变当前的状态
    override fun onDependentViewChanged(
        parent: CoordinatorLayout,
        child: AppCompatImageView,
        dependency: View
    ): Boolean 
        // 随机颜色
        child.setBackgroundColor(context.randomColor())
        return super.onDependentViewChanged(parent, child, dependency)
    

参数介绍:

参数作用默认
layoutDependsOn()用来判断以来的 Viewfalse
onDependentViewChanged()当 layoutDependsOn() = ture,并且当前 view 可见状态下执行false

这里有 2 个重要的角色

  • 依赖方: AppCompatImageView
  • 被依赖方: MoveView

那么现在依赖方(AppCompatImageView)就可以监听到被依赖方(MoveView)的变化

初步原理图:

初步分析:

CoordinatorLayout 保存记录所有的ChildView , 通过 addOnPreDrawListener() 监听所有View的变化

然后通过遍历所有保存的childView, 判断childView中是否有behavior

如果有behavior 那么在判断 Behavior#layoutDependsOn() 是否依赖MoveView

如果也依赖于MoveView,那么就将事件传递给对应的 Behavior#onDependentViewChanged

所以到底是什么意思呢?

一句话总结就是 如果Behavior#layoutDependsOn() 返回true 就会执行到 Behavior#onDependentViewChanged() 的方法

addOnPreDrawListener和 setOnHierarchyChangeListener

  • addOnPreDrawListener: 是ViewGroup用来监听所有childView变化的, 只要childView有变化,例如 DOWN / MOVE 事件等

  • setOnHierarchyChangeListener 是ViewGroup监听自身childView发生变化来响应的 一共有2个方法:

  • onChildViewAdded 添加响应

  • onChildViewRemoved 删除响应

效果图:

我认为这是CoordinatorLayout的核心!

CoordinatorLayout 源码分析

看源码的小技巧:

首先必须知道你要看的源码是做什么的,必须会使用,比如说本篇 CoordinatorLayout的源码,就是用来协调childView的

  • 构造函数 (必看)

  • 如果是View 根据 View 的生命周期看每个方法的实现, 如果是ViewGroup别忘记看 generateLayoutParams() 方法

  • 紧盯着主线流程

  • 先看某个方法的作用,在看细节

  • 熟能生巧,多看,多分析,多画流程图

  • 加注释!

  • 可以尝试打断点

CoordinatorLayout采取的策略就是跟随 view 的生命周期开始看,首先从构造聊起 !

构造方法

# CoordinatorLayout.java
  
public CoordinatorLayout(@NonNull Context context) 
    this(context, null);


public CoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs) 
    this(context, attrs, R.attr.coordinatorLayoutStyle);


public CoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs,
        @AttrRes int defStyleAttr) 
    // 监听 ViewGroup 的变化
    super.setOnHierarchyChangeListener(new HierarchyChangeListener());
    ...

# CoordinatorLayout.java
  
private class HierarchyChangeListener implements OnHierarchyChangeListener 
    @Override
    public void onChildViewAdded(View parent, View child) 
        ...
    

    @Override
    public void onChildViewRemoved(View parent, View child) 
      // 重点 当 childView 被删除的时候调用
        onChildViewsChanged(EVENT_VIEW_REMOVED);
       ..
    

核心方法,下面源码也会用到 !!

# CoordinatorLayout.java

final void onChildViewsChanged(@DispatchChangeEvent final int type) 
        // 当前的 childView
        final int childCount = mDependencySortedChildren.size();

 			  // 循环所有 childView 
        // 为了找到需要依赖的 view
        for (int i = 0; i < childCount; i++) 
            final View child = mDependencySortedChildren.get(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
          // 如果 当前的状态 = EVENT_PRE_DRAW 并且 childView 不可见 
          // 那么退出本次循环
            if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) 
                continue;
            
            ...

            for (int j = i + 1; j < childCount; j++) 
                final View checkChild = mDependencySortedChildren.get(j);
                final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
                final Behavior b = checkLp.getBehavior();
                
                // 如果 childView 有依赖的Behavior 并且 Behavior#layoutDependsOn() = true 就继续执行
                if (b != null && b.layoutDependsOn(this, checkChild, child)Android自助餐之Jni进阶

我的渲染技术进阶之旅如何在Windows系统编译Filament的android版本程序?

我的渲染技术进阶之旅如何在Windows系统编译Filament的android版本程序?

我的渲染技术进阶之旅如何在Windows系统编译Filament的android版本程序?

Android进阶——WMS与AMS浅析

Android -- 开发中级工程师进阶