1.下拉刷新肯定得用自己的 不用说 或者可以灵活运用别人的能满足各种需求 也凑合
2.而且 设计思路肯定得是包裹式的 即自定义一个view group包裹recycler view 这样解耦
这篇文章只是提供制作方法,在这里可以轻易的学会;但是想直接用恐怕不行,可以找别人开源的,这里的下拉刷新控件由于设计时间尚短 还很丑
郭霖的这篇文章提供了一些必备的知识
效果还很初级,不过该有的都有了,所以直接用可能读者还需要改动下(比如状态恢复的时候,还有一个判断,如果拉出来一点点,直接恢复;拉出来很多,就执行刷新,还要有一个延时动画,不过这些都不是什么难事)
需要解决几个问题
第一个问题
怎么实现下拉刷新,在于如何把header弄到屏幕之外
绿色的是手机屏幕,如何把header弄到外面就要:
LinearLayout.LayoutParams params = (LayoutParams) pullView.getLayoutParams();
params.topMargin = -headerHeight;
pullView.setLayoutParams(params);
headerHeight是在onWindowsFocusChanged中获取的控件高度,topMargin就是距离父控件的距离,负数自然就到了屏幕之外
当然这个难题可以通过ScrollView来实现,不过我猜ScrollView本质也是这样实现的
第二个问题
逻辑问题,什么时候事件给recyclerview,什么时候给下拉头(即recycler view的父view group ,我们要自定义的控件)
if (state == 下拉) {
拦截
执行下拉:修改lp
UP时,进入松开复原状态
}
if (state == 松开复原中) {
全体阻塞
执行复原:属性动画复原
属性动画执行完毕,进入无状态
}
if (无状态) {
if (rv位移为0 && 下拉手势) {
进入下拉状态
} else {
不拦截
}
}
第三个问题
逻辑转化为代码,由于你是父容器,所以你需要再onIntercept中拦截事件,onTouchEvent中处理事件,这两部分的事件各有耦合,所以算是个难点。不过代码注释的很清楚,可以轻松看懂
第四个问题
手松开后还有一个恢复过程,只需要弄一个value animator即可,监听topMargin的变化。
上代码,注释的一清二楚
public class PullToRefreshLayout extends LinearLayout {
private static final String TAG = "xbh";
private View pullView;
private RecyclerView rv;
private int headerHeight;
private int headerBarHeight;//有内容的部分
private static final float PULL_RATE = 0.4f;
private int recyclerViewOffset;
public PullToRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//获取header view,并把它添加到recycler view之前
pullView = LayoutInflater.from(context).inflate(R.layout.pull_refresh_view, null, false);
addView(pullView, 0);
//设置LinearLayout为垂直
setOrientation(VERTICAL);
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
//通过this.getChildAt来获取recycler view
rv = (RecyclerView) getChildAt(1);
//获取下拉view的高度(也包括上面的空白部分)
headerHeight = pullView.getHeight();
//获取下拉view不含空白部分的高度
headerBarHeight = ((ViewGroup)pullView).getChildAt(1).getHeight();
//初始,设置header view反向偏移一段距离,为了让他一开始处于隐藏状态
LinearLayout.LayoutParams params = (LayoutParams) pullView.getLayoutParams();
params.topMargin = -headerHeight;
pullView.setLayoutParams(params);
//通过recycler view的这个监听,实时获取recycler view的下滑位移距离
rv.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView rv, int dx, int dy) {
super.onScrolled(rv, dx, dy);
recyclerViewOffset = rv.computeVerticalScrollOffset();
}
});
}
int state = NONE;
private static final int PULL_DOWN = 1;
private static final int RECOVER = 2;
private static final int NONE = 3;
int downY = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;
downY = y;//获取按下时候的纵坐标
break;
case MotionEvent.ACTION_MOVE:
if (state == PULL_DOWN) {
intercepted = true;//如果是正在下拉状态、恢复状态,拦截!
}
if (state == RECOVER) {
intercepted = true;
}
if (state == NONE) {//当recycler view位移为0,且正是下拉手势,进入正在下拉状态
if (recyclerViewOffset == 0 && y - downY > 0) {
state = PULL_DOWN;
intercepted = true;
} else {
intercepted = false;
}
}
break;
}
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = y;
break;
case MotionEvent.ACTION_MOVE:
//这里获取的是鼠标下滑的距离
int verticalOffset = y - downY;
//鼠标下滑的距离 乘以 PULL_RATE,就是header view应该下滑的距离
//因为我发现如果鼠标下滑多少,header view就下来多少,有点不太好看
LinearLayout.LayoutParams params = (LayoutParams) pullView.getLayoutParams();
params.topMargin = -headerHeight + (int) (verticalOffset * PULL_RATE);
pullView.setLayoutParams(params);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//CANCEL情况是手机移出屏幕,导致事件意外取消的情况,和UP事件一致
if (state == PULL_DOWN) {//事件结束,从正在下拉状态 进入 恢复状态
state = RECOVER;
int verticalOffset2 = y - downY;
if (verticalOffset2 > headerBarHeight) {
//这里就是改进的地方,不足距离,正常恢复;超过距离,延时操作
//还有一个改进的地方:我在恢复的时候,封锁了事件;其实应该是在恢复之前可以继续下拉的
} else {
}
//topMargin
//start:从你当前偏移的位置开始
//end:恢复到初始位置
final int start = -headerHeight + (int) (verticalOffset2 * PULL_RATE);
final int end = -headerHeight;
//执行一个value动画
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.setDuration(500).start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//i:这里取得start到end动态在500ms中动态变化的值
int i = (int) animation.getAnimatedValue();
LinearLayout.LayoutParams params = (LayoutParams) pullView.getLayoutParams();
params.topMargin = i;
pullView.setLayoutParams(params);
//结束情况,修改标记位成:无状态
if (i == end) {
state = NONE;
}
}
});
}
break;
}
return true;
}
}
pull fresh view
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="275dp"
android:background="@color/colorPrimary"
tools:layout_editor_absoluteY="81dp">
<ImageView
android:id="@+id/imageView"
android:layout_width="38dp"
android:layout_height="36dp"
android:layout_marginStart="32dp"
android:layout_marginTop="220dp"
android:background="@mipmap/pull_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView"
android:layout_width="387dp"
android:layout_height="69dp"
android:layout_marginTop="208dp"
android:gravity="center"
android:textColor="#FFFFFF"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="下拉刷新" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="43dp"
android:layout_height="41dp"
android:layout_marginEnd="28dp"
android:layout_marginTop="216dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.test.MainActivity">
<com.example.test.PullToRefreshLayout
android:id="@+id/pt"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.example.test.PullToRefreshLayout>
</FrameLayout>