在上一章中,我们了解了RecyclerView
控件的基本用法,这里我们将实现RecyclerView
的刷新和加载。
我们可以利用SwipeRefreshLayout
控件来实现下拉刷新,详见Android SwipeRefreshLayout控件。
我们首先创建一个下拉刷新的父类RefreshViewCreator
,一般拥有四种状态,普通、下拉刷新、释放刷新和刷新。
主要方法如下
View onCreateRefreshView(Context context, ViewGroup parent)
,创建刷新界面int move(int dy)
,用于下拉时改变界面release(float dy)
,用于释放时改变界面refreshFinish()
,刷新结束,一般用于回调具体代码如下,
public abstract class RefreshViewCreator {
public final static int DONE = 0;
public final static int PULL_TO_REFRESH = 1; // 下拉刷新
public final static int RELEASE_TO_REFRESH = 2; // 释放刷新
public final static int REFRESHING = 3; // 刷新
public IOnRefreshListener mListener;
public RefreshViewCreator(IOnRefreshListener listener) {
this.mListener = listener;
}
// 创建刷新界面
public abstract View onCreateRefreshView(Context context, ViewGroup parent);
// 下拉时改变界面
public int move(int dy) {
return 0;
}
// 释放时改变界面
public void release(float dy) {
}
// 刷新结束
public void refreshFinish() {
}
protected void startRefresh() {
if (mListener != null) {
mListener.onRefresh();
}
}
public interface IOnRefreshListener {
void onRefresh();
}
}
CustomRecyclerView
继承RecyclerView
,默认使用LinearLayoutManager
布局。
public class CustomRecyclerView extends RecyclerView {
private LinearLayoutManager mLayoutManager;
private RefreshViewCreator mRefreshViewCreator;
private boolean mRecord;
private int mLastEventY;
private boolean mRefresh;
private int mTouchSlop;
public CustomRecyclerView(@NonNull Context context) {
this(context, null);
}
public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
setLayoutManager(mLayoutManager);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public void setRefreshViewCreator(RefreshViewCreator refreshViewCreator) {
this.mRefreshViewCreator = refreshViewCreator;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
record(ev);
break;
case MotionEvent.ACTION_MOVE:
record(ev);
int dy = (int) (ev.getY() - mLastEventY);
mLastEventY = (int) ev.getY();
if (allowRefresh()) {
if (dy > mTouchSlop) {
mRefresh = true;
}
if (mRefresh) {
int distance = mRefreshViewCreator.move(dy / 3);
if (distance == 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
return dispatchTouchEvent(ev);
}
return true;
}
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mRefresh) {
mRefreshViewCreator.release((int) (ev.getY() - mLastEventY) / 3);
mRefresh = false;
mLastEventY = 0;
mRecord = false;
return true;
}
break;
}
return super.dispatchTouchEvent(ev);
}
private void record(MotionEvent ev) {
if (allowRefresh() && !mRecord) {
mLastEventY = (int) ev.getY();
mRecord = true;
}
}
// 是否滚动到表头了
private boolean allowRefresh() {
return mRefreshViewCreator != null && !canScrollVertically(-1);
}
}
我们通过allowRefresh()
方法来判断是否已经滚动到表头了,这里有个问题是canScrollVertically()
方法,当direction等于-1的时候,才是判断是否可以下拉。另外如果刷新界面高度是0或者gone的时候,canScrollVertically(-1)
始终返回true。
/**
* Check if this view can be scrolled vertically in a certain direction.
*
* @param direction Negative to check scrolling up, positive to check scrolling down.
* @return true if this view can be scrolled in the specified direction, false otherwise.
*/
public boolean canScrollVertically(int direction) {
final int offset = computeVerticalScrollOffset();
final int range = computeVerticalScrollRange() - computeVerticalScrollExtent();
if (range == 0) return false;
if (direction < 0) {
return offset > 0;
} else {
return offset < range - 1;
}
}
在Activity
里面,创建一个RefreshViewCreator
,利用RecyclerViewAdapter
加入到表头第一行,RecyclerViewAdapter
可查看上一章。
private Handler mHandler = new Handler();
private RefreshViewCreator mRefreshViewCreator;
private RefreshViewCreator.IOnRefreshListener mListener = new RefreshViewCreator.IOnRefreshListener() {
@Override
public void onRefresh() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mRefreshViewCreator.refreshFinish();
}
}, 3000);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_material_design_recycler_view_refresh);
CustomRecyclerView recyclerView = findViewById(R.id.recycler_view);
RecyclerViewAdapter adapter = new RecyclerViewAdapter(this);
recyclerView.setAdapter(adapter);
mRefreshViewCreator = new CustomRefreshViewCreator(mListener);
recyclerView.setRefreshViewCreator(mRefreshViewCreator);
adapter.addHeaderView(mRefreshViewCreator.onCreateRefreshView(this, recyclerView));
}
CustomRefreshViewCreator
继承RefreshViewCreator
,实现自定义刷新界面
public class CustomRefreshViewCreator extends RefreshViewCreator {
private View mRefreshView;
private ImageView mIvArrow;
private ProgressBar mProgressBar;
private TextView mTvRefreshTips;
private String mPullToRefreshTips, mReleaseToRefreshTips, mRefreshingTips;
private int mContentHeight;
private int mHeadState = DONE;
private int mDistance = 0;
public CustomRefreshViewCreator(IOnRefreshListener listener) {
super(listener);
mPullToRefreshTips = "下拉刷新";
mReleaseToRefreshTips = "松开刷新";
mRefreshingTips = "正在刷新...";
}
@Override
public View onCreateRefreshView(Context context, ViewGroup parent) {
if (mRefreshView == null) {
mRefreshView = LayoutInflater.from(context).inflate(getLayoutId(), parent, false);
mContentHeight = context.getResources().getDimensionPixelOffset(R.dimen.margin_dpi_50);
mIvArrow = mRefreshView.findViewById(R.id.iv_arrow);
mProgressBar = mRefreshView.findViewById(R.id.progress_bar_refresh);
mTvRefreshTips = mRefreshView.findViewById(R.id.tv_refresh_tips);
refreshFinish();
}
return mRefreshView;
}
protected @LayoutRes int getLayoutId() {
return R.layout.list_view_refresh_view;
}
@Override
public int move(int dy) {
if (mHeadState != REFRESHING) {
mDistance += dy;
if (mDistance < 0) {
mDistance = 0;
} else if (mDistance > mContentHeight + 10) {
mDistance = mContentHeight + 10;
}
if (mDistance <= 0) {
mHeadState = DONE;
} else if (mDistance >= mContentHeight) {
mHeadState = RELEASE_TO_REFRESH;
mIvArrow.setRotation(180);
} else {
mHeadState = PULL_TO_REFRESH;
mIvArrow.setRotation(0);
}
if (mHeadState == PULL_TO_REFRESH || mHeadState == RELEASE_TO_REFRESH) {
int padding = mDistance - mContentHeight;
mRefreshView.setPadding(0, padding > 0 ? 0 : padding, 0, 0);
}
changeHeaderViewByState();
}
return mDistance;
}
@Override
public void release(float dy) {
if (mHeadState != REFRESHING) {
mDistance += dy;
if (mDistance >= mContentHeight) {
mHeadState = REFRESHING;
} else {
mHeadState = DONE;
}
mDistance = 0;
changeHeaderViewByState();
if (mHeadState == REFRESHING) {
startRefresh();
}
}
}
@Override
public void refreshFinish() {
mHeadState = DONE;
changeHeaderViewByState();
}
private void changeHeaderViewByState() {
switch (mHeadState) {
case DONE:
mRefreshView.setPadding(0, -1 * mContentHeight + 1, 0, 0);
mIvArrow.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
mTvRefreshTips.setText(mPullToRefreshTips);
break;
case PULL_TO_REFRESH:
mIvArrow.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
mTvRefreshTips.setText(mPullToRefreshTips);
break;
case RELEASE_TO_REFRESH:
mIvArrow.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
mTvRefreshTips.setText(mReleaseToRefreshTips);
break;
case REFRESHING:
mRefreshView.setPadding(0, 0, 0, 0);
mIvArrow.setVisibility(View.GONE);
mProgressBar.setVisibility(View.VISIBLE);
mTvRefreshTips.setText(mRefreshingTips);
break;
}
}
}
刷新界面list_view_refresh_view.xml
上拉加载的实现类似于刷新,同样需要上拉加载的父类LoadViewCreator
public abstract class LoadViewCreator {
public final static int DONE = 0;
public final static int PULL_TO_LOAD = 1; // 上拉加载
public final static int RELEASE_TO_LOAD = 2; // 释放加载
public final static int LOADING = 3; // 加载
public IOnLoadListener mListener;
public LoadViewCreator(IOnLoadListener listener) {
this.mListener = listener;
}
public abstract View onCreateLoadView(Context context, ViewGroup parent);
public int move(int dy) {
return 0;
}
public void release(float dy) {
}
public void loadFinish() {
}
protected void startLoad() {
if (mListener != null) {
mListener.onLoad();
}
}
public interface IOnLoadListener {
void onLoad();
}
}
自定义LoadViewCreator
的实现类似于CustomRefreshViewCreator
,只需要注意的是我们设置bottom
的padding
。
mLoadView.setPadding(0, 0, 0, padding);
而在CustomRecyclerView
里面,同样在dispatchTouchEvent()
方法里面处理事件吗,只是在调用LoadViewCreator
的move()
和release()`方法,需要把参数的值转换成正数。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
record(ev);
break;
case MotionEvent.ACTION_MOVE:
record(ev);
int dy = (int) (ev.getY() - mLastEventY);
mLastEventY = (int) ev.getY();
if (allowRefresh()) {
if (dy > mTouchSlop) {
mRefresh = true;
}
if (mRefresh) {
int distance = mRefreshViewCreator.move(dy / 3);
if (distance == 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
return dispatchTouchEvent(ev);
}
return true;
}
}
if (allowLoad()) {
if (-dy > mTouchSlop) {
mLoad = true;
}
if (mLoad) {
int distance = mLoadViewCreator.move(-dy);
if (distance == 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
return dispatchTouchEvent(ev);
}
return true;
}
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mRecord = false;
if (mRefresh) {
mRefreshViewCreator.release((int) (ev.getY() - mLastEventY) / 3);
mRefresh = false;
mLastEventY = 0;
return true;
}
if (mLoad) {
mLoadViewCreator.release((int) (mLastEventY - ev.getY()));
mLoad = false;
mLastEventY = 0;
return true;
}
break;
}
return super.dispatchTouchEvent(ev);
}
private void record(MotionEvent ev) {
if ((allowRefresh() || allowLoad()) && !mRecord) {
mLastEventY = (int) ev.getY();
mRecord = true;
}
}
private boolean allowLoad() {
return mLoadViewCreator != null && !canScrollVertically(1);
}
当用户向上翻动列表,快要到所有列表项的底部或者已经到底部的时候,可以自动加载更多的数据。为此,我们需要监听RecyclerView
列表的滚动,
public void addOnScrollListener(OnScrollListener listener)
同时也获取列表最下面的元素是哪一行,LinearLayoutManager
为我们提供了很多接口
public int findFirstVisibleItemPosition()
public int findFirstCompletelyVisibleItemPosition()
public int findLastVisibleItemPosition()
public int findLastCompletelyVisibleItemPosition()
实现代码如下
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(layoutManager);
final RecyclerViewAdapter adapter = new RecyclerViewAdapter(this);
recyclerView.setAdapter(adapter);
View footerView = getLayoutInflater().inflate(R.layout.list_view_load_more_view, recyclerView, false);
adapter.addFooterView(footerView);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (!mLoadMore && layoutManager.findLastVisibleItemPosition() == adapter.getItemCount() - 1) {
mLoadMore = true;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
adapter.addContent();
mLoadMore = false;
}
}, 1000);
}
}
});