阅读XRecyclerView源码一

时间:2022-12-11 16:57:34

学习目标

熟悉并掌握 XRecyclerView 实现下拉刷新的过程

阅读XRecyclerView源码一

概述

XRecyclerView这个开源项目,可以帮我们轻松的实现 RecyclerView 下拉刷新和上拉加载更多的功能,而且使用步骤和普通的 RecyclerView 一样,而且配合了 AVLoadingIndicatorView 可以方便的实现多种加载动画效果。

接下来试着拆下这个*,熟悉和学习它的实现过程。可以先从自己关注的功能来分步骤的阅读源码。这篇文章我是先从 RecyclerView 的下拉刷新功能实现开始阅读和说明。

源码解析

阅读XRecyclerView源码一

如上图所示,下拉刷新最重要的类是 ArrowRefreshHeader ,图中还有一个 SimpleViewSwitcher 类,它主要就是通过对外暴露的 setView() 方法将 AVLoadingIndicatorView 控件设置进去,如果对 AVLoadingIndicatorView 的动画样式进行改变,设置好后可以重新再调用这个方法进行覆盖之前的设置。

不过在 SimpleViewSwitcher 类中有一点就是我在仿照源码在 onMeasure() 方法里面对子 view 进行测量,然后重新根据子 view 的测量结果设置宽高的时候,发现 AVLoadingIndicatorView 这个控件的 大小总是就那么点大,在布局文件 listview_header 中再怎么改变它的大小都没效果,后来我还是用了系统默认的大小作为容器的宽高。

// 测量子 View 大小,设置 ViewGroup 宽高
// 里面就放了一个 AVLoadingIndicatorView 的子 View
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

// int childCount = this.getChildCount();
// int maxHeight = 0;
// int maxWidth = 0;
// for (int i = 0; i < childCount; i++) {
// View child = this.getChildAt(i);
// this.measureChild(child,widthMeasureSpec,heightMeasureSpec);
//
// maxHeight = child.getMeasuredHeight();
// maxWidth = child.getMeasuredWidth();
// }
setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);
}

上图中 ArrowRefreshHeader 类中只是列出了几个重要的方法。下面会重点说下这几个方法。

在初始化中,注意设置下拉刷新布局的高度为 0

// 初始情况,设置下拉刷新view高度为0
mContainer = (LinearLayout) LayoutInflater.from(getContext()).inflate(
R.layout.listview_header, null);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
lp.setMargins(0, 0, 0, 0);
this.setLayoutParams(lp);
this.setPadding(0, 0, 0, 0);

addView(mContainer, new LayoutParams(LayoutParams.MATCH_PARENT, 0));
setGravity(Gravity.BOTTOM);

初始化 AVLoadingIndicatorView 控件,并加入 SimpleViewSwitcher 容器中

 //init the progress view
mProgressBar = (SimpleViewSwitcher)findViewById(R.id.listview_header_progressbar);
AVLoadingIndicatorView progressView = new AVLoadingIndicatorView(getContext());
progressView.setIndicatorColor(0xffB5B5B5);
progressView.setIndicatorId(ProgressStyle.BallSpinFadeLoader);
mProgressBar.setView(progressView);

setState() 方法

setState(int state) 方法中,根据设置的状态 state 与之前的状态 mState 对比,进行下一步操作。

if (state == mState) return ;

if (state == STATE_REFRESHING) { // 显示进度
mArrowImageView.clearAnimation();
mArrowImageView.setVisibility(View.INVISIBLE);
mProgressBar.setVisibility(View.VISIBLE);
smoothScrollTo(mMeasuredHeight);
} else if(state == STATE_DONE) {
mArrowImageView.setVisibility(View.INVISIBLE);
mProgressBar.setVisibility(View.INVISIBLE);
} else { // 显示箭头图片
mArrowImageView.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.INVISIBLE);
}

switch(state){
case STATE_NORMAL:
if (mState == STATE_RELEASE_TO_REFRESH) {
mArrowImageView.startAnimation(mRotateDownAnim);
}
if (mState == STATE_REFRESHING) {
mArrowImageView.clearAnimation();
}
mStatusTextView.setText(R.string.listview_header_hint_normal);
break;
case STATE_RELEASE_TO_REFRESH:
if (mState != STATE_RELEASE_TO_REFRESH) {
mArrowImageView.clearAnimation();
mArrowImageView.startAnimation(mRotateUpAnim);
mStatusTextView.setText(R.string.listview_header_hint_release);
}
break;
case STATE_REFRESHING:
mStatusTextView.setText(R.string.refreshing);
break;
case STATE_DONE:
mStatusTextView.setText(R.string.refresh_done);
break;
default:
}

mState = state;

onMove() 方法

通过 onMove(float delta) 方法,根据当前显示的下拉刷新 view 显示的高度 getVisibleHeight 和传入的手指滑动的距离 delta (乘以 DRAG_RATE 滑动速率后的值) ,设置下拉刷新 view 的显示高度和显示状态

 @Override
public void onMove(float delta) {
if(getVisibleHeight() > 0 || delta > 0) {
setVisibleHeight((int) delta + getVisibleHeight());
if (mState <= STATE_RELEASE_TO_REFRESH) { // 未处于刷新状态,更新箭头
if (getVisibleHeight() > mMeasuredHeight) {
setState(STATE_RELEASE_TO_REFRESH);
}else {
setState(STATE_NORMAL);
}
}
}
}

releaseAction() 方法

releaseAction() 方法中,同样根据当前显示的下拉刷新 view 显示的高度,设置刷新状态或下拉刷新 view 的慢慢出现或消失的效果

@Override
public boolean releaseAction() {
boolean isOnRefresh = false;
int height = getVisibleHeight();
if (height == 0) // not visible.
isOnRefresh = false;

if(getVisibleHeight() > mMeasuredHeight && mState < STATE_REFRESHING){
setState(STATE_REFRESHING);
isOnRefresh = true;
}
// refreshing and header isn't shown fully. do nothing.
if (mState == STATE_REFRESHING && height <= mMeasuredHeight) {
//return;
}
if (mState != STATE_REFRESHING) {
smoothScrollTo(0); // view 慢慢消失
}

if (mState == STATE_REFRESHING) {
int destHeight = mMeasuredHeight;
smoothScrollTo(destHeight); // view 慢慢回到容器固定的高度
}

return isOnRefresh;
}

这里面“慢慢”动画效果的实现是

private void smoothScrollTo(int destHeight) {
ValueAnimator animator = ValueAnimator.ofInt(getVisibleHeight(), destHeight);
animator.setDuration(300).start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
setVisibleHeight((int) animation.getAnimatedValue());
}
});
animator.start();
}

关键的 XRecyclerView

阅读XRecyclerView源码一

因为 XRecyclerView 支持添加多个自定义 header view,这里使用两个集合来分别存储自定义的 header view 和 对于的 viewType,对外暴露一个添加的方法

 // 对外暴露添加头布局的方法
public void addHeaderView(View view) {
mHeaderTypes.add(HEADER_INIT_INDEX + mHeaderViews.size()); // 从初始位置 10002 开始
mHeaderViews.add(view);
if (mWrapAdapter != null) {
mWrapAdapter.notifyDataSetChanged(); // // 通知观察者 adapter 的数据发生变化
}
}

header view 添加进集合以后,我们需要在 adapter 中进行显示设置。

WrapAdapter

首先我们要把传入的 adapter 进行封装,所以

 // 定义 recyclerview 的 adapter
private RecyclerView.Adapter adapter;

// 构造方法传入的仍然是 recyclerview 的 adpater
public WrapAdapter(Adapter adapter) {
this.adapter = adapter;
}

// 获得封装前的 recyclerview 的 adapter
public RecyclerView.Adapter getOriginalAdapter() {return this.adapter;}

因为这篇文章主要是介绍下拉刷新功能的实现,所以

// 根据 position 判断当前 item 样式
public boolean isHeader(int position) {
return position >= 1 && position < mHeaderViews.size() + 1;
}
public boolean isRefreshHeader(int position) {
return position == 0;
}

// 获取头布局数量
public int getHeadersCount() {return mHeaderViews.size();}

getItemViewType() 方法中

// 不同 position 设置 itemType
@Override
public int getItemViewType(int position) {
int dataPosition = position - (getHeadersCount() + 1);
if (isRefreshHeader(position)) {
return TYPE_REFRESH_HEADER;
}
if (isHeader(position)) {
position = position - 1;
return mHeaderTypes.get(position);
}
int adapterCount;
if (adapter != null) {
adapterCount = adapter.getItemCount();
if(dataPosition < adapterCount) {
int type = adapter.getItemViewType(dataPosition);
if (isReservedItemViewType(type)) {
throw new IllegalStateException("XRecyclerView require itemViewType in adapter should be less than 10000 " );
}
return type;
}
}
return 0;
}

RecyclerView 配有LinerLayoutManagerGridLayoutManagerStaggeredGridLayoutManager,所以还要考虑到 item 的跨度问题

// 判断 LayoutManager 类型,对 item 跨度进行设置
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
final RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
((GridLayoutManager) manager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (isRefreshHeader(position) || isHeader(position)) ?
((GridLayoutManager) manager).getSpanCount() : 1;
}
});
}
adapter.onAttachedToRecyclerView(recyclerView);
}

还重写了一堆方法,主要是把方法的调用者都用 RecyclerView 的 adapter,而不是封装的 mWrapAdapter,而在外面操作的都是 mWrapAdapter。为了这层封装替换的东西挺多啊。

我们对传入的 adapter 进行封装

// 对传入的 adapter 进行封装
@Override
public void setAdapter(Adapter adapter) {
mWrapAdapter = new WrapAdapter(adapter); // 进行封装
super.setAdapter(mWrapAdapter); // 设置封装的 adapter
adapter.registerAdapterDataObserver(mDataObserver); // 注册新的封装的 adapter 的数据观察者,可具体操作用的 mWrapAdapter
mDataObserver.onChanged(); // 通知 adapter 变化
}

// 避免用户自己调用 getAdapter() 获得的是 WrapAdapter,引起 ClassCastException
@Override
public Adapter getAdapter() {
if (mWrapAdapter != null) {
return mWrapAdapter.getOriginalAdapter(); // 返回封装前的 adapter
} else {
return null;
}
}

处理下拉刷新的功能主要是要重写 onTouchEvenet() 方法,处理在手指滑动状态改变时的相关操作。

// 处理下拉刷新
// 涉及到滑动需要重写 onTouchEvenet() 方法,特别是针对 MotionEvent.ACTION_MOVE 处理
@Override
public boolean onTouchEvent(MotionEvent e) {
if (mLastY == -1) { // 初始状态,没被赋值,也就是滑动起始
mLastY = e.getRawY(); // 该方法是获取点击事件相对整个屏幕顶边的 y 轴坐标,即点击事件距离整个屏幕顶边的距离
}
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = e.getRawY(); //
break;
case MotionEvent.ACTION_MOVE:
float deltaY = e.getRawY() - mLastY; // 差值即为手指滑动的距离
mLastY = e.getRawY(); // mLastY 值随着手指的变化动态变化
if (isOnTop()) {
mRefreshHeader.onMove(deltaY / DRAG_RATE); // 下拉刷新头布局随手指上下移动而出现/消失
if (mRefreshHeader.getVisibleHeight() > 0 && // 判断下拉刷新头布局可见高度,状态在正在刷新之前
mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING) {
return false;
}
}
break;
default: // 没有按下,没有滑动,手放开
mLastY = -1; // reset
if (isOnTop()) {
if (mRefreshHeader.releaseAction()) { // 判断下拉刷新头布局可见高度是否到了显示刷新的高度
if (mLoadingListener != null) {
mLoadingListener.onRefresh(); // 调用刷新动作
}
}
}
break;
}
return super.onTouchEvent(e);
}

DataObserver
DataObserver 继承 RecyclerView.AdapterDataObserver,重写 onChanged() 方法,在该内部类里的所有方法的调用都使用封装的 mWrapAdapter 进行调用。

@Override
public void onChanged() {
super.onChanged();
if (mWrapAdapter != null) {
mWrapAdapter.notifyDataSetChanged();
}
}

@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
// super.onItemRangeChanged(positionStart, itemCount);
mWrapAdapter.notifyItemRangeChanged(positionStart,itemCount);
}

@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
// super.onItemRangeChanged(positionStart, itemCount, payload);
mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount, payload);
}

@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
// super.onItemRangeInserted(positionStart, itemCount);
mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount);
}

@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
// super.onItemRangeRemoved(positionStart, itemCount);
mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount);
}

@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
// super.onItemRangeMoved(fromPosition, toPosition, itemCount);
mWrapAdapter.notifyItemMoved(fromPosition, toPosition);
}

总结

通过学习 XRecyclerView ,我们知道 它的下拉刷新通过配合 AVLoadingIndicatorView ,自定义 ViewGroup,处理下拉刷新布局不同状态的具体变化。在自定义的 XRecyclerView 中通过两个集合存储要添加的头布局和对应的 itemType,对传入的 adapter 中进行二次封装,在封装的 WrapAdapter 中重写 onAttachedToRecyclerView() 针对 RecyclerView 的 GridLayoutManager 进行 item 跨度的设置,防止下拉刷新布局和头布局出现变形。调用 onTouchEvenet() 方法获得手指滑动的距离,设置 ArrowRefreshHeader 的显示和状态设置。

Github例子源码