MaoRecyclerView
Vimous · 2016-06-21 · Android
Android RecyclerView已经出来很久了, 官方推荐来替换原有的ListView.
现在项目中的ListView使用的是第三方框架PullToRefresh来实现View的下拉刷新和加载更多. 虽然MaterialDesign有自己的一套刷新设计, 但是产品还是要求UI使用仿照IOS的下拉刷新T^T. So, 想用RecyclerView, 就只好自己实现下拉刷新和上拉加载了.
下拉刷新
1. 下拉刷新View的几种状态:
- IDLE(空闲)
- PULL(下拉)
- PULL_TO_REFRESH(可刷新)
- REFRESHING(刷新中)
- COMPLETED(刷新完成)
2. View在拉取的时候弹性处理
采用的方法是 根据RecyclerView传过来的滑动坐标差值来针对View高度做属性动画. 抽象关键代码如下:
public abstract class MaoRefreshView extends LinearLayout {
private static final int COMPLETE_TIME = 1000; //完成等待时间
private int mHeight;
private enum State {IDLE, PULL, PULL_TO_REFRESH, REFRESHING, COMPLETED}
private State mState = State.IDLE; //初始化状态
public MaoRefreshView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//在要实现的View外层包装一层View 下拉时可向下拉动
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(onCreateView(),
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
setGravity(Gravity.BOTTOM);
measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
mHeight = getMeasuredHeight();
setVisibleHeight(0);
}
private void setState(State state){ //状态管理
if(state == mState) return;
mState = state;
switch (state){
case IDLE: smoothScrollTo(0); break;
case PULL: onPull(); break;
case PULL_TO_REFRESH: onPullToRefresh(); break;
case REFRESHING: onRefresh(); break;
case COMPLETED: onCompleted(); finishComplete(); break;
}
}
private void setVisibleHeight(int height) {
if (height < 0) height = 0;
ViewGroup.LayoutParams lp = getLayoutParams();
lp.height = height;
setLayoutParams(lp);
}
private int getVisibleHeight() {
return getLayoutParams().height;
}
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();
}
protected abstract View onCreateView();
protected abstract void onPull();
protected abstract void onPullToRefresh();
protected abstract void onRefresh();
protected abstract void onCompleted();
private void finishComplete(){
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
setState(State.IDLE);
}
}, COMPLETE_TIME);
}
/**
* 下拉
* @param offsetY //下拉Y轴偏移
* @return true//如果正常消费了下拉事件
* */
public boolean pull(float offsetY){
if(offsetY > 0){
if(!((mState == State.REFRESHING || mState == State.COMPLETED) && offsetY < mHeight))
setVisibleHeight((int) offsetY);
if(mState == State.IDLE) {
setState(State.PULL);
return true;
}
if(mState == State.PULL && getVisibleHeight() >= mHeight){
setState(State.PULL_TO_REFRESH);
return true;
}
if(mState == State.PULL_TO_REFRESH && getVisibleHeight() < mHeight){
setState(State.PULL);
}
return true;
}
return false;
}
/**
* 释放
* @return true//如果释放后刷新
* */
public boolean release(){
if(mState == State.PULL_TO_REFRESH){
setState(State.REFRESHING);
smoothScrollTo(mHeight);
return true;
}
if(mState == State.PULL)
setState(State.IDLE);
else
smoothScrollTo(mHeight);
return false;
}
/**
* 刷新完成
* */
public boolean complete(){
if(mState == State.REFRESHING ||
mState == State.COMPLETED) {
setState(State.COMPLETED);
return true;
}
return false;
}
}
加载更多
加载更多需要实现三种状态: + 空闲 + 加载中 + 没有更多了
相关代码逻辑和下拉刷新的View逻辑是类似的, 不在重复代码
事件传递
接下来是处理Recycler中的事件传递, 主要处理在顶部下拉事件时触发下拉刷新和最后n个Item可见时进行加载更多.
只需要重写Recyclerview的onTouchEvent和onScrollStateChanged方法就好了
在这里有一点一定要注意, 不能仅仅在MotionEvent.ACTION_DOWN中取得坐标来计算差值, 在实际中ItemView可能会消费掉事件, 导致坐标值取不到出现错误, 所以要在TouchEvent中直接去坐标差值.
关键代码如下:
private float mDownY = -1;
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(mDownY == -1) mDownY = ev.getRawY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
final float offsetY = ev.getRawY() - mDownY;
if (isOnTop() && isRefreshEnable) {
if(mHeaderView.pull(offsetY / RATE))
return true;
}
if(isOnBottom() && isLoadMoreEnable && isNoMore){
mFooterView.setHeight(-(int)offsetY/RATE);
}
break;
default:
mDownY = -1;
if (isOnTop() && isRefreshEnable && mHeaderView.release() &&
mRecyclerListener != null) {
mRecyclerListener.onRefresh();
isRefreshing = true;
}
if(isOnBottom() && isLoadMoreEnable && isNoMore){
mFooterView.hide();
}
break;
}
return super.onTouchEvent(ev);
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if(state != RecyclerView.SCROLL_STATE_IDLE || mRecyclerListener == null ||
!isLoadMoreEnable || isLoadMoring || isNoMore) return;
if(isRefreshing) {
mFooterView.setGone();
return;
}
mFooterView.setVisiable();
LayoutManager layoutManager = getLayoutManager();
if(!(layoutManager instanceof LinearLayoutManager)) return;
int last = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
if (layoutManager.getChildCount() > 0 &&
layoutManager.getItemCount() > layoutManager.getChildCount() &&
last >= layoutManager.getItemCount() - 1){
mFooterView.loadMore();
mRecyclerListener.onLoadMore();
isLoadMoring = true;
}
}
AdapterWrapper
Recycler中没有HeaderView和FooterView 我们只能通过增加ItemView的Type来实现增加Header和Footer 抽象出一个Adapter来实现该功能:
private class WrapAdapter extends RecyclerView.Adapter<ViewHolder> {
private RecyclerView.Adapter<ViewHolder> mAdapter;
@SuppressWarnings("unchecked")
public WrapAdapter(RecyclerView.Adapter adapter) {
mAdapter = adapter;
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType < TYPE_BASE){
if(viewType >= TYPE_FOOTER){
return new SimpleViewHolder(mFooterViews.get(viewType - TYPE_FOOTER));
}
if(viewType >= TYPE_HEADER){
return new SimpleViewHolder(mHeaderViews.get(viewType - TYPE_HEADER));
}
throw new IllegalStateException();
}
return mAdapter.onCreateViewHolder(parent, viewType - TYPE_BASE);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(mAdapter != null && getItemViewType(position) >= TYPE_BASE)
mAdapter.onBindViewHolder(holder, position - getHeadersCount());
}
@Override
public int getItemCount() {
return getHeadersCount() + getFootersCount() +
(mAdapter != null ? mAdapter.getItemCount() : 0);
}
public int getHeadersCount() {
return mHeaderViews.size();
}
public int getFootersCount() {
return mFooterViews.size();
}
@Override
public int getItemViewType(int position) {
if(position < 0 || position >= getItemCount()) return -1;
if(position < getHeadersCount()){
return TYPE_HEADER + position;
}
if(position >= getItemCount() - getFootersCount()){
return TYPE_FOOTER + getItemCount() - position - 1;
}
return mAdapter == null ? -1 :
(TYPE_BASE + mAdapter.getItemViewType(position - getHeadersCount()));
}
@Override
public long getItemId(int position) {
if(mAdapter == null || position < getHeadersCount() ||
position >= mAdapter.getItemCount() + getHeadersCount()) return -1;
return mAdapter.getItemId(position - getHeadersCount());
}
@Override
public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(observer);
}
}
@Override
public void registerAdapterDataObserver(AdapterDataObserver observer) {
if (mAdapter != null) {
mAdapter.registerAdapterDataObserver(observer);
}
}
private class SimpleViewHolder extends RecyclerView.ViewHolder {
public SimpleViewHolder(View itemView) {
super(itemView);
}
}
}
分割线
RecyclerView中是没有ListView的分割线的, 我们要实现分割线效果就要自己实现Decoration
public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int space;
public SpacesItemDecoration(int space) {
this.space = space;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// Add top margin only for the first item to avoid double space between items
if(parent.getChildLayoutPosition(view) > 1 ||
parent.getChildLayoutPosition(view) < parent.getChildCount())
outRect.top = space;
}
}
运行截图
