自定义CoordinatorLayout.Behavior 实现下拉回弹

先看效果


123.gif
package com.tospur.exmind.testrecycerviewwithtopandbottomrefresh.refresh;

import android.content.Context;
import android.graphics.Rect;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;

/**
 * Created by lehow on 2017/2/20.
 * 内容摘要:
 * 版权所有:极策科技
 */
public class PullRefreshBehavior extends CoordinatorLayout.Behavior {

    private static final String TAG = "PullRefreshBehavior";
    private static int HEADER_MIN_HEIGTH = 0;
    private float default_pull_trans_y=0;

    public PullRefreshBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        HEADER_MIN_HEIGTH = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 72 * 2, context.getResources().getDisplayMetrics());

    }


    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
        Log.i(TAG, "onLayoutChild: ");
        int indexOfChild = parent.indexOfChild(child);
//        super.onLayoutChild(parent, child, layoutDirection);
        int childCount = parent.getChildCount();
        int topOffset = parent.getTop();
        for (int i = 0; i < childCount; i++) {
            View childAt = parent.getChildAt(i);
            if (childAt == child){
                childAt.layout(0, 0, parent.getWidth(), 0 + Math.abs(childAt.getMeasuredHeight()));//需要这行,否则child不显示
            }

            childAt.setTranslationY(topOffset);
            if (i == childCount - 1) {//最后一个,调整他的高度,防止向上滚动到底,scrollview的内容显示不全
                int w = View.MeasureSpec.makeMeasureSpec(parent.getWidth(),View.MeasureSpec.EXACTLY);
                int h = View.MeasureSpec.makeMeasureSpec(parent.getHeight()-HEADER_MIN_HEIGTH,View.MeasureSpec.EXACTLY);
                childAt.measure(w,h);
                default_pull_trans_y=childAt.getTranslationY();
//                childAt.layout(0, 0, parent.getWidth(), parent.getHeight()-HEADER_MIN_HEIGTH);
            }
            if (i == indexOfChild) {//当前的pullview不显示,所以后面的偏移不应该累加他的高度
                continue;
            }
            topOffset += childAt.getMeasuredHeight();
        }
        return true;
    }


    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        Log.i(TAG, "==onStartNestedScroll: ");
        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
        //coordinatorLayout 在此处判断 如果是垂直方向的滚,我就要先接收着看一看
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        Log.i(TAG, "onNestedPreScroll: dx="+dx+" dy="+dy);
        //target 为滚动控件
        //target.getTranslationY() > HEADER_MIN_HEIGTH; 滚动控件与
        int targetHeaderOffest = (int) (target.getTranslationY() - HEADER_MIN_HEIGTH);
        if (dy > 0 && targetHeaderOffest > 0) {//向上移动,并且与最小的Header距离大于0,先整体向上滚动
            int childCount = coordinatorLayout.getChildCount();
            int indexOfChild = coordinatorLayout.indexOfChild(child);
            int scollY = Math.min(targetHeaderOffest, dy);
            for (int i = 0; i < childCount; i++) {
                View childAt = coordinatorLayout.getChildAt(i);
                if (i <=indexOfChild) {//固定在顶部
                } else {//移动pullview之后的控件
                    childAt.setTranslationY(childAt.getTranslationY() - scollY);
                }
            }
            consumed[1] = scollY;//更新coordinatorLayout消耗的距离
        }

    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
        Log.i(TAG, "onNestedScroll: dxConsumed="+dxConsumed+" dyConsumed="+dyConsumed+" dxUnconsumed="+dxUnconsumed+" dyUnconsumed="+dyUnconsumed);

        if (dyConsumed<=0&&dyUnconsumed<0){//向下滚动,有剩余的,整体向下滚动

            int childCount = coordinatorLayout.getChildCount();
            int indexOfChild = coordinatorLayout.indexOfChild(child);
            for (int i = 0; i < childCount; i++) {
                View childAt = coordinatorLayout.getChildAt(i);
                if (i <=indexOfChild) {//固定在顶部
                } else {//pullview之下的控件之间移动就好
                    childAt.setTranslationY(childAt.getTranslationY() - dyUnconsumed);
                }
            }
        }


    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
        super.onStopNestedScroll(coordinatorLayout, child, target);
        float offsetToDefault = target.getTranslationY() - default_pull_trans_y;
        if (offsetToDefault>0) {
            int childCount = coordinatorLayout.getChildCount();
            int indexOfChild = coordinatorLayout.indexOfChild(child);
            for (int i = 0; i < childCount; i++) {
                View childAt = coordinatorLayout.getChildAt(i);
                if (i <=indexOfChild) {//固定在顶部
                } else {//回弹
                    childAt.setTranslationY(childAt.getTranslationY() - (offsetToDefault));
                }
            }
        }
    }

    @Override
    public boolean onRequestChildRectangleOnScreen(CoordinatorLayout coordinatorLayout, View child, Rect rectangle, boolean immediate) {
        return super.onRequestChildRectangleOnScreen(coordinatorLayout, child, rectangle, immediate);
    }


}

onLayoutChild

主要处理了pillview之下控件的偏移问题,因为CoordinatorLayout默认是层叠的方式布局,所以利用setTranslationY把布局错开。同时调整了scrollview的高度为折叠后的最大高度,防止内容少的时候,没有沾满整个空间,和内容过多,向上滚动到头的时候,内容显示不完整。

onNestedPreScroll

主要处理向上滚动时,先整体向上滚动到折叠的最小状态,然后才是scrollview自己的内容滚动

onNestedScroll

先让scrollview自己的内容滚动,然后再整体滚动,实现下拉效果

onStopNestedScroll

处理滚动回弹问题

MyCoordinatorLayout 处理了toolbar置顶的问题和,下面控件向上滚动会覆盖toolbar的问题

package com.tospur.exmind.testrecycerviewwithtopandbottomrefresh.refresh;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;

import com.tospur.exmind.testrecycerviewwithtopandbottomrefresh.R;

/**
 * Created by lehow on 2017/2/23.
 * 内容摘要:
 * 版权所有:极策科技
 */
public class MyCoordinatorLayout extends CoordinatorLayout{

    //处理顶部toolbar区域置顶的效果
    private int fixed_num = 0;
    private int HEADER_FIXED_HEIGTH = 0;
    public MyCoordinatorLayout(Context context) {
        this(context, null);
    }

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

    public MyCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.MyCoordinatorLayout);
        fixed_num = a.getInteger(
                R.styleable.MyCoordinatorLayout_fix_num, 0);
        a.recycle();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        HEADER_FIXED_HEIGTH = 0;
        for (int i=0;i= fixed_num) {//非置顶项,裁剪置顶的高度
            canvas.clipRect(0, HEADER_FIXED_HEIGTH - child.getTop(), canvas.getWidth(), canvas.getHeight());
        }
        return super.drawChild(canvas, child, drawingTime);
    }
}

布局结构





    


    
    
        
    

    
        
    


当然,只实现到了下拉回弹,下拉刷新的逻辑还有很多,初步已经写放弃了,有时间我再接着写,因为我突然想到另一种方式去实现。

在这里我只给下拉刷新所在的LinearLayout 指定了Behavior,而这个LinearLayout 本身不接收事件也不支持嵌套滚动,而他的Behavior确是可以接收到NestedScrollView触发的滚动事件的。也就是说Behavior可以绑定在任何CoordinatorLayout的直接子view上,而其他view触发的嵌套滚动事件,该Behavior都有机会接收到。

这就是为啥AppBarLayout的default Behavior能接收到 其他NestedView的嵌套滚动事件,而不用自己触发,自己会拦截在其上的touch事件作消耗处理,发生在其他控件上的nested事件则通过Behavior来监听。

你可能感兴趣的:(自定义CoordinatorLayout.Behavior 实现下拉回弹)