android 增强版 RecyclerView
多 Header / Footer 支持
- 原来只能添加一个,现在可以通过
addHeaderView()
/addFooterView()
多次添加。
- 原来只能添加一个,现在可以通过
空数据视图
- 通过
setEmptyView()
设置,当列表无数据时自动显示。
- 通过
下拉刷新
- 自定义头部视图(默认提供一个简单样式)。
- 可通过
setPullRefreshListener()
设置刷新回调。
上拉加载更多
- 自定义底部视图(默认提供一个简单样式)。
- 可通过
setLoadMoreListener()
设置加载更多回调。
Item 点击 / 长按事件
- 简化外部监听,直接返回业务数据的真实位置。
滚动监听
- 提供更友好的滚动回调接口。
兼容性
- 兼容 Android 4.1 (API 16)。
8、刷新和加载
- 是否启用下拉刷新
- 是否启用上拉加载
- 使用自带的刷新 / 加载更多功能 还是 接入第三方库(如 SmartRefreshLayout、TwinklingRefreshLayout)
下拉刷新
- 在
DefaultPullRefreshImpl
中实现触摸事件处理 - 添加下拉弹性动画和平滑回弹效果
- 状态管理(下拉中、释放可刷新、刷新中、完成)
- 在
上拉加载
- 在
DefaultLoadMoreImpl
中实现滚动监听 - 当滚动到底部时触发加载更多
- 加载完成后更新状态
- 在
设计思路
- 接口隔离
- 定义
PullRefreshable
和LoadMoreable
接口,里面只定义刷新和加载的方法。
- 定义
- 默认实现
- 写一个
DefaultPullRefreshImpl
和DefaultLoadMoreImpl
实现自带的下拉刷新和上拉加载。
- 写一个
- 第三方实现
- 如果你想用第三方库,只需要实现对应的接口,注入到
XCRecyclerView
中。
- 如果你想用第三方库,只需要实现对应的接口,注入到
- 开关控制
- 在
XCRecyclerView
中提供setPullRefreshEnabled(boolean)
和setLoadMoreEnabled(boolean)
方法。
- 在
代码如下
1、接口定义
LoadMoreable
package com.nyw.mvvmmode.widget.recyclerview;/*** 上拉加载更多功能接口* 定义上拉加载的核心操作:启用/禁用、设置监听、触发加载、判断加载状态、设置是否有更多数据*/
public interface LoadMoreable {/*** 启用或禁用上拉加载更多功能* @param enable true=启用,false=禁用*/void setLoadMoreEnabled(boolean enable);/*** 设置上拉加载的回调监听(触发加载时会回调此接口)* @param listener 加载监听器,传null表示取消监听*/void setOnLoadMoreListener(LoadMoreListener listener);/*** 主动触发或停止上拉加载* @param loading true=触发加载(显示加载动画),false=停止加载(隐藏动画)*/void setLoading(boolean loading);/*** 判断当前是否处于上拉加载状态* @return true=正在加载,false=未加载*/boolean isLoading();/*** 设置是否还有更多数据可加载* @param hasMore true=有更多数据(触发加载时会回调),false=无更多数据(隐藏加载视图)*/void setHasMore(boolean hasMore);
}
LoadMoreListener
package com.nyw.mvvmmode.widget.recyclerview;/*** 上拉加载更多的回调接口* 当用户上拉到底部触发加载,或通过代码主动触发加载时,会回调此接口的onLoadMore方法*/
public interface LoadMoreListener {/*** 加载触发时的回调方法* 在这里处理加载更多逻辑(如请求下一页数据、添加到列表)*/void onLoadMore();
}
PullRefreshable
package com.nyw.mvvmmode.widget.recyclerview;/*** 下拉刷新功能接口* 定义下拉刷新的核心操作:启用/禁用、设置监听、触发刷新、判断刷新状态*/
public interface PullRefreshable {/*** 启用或禁用下拉刷新功能* @param enable true=启用,false=禁用*/void setPullRefreshEnabled(boolean enable);/*** 设置下拉刷新的回调监听(刷新触发时会回调此接口)* @param listener 刷新监听器,传null表示取消监听*/void setOnRefreshListener(PullRefreshListener listener);/*** 主动触发或停止下拉刷新* @param refreshing true=触发刷新(显示刷新动画),false=停止刷新(隐藏动画)*/void setRefreshing(boolean refreshing);/*** 判断当前是否处于下拉刷新状态* @return true=正在刷新,false=未刷新*/boolean isRefreshing();
}
PullRefreshListener
package com.nyw.mvvmmode.widget.recyclerview;
/*** 下拉刷新的回调接口* 当用户下拉触发刷新,或通过代码主动触发刷新时,会回调此接口的onRefresh方法*/
public interface PullRefreshListener {/*** 刷新触发时的回调方法* 在这里处理刷新逻辑(如请求网络数据、更新列表)*/void onRefresh();
}
2️⃣ 默认下拉刷新实现(带动画)
package com.nyw.mvvmmode.widget;import android.view.*;
import android.view.animation.*;
import android.widget.*;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;public class DefaultPullRefreshImpl implements PullRefreshable {private final XCRecyclerView recyclerView;private final View refreshHeader;private final ProgressBar progressBar;private final TextView statusText;private boolean enabled = true;private PullRefreshListener listener;private boolean refreshing = false;private int headerHeight;private float downY;private boolean isDragging = false;private int refreshState = STATE_IDLE;private static final int STATE_IDLE = 0; // 空闲private static final int STATE_PULLING = 1; // 下拉中private static final int STATE_RELEASING = 2; // 释放可刷新private static final int STATE_REFRESHING = 3;// 刷新中public DefaultPullRefreshImpl(XCRecyclerView recyclerView) {this.recyclerView = recyclerView;// 加载刷新头部布局this.refreshHeader = LayoutInflater.from(recyclerView.getContext()).inflate(R.layout.xc_recyclerview_refresh_header, null);this.progressBar = refreshHeader.findViewById(R.id.refresh_progress);this.statusText = refreshHeader.findViewById(R.id.refresh_status_text);// 测量头部高度measureHeaderHeight();// 默认隐藏头部setHeaderTopMargin(-headerHeight);// 添加到RecyclerView的HeaderrecyclerView.addHeaderView(refreshHeader);// 设置触摸监听setupTouchListener();}/*** 测量Header高度*/private void measureHeaderHeight() {int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);refreshHeader.measure(w, h);headerHeight = refreshHeader.getMeasuredHeight();}/*** 设置Header的marginTop*/private void setHeaderTopMargin(int margin) {ViewGroup.LayoutParams lp = refreshHeader.getLayoutParams();if (lp instanceof ViewGroup.MarginLayoutParams) {((ViewGroup.MarginLayoutParams) lp).topMargin = margin;refreshHeader.setLayoutParams(lp);}}/*** 设置触摸监听,处理下拉刷新逻辑*/private void setupTouchListener() {recyclerView.setOnTouchListener((v, event) -> {if (!enabled || refreshing) return false;switch (event.getAction()) {case MotionEvent.ACTION_DOWN:downY = event.getY();isDragging = false;break;case MotionEvent.ACTION_MOVE:float moveY = event.getY();float diff = moveY - downY;// 仅在顶部且向下拉时触发if (diff > 0 && !recyclerView.canScrollVertically(-1)) {isDragging = true;float offset = diff / 2; // 阻尼效果setHeaderTopMargin((int) (-headerHeight + offset));// 更新状态文字if (offset >= headerHeight) {refreshState = STATE_RELEASING;statusText.setText("释放刷新");} else {refreshState = STATE_PULLING;statusText.setText("下拉刷新");}return true;}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:if (isDragging) {if (refreshState == STATE_RELEASING) {startRefresh(); // 进入刷新} else {resetHeader(); // 回弹}isDragging = false;downY = -1;return true;}break;}return false;});}/*** 开始刷新*/private void startRefresh() {refreshing = true;refreshState = STATE_REFRESHING;statusText.setText("正在刷新...");progressBar.setVisibility(View.VISIBLE);// 平滑显示HeaderValueAnimator animator = ValueAnimator.ofInt(refreshHeader.getTop(), 0);animator.setDuration(300);animator.setInterpolator(new FastOutSlowInInterpolator());animator.addUpdateListener(animation ->setHeaderTopMargin((Integer) animation.getAnimatedValue()));animator.start();if (listener != null) {listener.onRefresh();}}/*** 重置Header*/private void resetHeader() {ValueAnimator animator = ValueAnimator.ofInt(refreshHeader.getTop(), -headerHeight);animator.setDuration(300);animator.setInterpolator(new FastOutSlowInInterpolator());animator.addUpdateListener(animation ->setHeaderTopMargin((Integer) animation.getAnimatedValue()));animator.start();refreshState = STATE_IDLE;statusText.setText("下拉刷新");progressBar.setVisibility(View.GONE);}@Overridepublic void setPullRefreshEnabled(boolean enable) {this.enabled = enable;}@Overridepublic void setOnRefreshListener(PullRefreshListener listener) {this.listener = listener;}@Overridepublic void setRefreshing(boolean refreshing) {if (this.refreshing == refreshing) return;this.refreshing = refreshing;if (refreshing) {startRefresh();} else {finishRefresh();}}/*** 结束刷新*/public void finishRefresh() {refreshing = false;refreshState = STATE_IDLE;statusText.setText("下拉刷新");progressBar.setVisibility(View.GONE);resetHeader();}@Overridepublic boolean isRefreshing() {return refreshing;}
}
3️⃣ 默认上拉加载实现(带动画)
package com.nyw.mvvmmode.widget;import android.view.*;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;public class DefaultLoadMoreImpl implements LoadMoreable {private final XCRecyclerView recyclerView;private final View loadMoreView;private boolean enabled = true;private LoadMoreListener listener;private boolean loading = false;private boolean hasMore = true;public DefaultLoadMoreImpl(XCRecyclerView recyclerView) {this.recyclerView = recyclerView;// 加载加载更多布局this.loadMoreView = LayoutInflater.from(recyclerView.getContext()).inflate(R.layout.xc_recyclerview_load_more, null);// 添加到底部recyclerView.addFooterView(loadMoreView);// 默认隐藏loadMoreView.setVisibility(View.GONE);// 设置滚动监听setupScrollListener();}/*** 设置滚动监听,判断是否到底部*/private void setupScrollListener() {recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);if (!enabled || loading || !hasMore) return;// 滚动到底部且处于空闲状态时触发加载更多if (newState == RecyclerView.SCROLL_STATE_IDLE &&!recyclerView.canScrollVertically(1)) {startLoadMore();}}});}/*** 开始加载更多*/private void startLoadMore() {loading = true;loadMoreView.setVisibility(View.VISIBLE);// 淡入动画AlphaAnimation fadeIn = new AlphaAnimation(0, 1);fadeIn.setDuration(300);loadMoreView.startAnimation(fadeIn);if (listener != null) {listener.onLoadMore();}}@Overridepublic void setLoadMoreEnabled(boolean enable) {this.enabled = enable;}@Overridepublic void setOnLoadMoreListener(LoadMoreListener listener) {this.listener = listener;}@Overridepublic void setLoading(boolean loading) {this.loading = loading;if (loading) {startLoadMore();} else {finishLoadMore();}}/*** 结束加载更多*/public void finishLoadMore() {loading = false;// 淡出动画AlphaAnimation fadeOut = new AlphaAnimation(1, 0);fadeOut.setDuration(300);fadeOut.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {loadMoreView.setVisibility(View.GONE);}@Overridepublic void onAnimationRepeat(Animation animation) {}});loadMoreView.startAnimation(fadeOut);}@Overridepublic boolean isLoading() {return loading;}@Overridepublic void setHasMore(boolean hasMore) {this.hasMore = hasMore;if (!hasMore) {loadMoreView.setVisibility(View.GONE);}}
}
4️⃣ 刷新头部布局 res/layout/xc_recyclerview_refresh_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"android:padding="12dp"><ProgressBarandroid:id="@+id/refresh_progress"android:layout_width="24dp"android:layout_height="24dp"android:indeterminate="true"android:visibility="gone" /><TextViewandroid:id="@+id/refresh_status_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="8dp"android:text="下拉刷新"android:textColor="#666666"android:textSize="14sp" /></LinearLayout>
5️⃣ 加载更多布局 res/layout/xc_recyclerview_load_more.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="48dp"android:gravity="center"android:orientation="horizontal"><ProgressBarandroid:id="@+id/load_more_progress"android:layout_width="20dp"android:layout_height="20dp"android:indeterminate="true" /><TextViewandroid:id="@+id/load_more_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="8dp"android:text="正在加载更多..."android:textColor="#666666"android:textSize="14sp" /></LinearLayout>
6️⃣ 核心 XCRecyclerView(支持切换刷新 / 加载实现)
package com.nyw.mvvmmode.widget;import android.content.Context;
import android.util.AttributeSet;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;public class XCRecyclerView extends RecyclerView {private PullRefreshable pullRefreshImpl;private LoadMoreable loadMoreImpl;// Header & Footerprivate final List<View> mHeaderViews = new ArrayList<>();private final List<View> mFooterViews = new ArrayList<>();public XCRecyclerView(Context context) {super(context);init(context);}public XCRecyclerView(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public XCRecyclerView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init(context);}private void init(Context context) {// 默认使用自带的下拉刷新和上拉加载pullRefreshImpl = new DefaultPullRefreshImpl(this);loadMoreImpl = new DefaultLoadMoreImpl(this);}/*** 添加Header*/public void addHeaderView(View view) {mHeaderViews.add(view);}/*** 添加Footer*/public void addFooterView(View view) {mFooterViews.add(view);}/*** 切换下拉刷新实现(可传入第三方)*/public void setPullRefreshImpl(PullRefreshable impl) {this.pullRefreshImpl = impl;}/*** 切换上拉加载实现(可传入第三方)*/public void setLoadMoreImpl(LoadMoreable impl) {this.loadMoreImpl = impl;}/*** 启用/禁用下拉刷新*/public void setPullRefreshEnabled(boolean enable) {pullRefreshImpl.setPullRefreshEnabled(enable);}/*** 启用/禁用上拉加载*/public void setLoadMoreEnabled(boolean enable) {loadMoreImpl.setLoadMoreEnabled(enable);}/*** 设置下拉刷新监听*/public void setOnRefreshListener(PullRefreshListener listener) {pullRefreshImpl.setOnRefreshListener(listener);}/*** 设置上拉加载监听*/public void setOnLoadMoreListener(LoadMoreListener listener) {loadMoreImpl.setOnLoadMoreListener(listener);}/*** 主动触发刷新*/public void setRefreshing(boolean refreshing) {pullRefreshImpl.setRefreshing(refreshing);}/*** 主动触发加载更多*/public void setLoading(boolean loading) {loadMoreImpl.setLoading(loading);}/*** 设置是否还有更多数据*/public void setHasMore(boolean hasMore) {loadMoreImpl.setHasMore(hasMore);}
}
7️⃣ 使用示例
XCRecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));// 启用下拉刷新
recyclerView.setPullRefreshEnabled(true);
recyclerView.setOnRefreshListener(() -> {new Handler().postDelayed(() -> {// 刷新数据recyclerView.setRefreshing(false);}, 2000);
});// 启用上拉加载
recyclerView.setLoadMoreEnabled(true);
recyclerView.setOnLoadMoreListener(() -> {new Handler().postDelayed(() -> {// 加载数据recyclerView.setLoading(false);}, 2000);
});recyclerView.setAdapter(adapter);
一个完整的、带动画的、可切换第三方库的 XCRecyclerView
,并且全部代码都有详细的中文注释。
这样做的好处
✅ 功能解耦:刷新和加载更多逻辑独立,方便替换不同实现✅ 灵活扩展:支持自带实现和第三方库✅ 控制方便:一行代码开关功能✅ 易于维护:接口清晰,逻辑分明