首先看效果
下拉刷新:
上划加载
项目github地址:https://github.
并且编译好的demo apk包也在github上
在项目更新的过程中,遇到了一个将XListView换成recyclerView的需求,而且更换完之后大体效果不能变,但是对于下拉刷新这样的效果,谷歌给出的解决方案是把RecyclerView放在一个SwipeRefreshLayout中,但是这样其实是拉下一个小圆形控件实现的,和XListView的header效果不同。在网上找了很多的别人代码,都没有实现我想要的效果,于是自己动手写了一个。
具体实现的效果有以下几条
下拉刷新功能:
1、实现一个有弹性的拖出效果:思路参考XListView,recyclerView的position=0的位置放一个header布局,这个布局的margin top默认为负的布局高度,所以这块布局就一直处于屏幕外部,在下拉的时候通过onTouchListener根据手指的移动动态修改margin top,慢慢的拖出,当拖出的距离也就是margin top变为正数以后,就盖面header布局的状态,改变箭头的方向并改变提示语
2、实现有弹性的回弹效果:用timerTask写了一个动态修改的header布局的margin top的动画,每隔一定的时间减小margin top的值,当用户松手的时候通过一个函数updateHeaderHeight()来执行这个动画。
3、实现用户非手动拖拉的自动刷新效果:这个recyclerView还有一个方法叫forceRefresh(),就是不需要用户手动下拉,头部自己滚动出来,然后刷新完再自己收回去,自动下拉也是用一个timerTask每隔十几毫秒增加margin top的值让头部慢慢露出来
上划加载更多功能:
1、实现滚动到底部自动停住效果: 有时候recyclerVIew滚动太快,滚到底部的时候会根据惯性向上飘,这个地方到底的时候监控recyclerView滚动速度,如果非常快说明是惯性滚动,就不修改footer布局的高度
2、实现向上拖动效果:复写了recyclerView的onScrollListener,在手指向上滚动的时候,通过updateFooterHeight()方法动态修改底部footerView的margin bottom,同headerView一样,在手指移动的时候让这个margin跟着变大,以增加footer布局的高度,而且手指移动的越网上,增加的margin的高度就越小,实现一个有弹性的上拉效果,防止误操作。
3、实现自动回弹的效果:通过监控footer布局的margin bottom来确定松手的时候是否需要开始刷新,如果margin bottom大于某个值得时候就修改footer布局的状态从normal变成ready,在ready状态下松手就开始刷新操作,回弹也像header布局一样通过一个timerTask每隔十几毫秒修改margin的大小来实现回弹效果
注意事项:
1、为了实现头部和底部的代码分离,头部用的是onTouchListener,底部用的是onScrollListener
2、本recyclerVIew里面已经内置了一个layoutManager,所以不要给recyclerView再设置layoutManager,否则会出现头部不出来,下拉报空指针的情况,底部出现但是滑动没有效果
3、这个recyclerView内置了一个抽象类作为adapter,请继承这个内置的AlxDragRecyclerViewAdapter使用,或者按照这里面的逻辑重新写adapter
有其他的问题欢迎问我
4、一些常用的功能,比如设置该控件是否能够支持下拉加载和上拉刷新,等等api接口,请直接参考XListView的用法即可
使用方法:
继承AlxDragRecyclerViewAdapter写一个adapter,然后写两个类分别实现OnRefreshListener和LoadMoreListener,把具体的刷新逻辑写在里面,在准备好显示数据后调用adapter的notifyDataSetChanged()方法或notifyItemInserted()方法,并执行该recyclerView的stopLoadmore()方法和stopRefresh()方法。
下面是代码,这个控件有很多的内部类,包括头部,底部的布局控件(CustomDragHeaderView,CustomDragFooterView),adapter,layoutmanager都已经以内部类的方式集成在里面,减少迁移时候的复杂度
import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.xxx.app.R;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by Alex on 2016/1/27.
*/
public class AlxRefreshLoadMoreRecyclerView extends RecyclerView {
private int footerHeight = -1;
LinearLayoutManager layoutManager;
// -- footer view
private CustomDragRecyclerFooterView mFooterView;
private boolean mEnablePullLoad;
private boolean mPullLoading;
private boolean isBottom;
private boolean mIsFooterReady = false;
private LoadMoreListener loadMoreListener;
// -- header view
private CustomDragHeaderView mHeaderView;
private boolean mEnablePullRefresh = true;
private boolean mIsRefreshing;
private boolean isHeader;
private boolean mIsHeaderReady = false;
private Timer timer;
private float oldY;
Handler handler = new Handler();
private OnRefreshListener refreshListener;
private AlxDragRecyclerViewAdapter adapter;
private int maxPullHeight = 50;//最多下拉高度的px值
private static final int HEADER_HEIGHT = 68;//头部高度68dp
private static final int MAX_PULL_LENGTH = 150;//最多下拉150dp
private OnClickListener footerClickListener;
public AlxRefreshLoadMoreRecyclerView(Context context) {
super(context);
initView(context);
}
public AlxRefreshLoadMoreRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public AlxRefreshLoadMoreRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
}
public void setAdapter(AlxDragRecyclerViewAdapter adapter){
super.setAdapter(adapter);
this.adapter = adapter;
}
public boolean ismPullLoading() {
return mPullLoading;
}
public boolean ismIsRefreshing() {
return mIsRefreshing;
}
private void updateFooterHeight(float delta) {
if(mFooterView==null)return;
int bottomMargin = mFooterView.getBottomMargin();
// Log.i("Alex3","初始delta是"+delta);
if(delta>50)delta = delta/6;
if(delta>0) {//越往下滑越难滑
if(bottomMargin>maxPullHeight)delta = delta*0.65f;
else if(bottomMargin>maxPullHeight * 0.83333f)delta = delta*0.7f;
else if(bottomMargin>maxPullHeight * 0.66667f)delta = delta*0.75f;
else if(bottomMargin>maxPullHeight >> 1)delta = delta*0.8f;
else if(bottomMargin>maxPullHeight * 0.33333f)delta = delta*0.85f;
else if(bottomMargin>maxPullHeight * 0.16667F && delta > 20)delta = delta*0.2f;//如果是因为惯性向下迅速的俯冲
else if(bottomMargin>maxPullHeight * 0.16667F)delta = delta*0.9f;
// Log.i("Alex3","bottomMargin是"+mFooterView.getBottomMargin()+" delta是"+delta);
}
int height = mFooterView.getBottomMargin() + (int) (delta+0.5);
if (mE