本文共 17074 字,大约阅读时间需要 56 分钟。
通过导入第三方库实现ListView的上拉加载和下拉刷新比较简单,今天我要讲的是自定义RecyclerView实现下拉刷新和上拉加载。首先,自定义下拉刷新上拉加载是通过给RecyclerView添加头部和尾部实现的。而问题是RecyclerView并没有addHeaderView(View v)和addFooterView(View v)方法。
第一步:自定义HeaderAndFooterWrapper(装饰者模式)实现给RecyclerView添加头部和尾部
方法:public void addHeaderView(View v) public void addFooterView(View v) 代码参照张鸿洋:Android 优雅的为RecyclerView添加HeaderView和FooterView 链接:第二步:定义类RefreshRecyclerView并抽取为库
1) 头部布局:refresh_recyclerview_header.xml
<?xml version="1.0" encoding="utf-8"?>
<!--下拉刷新控件-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp">
<ImageView
android:id="@+id/iv_header_refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/headview_red_arrow" />
<ProgressBar
android:id="@+id/pb_header_refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminateDrawable="@drawable/custom_progressbar"
android:visibility="gone" />
</FrameLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/tv_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="下拉刷新"
android:textColor="#ff0000"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:gravity="center_horizontal"
android:text="上次更新时间:2016-10-31"
android:textColor="#55000000"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
2)尾部布局(上拉加载部分):refresh_recyclerview_footer.xml
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ProgressBar
android:indeterminateDrawable="@drawable/custom_progressbar"
android:layout_margin="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:text="加载更多中...."
android:textColor="#ff0000"
android:textSize="25sp"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout></span>
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%">
<shape android:shape="ring"
android:innerRadiusRatio="2.5"
android:thicknessRatio="15"
android:useLevel="false"
>
<gradient android:startColor="#ff0000"
android:endColor="#ffffff"
android:centerColor="#88ff0000"
android:type="sweep"></gradient>
</shape>
</rotate></span>
代码如下:
public class RefreshRecyclerView extends RecyclerView {
private final Context mContext;
// 顶部视图,下拉刷新控件
private LinearLayout headerView;
// 正在刷新状态的进度条
private ProgressBar pb_header_refresh;
// 刷新箭头
private ImageView iv_header_refresh;
// 显示刷新状态
private TextView tv_status;
// 显示最近一次的刷新时间
private TextView tv_time;
// 转到下拉刷新状态时的动画
private RotateAnimation downAnima;
// 转到释放刷新状态时的动画
private RotateAnimation upAnima;
//触摸事件中按下的Y坐标,初始值为-1,为防止ACTION_DOWN事件被抢占
private float startY = -1;
// 下拉刷新控件的高度
private int pulldownHeight;
// 刷新状态:下拉刷新
private final int PULL_DOWN_REFRESH = 0;
// 刷新状态:释放刷新
private final int RELEASE_REFRESH = 1;
// 刷新状态:正常刷新
private final int REFRESHING = 2;
// 当前头布局的状态-默认为下拉刷新
private int currState = PULL_DOWN_REFRESH;
//
private RefreshRecyclerView.OnRefreshListener mOnRefreshListener;
// 尾部视图
private View footerView;
// 尾部试图(上拉加载控件)的高度
private int footerViewHeight;
// 判断是否是加载更多
private boolean isLoadingMore;
public RefreshRecyclerView(Context context) {
this(context, null);
}
public RefreshRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RefreshRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initHeaderView();
initFooterView();
}
/**
* 返回尾部布局,供外部调用
* @return
*/
public View getFooterView(){
return footerView;
}
/**
* 返回头部布局,供外部调用
* @return
*/
public View getHeaderView(){
return headerView;
}
/**
* 通过HeaderAndFooterWrapper对象给RecyclerView添加尾部
* @param footerView 尾部视图
* @param headerAndFooterWrapper RecyclerView.Adapter的包装类对象,通过它给RecyclerView添加尾部视图
*/
public void addFooterView(View footerView, HeaderAndFooterWrapper headerAndFooterWrapper) {
headerAndFooterWrapper.addFooterView(footerView);
}
/**
* 通过HeaderAndFooterWrapper对象给RecyclerView添加头部部
* @param headerView 尾部视图
* @param headerAndFooterWrapper RecyclerView.Adapter的包装类对象,通过它给RecyclerView添加头部视图
*/
public void addHeaderView(View headerView,HeaderAndFooterWrapper headerAndFooterWrapper) {
headerAndFooterWrapper.addHeaderView(headerView);
}
初始化头部布局和尾部布局
private void initHeaderView() {
headerView = (LinearLayout) View.inflate(mContext, R.layout.refresh_recyclerview_header, null);
tv_time = (TextView) headerView.findViewById(R.id.tv_time);
tv_status = (TextView) headerView.findViewById(R.id.tv_status);
iv_header_refresh = (ImageView) headerView.findViewById(R.id.iv_header_refresh);
pb_header_refresh = (ProgressBar) headerView.findViewById(R.id.pb_header_refresh);
headerView.measure(0, 0);
pulldownHeight = headerView.getMeasuredHeight();
headerView.setPadding(0, -pulldownHeight, 0, 0);
//初始化头部布局的动画
initAnimation();
}
/**
* 刷新状态改变时的动画
*/
private void initAnimation() {
// 从下拉刷新状态转换为释放刷新状态
upAnima = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
upAnima.setFillAfter(true);
upAnima.setDuration(500);
// 转到下拉刷新的动画
downAnima = new RotateAnimation(-180, -360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
downAnima.setFillAfter(true);
downAnima.setDuration(500);
}
private void initFooterView() {
footerView = View.inflate(mContext, R.layout.refresh_recyclerview_footer, null);
footerView.measure(0, 0);
//得到控件的高
footerViewHeight = footerView.getMeasuredHeight();
//默认隐藏下拉刷新控件
// View.setPadding(0,-控件高,0,0);//完全隐藏
//View.setPadding(0, 0,0,0);//完全显示
footerView.setPadding(0, -footerViewHeight, 0, 0);
// addFooterView(footerView);
// 自己监听自己
this.addOnScrollListener(new MyOnScrollListener());
}
重写onTouchEvent()方法,startY表示按下时的坐标,但是有可能ACTION_DOWN事件被强占,那么case MotionEvent.ACTION_DOWN:就没机会执行。所以case MotionEvent.ACTION_MOVE:子句时首先判断startY是否为-1,是的话把第一次移动的坐标当作按下时的坐标。然后判断当前的状态,如果正在刷新就不必再次执行下面的刷新代码,直接跳出。dY>0表示用户正在执行下拉操作,因为头部视图初始坐标为-pulldownHeight,所以手指在屏幕上滑动多少距离,顶部视图的y坐标就应该增加相应值。paddingTop==0是一个状态分界线,paddingTop < 0 是下拉刷新状态,大于0是释放刷新状态。根据paddingTop的值跳转相应的状态。最后在手指抬起的时候,首先重置startY = -1;然后判断刷新状态,如果是下拉刷新(头部视图没有完全显示)就设置下拉控件为默认隐藏状态,如果是释放刷新状态(paddingTop > 0),就跳转到正在刷新状态,并让下拉控件完全显示同时调用用户的回调事件,刷新页面数据。
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//防止ACTION_DOWN事件被抢占,没有执行
if (startY == -1) {
startY = ev.getY();
}
float endY = ev.getY();
float dY = endY - startY;
//判断当前是否正在刷新中
if (currState == REFRESHING) {
//如果当前是正在刷新,不执行下拉刷新了,直接break;
break;
}
// 如果是下拉
if (dY > 0) {
int paddingTop = (int) (dY - pulldownHeight);
if (paddingTop > 0 && currState != RELEASE_REFRESH) {
//完全显示下拉刷新控件,进入松开刷新状态
currState = RELEASE_REFRESH;
refreshViewState();
} else if (paddingTop < 0 && currState != PULL_DOWN_REFRESH) {
//没有完全显示下拉刷新控件,进入下拉刷新状态
currState = PULL_DOWN_REFRESH;
refreshViewState();
}
headerView.setPadding(0, paddingTop, 0, 0);
}
break;
case MotionEvent.ACTION_UP:
//5.重新记录值
startY = -1;
if (currState == PULL_DOWN_REFRESH) {
//设置默认隐藏
headerView.setPadding(0, -pulldownHeight, 0, 0);
} else if (currState == RELEASE_REFRESH) {
//当前是释放刷新,进入到正在刷新状态,完全显示
currState = REFRESHING;
refreshViewState();
headerView.setPadding(0, 0, 0, 0);
//调用用户的回调事件,刷新页面数据
if (mOnRefreshListener != null) {
mOnRefreshListener.onPullDownRefresh();
}
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 跳转刷新状态
*/
private void refreshViewState() {
switch (currState) {
// 跳转到下拉刷新
case PULL_DOWN_REFRESH:
iv_header_refresh.startAnimation(downAnima);
tv_status.setText("下拉刷新");
break;
// 跳转到释放刷新
case RELEASE_REFRESH:
iv_header_refresh.startAnimation(upAnima);
tv_status.setText("释放刷新");
break;
// 跳转到正在刷新
case REFRESHING:
iv_header_refresh.clearAnimation();
iv_header_refresh.setVisibility(GONE);
pb_header_refresh.setVisibility(VISIBLE);
tv_status.setText("正在刷新中.....");
break;
}
}
定义接口
/**
* 定义下拉刷新和上拉加载的接口
*/
public interface OnRefreshListener {
/**
* 当下拉刷新时触发此方法
*/
void onPullDownRefresh();
/**
* 当加载更多的时候回调这个方法
*/
void onLoadingMore();
}
public void setOnRefreshListener(RefreshRecyclerView.OnRefreshListener listener) {
this.mOnRefreshListener = listener;
}
/**
* 当刷新完数据之后,调用此方法,把头文件隐藏,并且状态设置为初始状态
* @param isSuccess
*/
public void onFinishRefresh(boolean isSuccess) {
if (isLoadingMore) {
footerView.setPadding(0, -footerViewHeight, 0, 0);
isLoadingMore = false;
} else {
headerView.setPadding(0, -pulldownHeight, 0, 0);
currState = PULL_DOWN_REFRESH;
iv_header_refresh.setVisibility(VISIBLE);
pb_header_refresh.setVisibility(GONE);
tv_status.setText("下拉刷新");
if (isSuccess) {
//设置更新时间
tv_time.setText(getSystemTime());
}
}
}
private String getSystemTime() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return dateFormat.format(new Date());
}
上拉加载逻辑的实现,重写onScrollStateChanged()方法。如果newState为空闲状态或者是快速滑动状态就判断RecyclerView的最后一个可见Item的索引是否 >= getChildCount()-1。是的话就触发加载更多事件,完全显示上拉加载控件并调用回调事件。这里要注意:ListView是SCROLL_STATE_FLING而RecyclerView是SCROLL_STATE_SETTLING,ListView得到最后一个可见Item索引直接
getLastVisiblePosition()方法就行了。RecyclerView比较麻烦:首先要判断布局管理器是否是线性布局管理器,因为只有LinearLayoutManager才有查找第一个和最后一个可见View位置的方法。
private class MyOnScrollListener extends OnScrollListener {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
// super.onScrollStateChanged(recyclerView, newState);
if (newState == SCROLL_STATE_IDLE || newState == SCROLL_STATE_SETTLING) {
//判断是当前layoutManager是否为LinearLayoutManager
// 只有LinearLayoutManager才有查找第一个和最后一个可见view位置的方法
LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
//当停止滚动时或者惯性滚动时,RecyclerView的最后一个显示的条目:getCount()-1
// 注意是findLastVisibleItemPosition()而不是getLastVisiblePosition
if (linearLayoutManager.findLastVisibleItemPosition() >= getChildCount() - 1) {
isLoadingMore = true;
//把底部加载显示
footerView.setPadding(0, 0, 0, 0);
if (mOnRefreshListener != null) {
mOnRefreshListener.onLoadingMore();
}
}
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.refreshrecyclerview_library.RefreshRecyclerView
android:id="@+id/custom_recyclerview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:cacheColorHint="@android:color/transparent"
android:divider="@null"
android:fadingEdge="none" />
</LinearLayout>
public class MainActivity extends Activity {
private static final int REFRESH = 0;
private static final int LOADMORE = 1;
private HeaderAndFooterWrapper headerAndFooterWrapper;
private RefreshRecyclerViewAdapter recyclerAdapter;
private RefreshRecyclerView custom_recyclerview;
private Handler handler = new Handler(){
public void handleMessage(Message msg){
switch (msg.what) {
case REFRESH:
custom_recyclerview.onFinishRefresh(true);
break;
case LOADMORE:
custom_recyclerview.onFinishRefresh(false);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
custom_recyclerview = (RefreshRecyclerView) findViewById(R.id.custom_recyclerview);
initRefreshRecyclerView();
}
private void initRefreshRecyclerView() {
// 给Recycler设置分割线
custom_recyclerview.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
recyclerAdapter = new RefreshRecyclerViewAdapter(this);
headerAndFooterWrapper = new HeaderAndFooterWrapper(recyclerAdapter);
// 不要忘记设置布局管理器
custom_recyclerview.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
custom_recyclerview.setAdapter(headerAndFooterWrapper);
custom_recyclerview.addHeaderView(custom_recyclerview.getHeaderView(), headerAndFooterWrapper);
custom_recyclerview.addFooterView(custom_recyclerview.getFooterView(), headerAndFooterWrapper);
custom_recyclerview.setOnRefreshListener(new OnRecyclerRefreshListener());
}
private class OnRecyclerRefreshListener implements RefreshRecyclerView.OnRefreshListener {
@Override
public void onPullDownRefresh() {
// 执行下拉刷新操作,一般是联网更新数据
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
handler.sendEmptyMessage(REFRESH);
}
}).start();
}
@Override
public void onLoadingMore() {
// 执行上拉加载操作,一般是联网请求更多分页数据
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
handler.sendEmptyMessage(LOADMORE);
}
}).start();
}
}
}
<pre name="code" class="java">
public class RefreshRecyclerViewAdapter extends RecyclerView.Adapter<RefreshRecyclerViewAdapter.ViewHolder> {
private Context mContext;
private List<String> datas;
public RefreshRecyclerViewAdapter(Context mContext) {
this.mContext = mContext;
datas = new ArrayList<>();
for(int i = 1; i <= 20; i++) {
datas.add("我是Content :"+i);
}
}
@Override
public RefreshRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View convertView = View.inflate(mContext, R.layout.item_recycler, null);
return new ViewHolder(convertView);
}
@Override
public void onBindViewHolder(RefreshRecyclerViewAdapter.ViewHolder holder, final int position) {
String data = datas.get(position);
holder.tv_content.setText(data);
}
@Override
public int getItemCount() {
return datas.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView tv_content;
public ViewHolder(final View itemView) {
super(itemView);
tv_content = (TextView) itemView.findViewById(R.id.tv);
}
}
}
这里还用到一个分割线的类,RecyclerView默认是没有分割线的,这段代码可以直接粘贴使用
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
public final static int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
public final static int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
private int mOrientation;
private Drawable mDivider;
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
public DividerItemDecoration(Context context, int mOrientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(mOrientation);
}
private void setOrientation(int mOrientation) {
if (mOrientation != VERTICAL_LIST && mOrientation != HORIZONTAL_LIST) {
throw new IllegalArgumentException("Invalid orientation");
} else {
this.mOrientation = mOrientation;
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else if (mOrientation == HORIZONTAL_LIST) {
drawHorizontal(c, parent);
}
}
private void drawVertical(Canvas c, RecyclerView parent) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private void drawHorizontal(Canvas c, RecyclerView parent) {
int top = parent.getPaddingTop();
int bottom = parent.getHeight() - parent.getPaddingBottom();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getRight() + params.rightMargin;
int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left,top,right,bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// super.getItemOffsets(outRect, view, parent, state);
if(mOrientation == VERTICAL_LIST) {
outRect.set(0,0,0,mDivider.getIntrinsicHeight());
}else{
outRect.set(0,0,mDivider.getIntrinsicHeight(),0);
}
}
}