先看效果
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来监听。