创建自己的CoordinatorLayout实现

CoordinatorLayout顾名思义是一个协调布局,其作用是监听其子View对象的变化,如位移,滚动事件,并将事件传递到另外的对象上

根据源码分析可知CoordinatorLayout主要通过两种方式监听事件的产生

  • ViewTreeObserver.addOnPreDrawListener
  • 实现NestedScrollingParent接口

前者监听视图树绘制事件,而后者可监听子View滚动事件

监听到事件后如何将事件传递到对应的子View响应方法上呢?CoordinatorLayout是通过Behavior实现的,Behavior是CoordinatorLayout的一个内部抽象类,该类主要完成两件事

  • 指出当前子View依赖于另外某一个子View的事件发生
  • 实现事件响应
通过Behavior实现类,CoordinatorLayout可以在Layout.xml里设定子View间的连动关系,而修改界面代码或使用自定义View来完成。事件发生的目标对象并不知道有别的对象监听自己的变化,响应对象也不知道自己响应了别人的事件,其中都是CoordinatorLayout与Behavior在监听与响应。

        根据上面的分析我们来实现自己的CoordinatorLayout类,思路如下
  1. 创建ViewGroup实现类,重写其构造方法,onMeasure, onLayout,实现其ViewGroup.LayoutParams实现类并重写generateLayoutParams方法
  2. 自定义子View使用的XML配置项behavior_class,类型为string,并在LayoutParams构造方法里读取该值并构造对应的Behavior实现类
  3. 定义抽象Behavior类,添加抽象方法layoutDependsOn,用于返回依赖的对象
  4. 通过为ViewTree注册监听来监听界面View的变化
  5. 通过实现NestedScrollingParent接口并实现onStartNestedScroll与onNestedScroll方法来监听NestedScrollView子view的滚动事件
  6. 在viewtree及scrollview监听事件方法里循环子View,当发现存在behavior_class配置项且依赖对象发生变化时,调用子类上beavior实现的onDepdentViewChanged或onNestedScroll方法

  • 自定义ViewGroup实现类

public class MyCoordinatorLayout extends ViewGroup{
    public  MyCoordinatorLayout(Context context) {
        super(context);
    }

    public  MyCoordinatorLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void  onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize, heightSize;

        //Get the width based on the measure specs
        widthSize = getDefaultSize(0, widthMeasureSpec);

        //Get the height based on measure specs
        heightSize = getDefaultSize(0, heightMeasureSpec);

        int majorDimension = Math.min(widthSize, heightSize);
        //Measure all child views
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        //MUST call this to save our own dimensions
        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    protected void  onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i=0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            View lastChild = getChildAt(i-1);

            if(lastChild==null) {
                child.layout(l,
                        t,
                        l+child.getMeasuredWidth(),
                        t+child.getMeasuredHeight());
            } else {
                child.layout(lastChild.getLeft(),
                        lastChild.getBottom(),
                        lastChild.getLeft()+child.getMeasuredWidth(),
                        lastChild.getBottom()+child.getMeasuredHeight());
            }
        }
    }

   
    @Override
    protected LayoutParams  generateLayoutParams(ViewGroup.LayoutParams p) {
        return new MyLayoutParams(p);
    }

    @Override
    protected LayoutParams  generateDefaultLayoutParams() {
        return new MyLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    @Override
    public  LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MyLayoutParams(getContext(), attrs);
    }
    class MyLayoutParams extends ViewGroup.LayoutParams{
        MyBehavior behavior;

        public MyLayoutParams(ViewGroup.LayoutParams p){
            super(p);
        }

        public MyLayoutParams(int width, int height){
            super(width,height);
        }

        public MyLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }
    }

}


  • 添加自定义属性并在LayoutParams实现类里添加读取并实例化的代码

<declare-styleable name="MyCoordinatorLayout_LayoutParams">
    <attr name="behavior_class" format="string" />
</declare-styleable>

并修改LayoutParams实现类为如下
class MyLayoutParams extends ViewGroup.LayoutParams{
        MyBehavior behavior;

        public MyLayoutParams(ViewGroup.LayoutParams p){
            super(p);
        }

        public MyLayoutParams(int width, int height){
            super(width,height);
        }

        public MyLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a = c.getTheme ().obtainStyledAttributes(attrs,R.styleable.MyCoordinatorLayout_LayoutParams,0,0);
            try {
                String fullName = a.getString(R.styleable.MyCoordinatorLayout_LayoutParams_behavior_class);
                if(fullName != null) {
                    Class clazz = Class.forName(fullName, true, c.getClassLoader());
                    Constructor constructor = clazz.getConstructor(new Class[]{Context.class, AttributeSet.class});
                    constructor.setAccessible(true);
                    behavior = (MyBehavior)constructor.newInstance(c, attrs);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } finally {
                a.recycle();
            }
        }
    }

  • 我们需要新增MyBehavior类,该类主要用于定义依赖对象及响应逻辑

public static abstract class MyBehavior{
    public MyBehavior(){};
    public MyBehavior(Context c, AttributeSet as){};
    public abstract int layoutDependsOn();
    public void onDependentViewChanged(View child, View dependency){};
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { }
}


  • 注册ViewTree事件并添加监听逻辑

public MyCoordinatorLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    // TODO Auto-generated constructor stub
    final ViewTreeObserver vto = getViewTreeObserver();
    vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            dispatchOnDependentViewChanged();
            return true;
        }
    });

}

当监听到ViewTree发生变化时轮循子View,当子View有定义behavior时调用其behavior实例上的onDependentViewChanged方法,当然最好是先判断变化的是否其依赖对象
private void dispatchOnDependentViewChanged() {
    int childCount = getChildCount();
    for(int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        MyLayoutParams lp = (MyLayoutParams) child.getLayoutParams();
        if(lp.behavior!=null) {
            lp.behavior.onDependentViewChanged(child,findViewById(lp.behavior.layoutDependsOn()));
        }
    }
}

实现NestedScrollingParent并实现onStartNestedScroll与onNestedScroll方法

也是直接将事件传递到behavior实现类上对应方法,可以看出CoordinatorLayout实现做为一个事件监听者,但实际的逻辑由其子View上Behavior对应的方法执行
public class MyCoordinatorLayout extends ViewGroup implements NestedScrollingParent {
    。。。。。。。

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return true;//表示接收监听的事件
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        int childCount = getChildCount();
        for(int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            MyLayoutParams lp = (MyLayoutParams) child.getLayoutParams();
            if(lp.behavior!=null) {
                lp.behavior. onNestedScroll(target, dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed);
            }
        }
    }
}

  • 在界面上使用我们自定义的CoordinatorLayout

例子1:onDependentViewChanged
添加了两个子View, second添加了behavior_class,实现类为MyBehavior2
<?xml version="1.0" encoding="utf-8"?>
<com.example.myapplication.MyCoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <View
        android:id="@+id/first"
        android:layout_width="match_parent"
        android:layout_height="128dp"
        android:background="@android:color/holo_blue_light"/>

    <View
        android:id="@+id/second"
        android:layout_width="match_parent"
        android:layout_height="128dp"
        custom:behavior_class="com.example.myapplication.MyBehavior2"
        android:background="@android:color/holo_green_light"/>
</com.example.myapplication.MyCoordinatorLayout>

创建MyBehavior2类

public class MyBehavior2 extends MyCoordinatorLayout.MyBehavior{

    //当依赖对象发生变化时让当前对象(即second对象一直保持位于依赖对象下方)
    public void onDependentViewChanged(View child, View dependency){
        child.setY(dependency.getY()+dependency.getHeight());
    };

    public MyBehavior2(Context c, AttributeSet as) {

    }

    //依赖的对象为first对象
    public int layoutDependsOn() {
        return R.id.first;
    }
}

让依赖对象发生变化,每500ms发生50的Y轴位移

final View iv = findViewById(R.id.first);
Runnable r = new Runnable(){
    @Override
    public void run() {

        int i = 0;
        while(i < 10) {
            iv.setY(iv.getY()+50);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i++;
        }
    }
};
new Thread(r).start();


例子2:NestedScrollView

添加界面元素
<?xml version="1.0" encoding="utf-8"?>
<com.example.myapplication.MyCoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <View
        android:id="@+id/first"
        android:layout_width="match_parent"
        android:layout_height="128dp"
        custom:behavior_class="com.example.myapplication.MyBehavior3"
        android:background="@android:color/holo_blue_light"/>

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/second"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                style="@style/TextAppearance.AppCompat.Display3"
                android:text="A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ"
                android:background="@android:color/holo_red_light"/>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>

</com.example.myapplication.MyCoordinatorLayout>

实现MyBehavior3类

package com.example.myapplication;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by ling on 2016/6/29.
 */

public class MyBehavior3 extends MyCoordinatorLayout.MyBehavior {
    int offsetTotal = 0;
    boolean scrolling = false;

    public MyBehavior3(Context c, AttributeSet as) {
        super(c, as);
    }

    @Override
    public int layoutDependsOn() {
        return R.id.second;
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        offset(target, dyConsumed);
    }
    public void offset(View child,int dy){
        int old = offsetTotal;
        int top = offsetTotal - dy;
        top = Math.max(top, -child.getHeight());
        top = Math.min(top, 0);
        offsetTotal = top;
        if (old == offsetTotal){
            scrolling = false;
            return;
        }
        int delta = offsetTotal-old;
        child.offsetTopAndBottom(delta);
        scrolling = true;
    }

}

界面上拉动NestedScrollView会看到first对象的位置发生了变化

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值