自定义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;i++) {
            HEADER_FIXED_HEIGTH += getChildAt(i).getMeasuredHeight();
        }
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        Log.i("MyCoordinatorLayout", "drawChild: "+child);
        if (indexOfChild(child)>= fixed_num) {//非置顶项,裁剪置顶的高度
            canvas.clipRect(0, HEADER_FIXED_HEIGTH - child.getTop(), canvas.getWidth(), canvas.getHeight());
        }
        return super.drawChild(canvas, child, drawingTime);
    }
}

布局结构

<?xml version="1.0" encoding="utf-8"?>
<com.tospur.exmind.testrecycerviewwithtopandbottomrefresh.refresh.MyCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:fix_num="1"
    tools:context="com.tospur.exmind.testrecycerviewwithtopandbottomrefresh.refresh.RefreshActivity">
<android.support.v7.widget.Toolbar
    android:layout_width="match_parent"
    android:layout_height="72dp"
    android:background="#ff0"
    android:id="@+id/toolbar"
    ></android.support.v7.widget.Toolbar>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#f00"
    android:orientation="vertical"
    app:layout_behavior="com.tospur.exmind.testrecycerviewwithtopandbottomrefresh.refresh.PullRefreshBehavior"
    >
    <TextView
        android:id="@+id/pull_text_view"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="下拉刷新"
        android:textSize="24sp"
        android:textColor="#fff"
        />

</LinearLayout>
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="220dp"
        android:src="@mipmap/test"
        android:adjustViewBounds="true"
        android:scaleType="fitCenter"
        />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="72dp"
        android:background="#00f"
        android:orientation="vertical"
        >
        <TextView
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="过滤条件"
            android:textColor="#fff"
            android:textSize="24sp"
            />
    </LinearLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#0f0"
        >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/my_txt"
            />
    </android.support.v4.widget.NestedScrollView>
</com.tospur.exmind.testrecycerviewwithtopandbottomrefresh.refresh.MyCoordinatorLayout>

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容