美文网首页MD风格Android CommunityMaterial Design
CoordinatorLayout相关知识点详解

CoordinatorLayout相关知识点详解

作者: lanceJin | 来源:发表于2017-05-16 15:32 被阅读134次

    1.前言


    提起CoordinatorLayout,大家立马能想到绚丽的首页滚动交互。可是真当去实践时,官方Demo提供的那一堆控件及其属性组合,令人头昏眼花、理不清思路。这次,我们从外(父)到内(子)来分析各控件组合及作用。

    2.CoordinatorLayout


    这是一个布局,继承自ViewGroup,从安置子视图的角度来说,相当于是强大的FrameLayout。对它的使用要明确两点:作为顶层布局使用;给内部的一个或多个子视图提供指定交互。先说说两个高级属性(已被封装好的交互):

    • anchor及anchorGravity。这可以将悬浮视图与内容视图关联,使之跟随内容移动。anchor可以指定为CoordinatorLayout内的任意视图Id(除了使用这个属性的视图及其子视图),而anchorGravity负责在内容视图内放置悬浮视图。
    • insetEdge和dodgeInsetEdges。这是一对同时使用的属性,目的是不让视图被遮挡。当两个视图有遮挡时,其中一个insetEdge设置为bottom,另一个dodgeInsetEdges也设置为bottom,则第二个向top方向避让。若两属性设置方向不一致时,无效;也可以设置多个方向或者给多个视图设置(部分情况会出问题)。详细参考jscoolstar的文章

    由于这两对交互比较常用且逻辑清晰,所以抽取出来。那么内部是由什么实现的呢?自己如何定义交互呢?这时得使用Behavior类,具体逻辑是这样:

    • CoordinatorLayout作为容器可以监听子视图状态上的变化,但是滚动视图只是内容变化,状态并未变化,监听不到。所以系统给监听者和被监听者分别提供两个接口NestedScrollingParent和NestedScrollingChild。第一个接口需要被ViewGroup的子类实现,表明希望协助完成嵌套子视图的滚动操作;第二个接口需要被View的子类实现,表明希望分发嵌套滚动操作给协作的父布局。有这些类满足:

      NestedScrolling.png
    • CoordinatorLayout则将状态变化和滚动变化分发给直接子视图,由它们的Behavior对象接受判断是否符合条件,进行什么操作,有点类似广播机制(其实是遍历子视图)。注意,Behavior在布局文件中由app:layout_behavior声明或者在从动视图类上加 @DefaultBehavior() 声明,初始化是在CoordinatorLayout的LayoutParams中通过反射完成的。详细信息可以参考源码分析

    3.Behavior


    在自定义Behavior类之前,先声明两个概念,即主动与从动。由于是视图的交互,必然是其中一个发生变化引起另一个的变化,那么前者就是主动,后者便是从动。Behavior可以接收许多事件,我们主要重写依赖(状态变化)和滚动。

    import android.content.Context;
    import android.support.design.widget.CoordinatorLayout;
    import android.util.AttributeSet;
    import android.view.View;
    import android.widget.TextView;
    
    // 1.为了演示,声明Behavior的从动对象是TextView
    // 使用时,被设置的对象类型是它或子类,否则报错
    public class MyBehavior extends CoordinatorLayout.Behavior<TextView> {
    
        // 2.重写构造函数,若在XML布局中使用,得有AttributeSet
        public MyBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        // 3.自己判断是否需要执行依赖交互,返回true执行,false不执行
        // child从动对象,dependency主动对象
        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
            return super.layoutDependsOn(parent, child, dependency);
        }
    
        // 4.自己实现交互操作,返回true表示需改变child大小和位置,false则不用
        // child从动对象,dependency主动对象
        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
            return super.onDependentViewChanged(parent, child, dependency);
        }
    
        // 5.自己判断是否需要执行滚动交互,返回true执行,false不执行
        // child从动对象,target主动对象,directTarget为CoordinatorLayout子视图,是或包含主动对象,nestedScrollAxes水平还是竖直滑动
        @Override
        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, TextView child, View directTargetChild, View target, int nestedScrollAxes) {
            return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
        }
    
        // 6.自己实现交互操作
        // child从动对象,target主动对象,dxConsumed/dyConsumed为主动对象在水平和竖直上已滚动距离,另外两个是未滚动距离
        @Override
        public void onNestedScroll(CoordinatorLayout coordinatorLayout, TextView child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
        }
    
        // 7.这是快速滑动操作
        @Override
        public boolean onNestedFling(CoordinatorLayout coordinatorLayout, TextView child, View target, float velocityX, float velocityY, boolean consumed) {
            return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
        }
    }
    

    Behavior能在从动视图前收到CoordinatorLayout的所有触摸事件,并做出相应处理,与View的事件分发一致。详细参考Jude95的文章

        @Override
        public boolean onInterceptTouchEvent(CoordinatorLayout parent, TextView child, MotionEvent ev) {
            return super.onInterceptTouchEvent(parent, child, ev);
        }
        @Override
        public boolean onTouchEvent(CoordinatorLayout parent, TextView child, MotionEvent ev) {
            return super.onTouchEvent(parent, child, ev);
        }
    

    4.AppBarLayout


    AppBarLayout继承自LinearLayout,最大的特色就是实现了滚动手势,并通过给子视图设置scrollFlags来操作它们的滚动行为。若要滚动,scroll必须第一个设置。但是此功能仅当它作为CoordinatorLayout的直接子视图时有效,这不是和Behavior的用法一样吗?看看源码。

    @CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
    public class AppBarLayout extends LinearLayout {
    
        public AppBarLayout(Context context) {
            this(context, null);
        }
    
        public AppBarLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior {
    
            public ScrollingViewBehavior() {}
    
            public ScrollingViewBehavior(Context context, AttributeSet attrs) {
                super(context, attrs);
            }
    
            @Override
            public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
                // We depend on any AppBarLayouts
                return dependency instanceof AppBarLayout;
            }
    
            @Override
            public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
                    View dependency) {
                offsetChildAsNeeded(parent, child, dependency);
                return false;
            }
    }
    
    public static class Behavior extends HeaderBehavior<AppBarLayout> {
    
            public Behavior() {}
    
            public Behavior(Context context, AttributeSet attrs) {
                super(context, attrs);
            }
    
            @Override
            public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
                    View directTargetChild, View target, int nestedScrollAxes) {
                // Return true if we're nested scrolling vertically, and we have scrollable children
                // and the scrolling view is big enough to scroll
                final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
                        && child.hasScrollableChildren()
                        && parent.getHeight() - directTargetChild.getHeight() <= child.getHeight();
    
                if (started && mOffsetAnimator != null) {
                    // Cancel any offset animation
                    mOffsetAnimator.cancel();
                }
    
                // A new nested scroll has started so clear out the previous ref
                mLastNestedScrollingChildRef = null;
    
                return started;
            }
    }
    

    果然是通过Behavior实现AppBarLayout与滚动手势交互的。按照前面学的知识可以知道,onStartNestedScroll() 方法中的条件就是实现滚动的条件。那官方的Demo中为什么要给NestedScrollView设置AppBarLayout.ScrollingViewBehavior?通过ScrollingViewBehavior的 layoutDependsOn() 方法可知,是设置的视图依赖AppBarLayout,看来是为了根据AppBarLayout的移动来调整自己在界面中的位置,onDependentViewChanged() 方法证明了这点。

    5.总结


    知识点太杂了,我来理一理思路。CoordinatorLayout作为容器,接收子视图的变化,再分发对应事件,起到解耦的作用,使事件的交互对象只有它。Behavior则是从视图的事件处理,只在CoordinatorLayout的直接子视图中起作用,当满足条件就可以操作视图。AppBarLayout目的就一个,带着子视图一起响应滚动手势。

    相关文章

      网友评论

        本文标题:CoordinatorLayout相关知识点详解

        本文链接:https://www.haomeiwen.com/subject/vtrkxxtx.html