写在前面
最近项目里有需求要用到下拉刷新,以前写好的代码最好别动太多~在github上发现了一个XRecyclerView能满足的需求,而且也不必对我原来的代码做多大的修改。
但是光会用还不够,万一碰到啥满足不了自己需求或者是坑自己还得有办法解决啊。所以打算通过自己写一个,以此来加深自己对XRecyclerView的理解。
当然了,我只是抱着学习的目的实现一下,重复造轮子是不好的~这篇文主要是理一下自己的思路的,代码不会贴非常完整的,完整代码地址文末有。最后自己也稍微完善了一下代码,权当是练习一下自己的所学所得吧。
简单的实现部分逻辑
首先想一下我最初的需求,希望尽量少的改动自己的代码,XRecyclerView做到了这一点,他内部是有一个WrapAdapter继承于RecyclerView.Adapter。并且XRecyclerView重写了setAdapter这个方法,当你调用setAdapter方法时,会先在WrapAdapter内部持有一个adapter的引用,之后调用RecyclerView的setAdapter将这个WrapAdapter设置给RecyclerView,这么做有一个好处,就是可以自己先在onCreateViewHolder和onBindViewHolder方法里对刷新顶部和你添加的头部布局进行处理和绑定,在这之后再调用adapter的相应方法。
如果你对于RecyclerView的工作机制还不是很熟悉,可能会疑惑我以上的这段话。可以先简单的理解为:onCreateViewHolder是生成整个列表中的一个个元素的,而onBindViewHolder则是在这个元素被滑动到可见(进入屏幕)的时候被调用,你可以在这个方法进行具体的元素视图的设置操作。如果这么说你还不明白,我的这篇探究RecyclerView的ViewHolder复用可能会对你有所帮助。
既然知道了这么写的好处,那么我自然也得弄一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| private class AdapterWrapper extends Adapter {
private Adapter mAdapter;
AdapterWrapper(Adapter adapter) { this.mAdapter = adapter; }
@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return null; }
@Override public void onBindViewHolder(ViewHolder holder, int position) {
}
@Override public int getItemCount() { return 100; }
@Override public int getItemViewType(int position) {
} }
|
接下来当然是看看这玩意能不能用了,写一个RecyclerVIew的子类,就叫做MRecyclerView吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| package com.xiasuhuei321.test;
import android.content.Context; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup;
public class MRecyclerView extends RecyclerView { private Context mContext;
public MRecyclerView(Context context) { this(context, null); }
public MRecyclerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); }
public MRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setAdapter(new AdapterWrapper(null)); this.mContext = context; }
private class AdapterWrapper extends Adapter {
private Adapter mAdapter;
AdapterWrapper(Adapter adapter) { this.mAdapter = adapter; }
@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new HeadViewHolder(LayoutInflater.from(mContext) .inflate(R.layout.item_test, parent, false)); }
@Override public void onBindViewHolder(ViewHolder holder, int position) {
}
@Override public int getItemCount() { return 100; }
@Override public int getItemViewType(int position) { return super.getItemViewType(position); } }
class HeadViewHolder extends ViewHolder {
public HeadViewHolder(View itemView) { super(itemView); } } }
MRecyclerView mr = (MRecyclerView) findViewById(R.id.mrv); mr.setLayoutManager(new LinearLayoutManager(this));
|
这里暂时只是简单的实践,所以没有更进一步的实现,先看一下效果是怎样的:
还行,感觉思路应该是对的。接着尝试加上一个刷新的头布局。这个可以通过设置ViewType和不同的ViewHolder来解决,首先复写AdapterWrap内的getItemViewType方法,这里因为暂时不考虑外部adapter的设置,所以代码比较简单:
1 2 3 4 5 6
| private static final int REFRESH_HEADER = 10086;
@Override public int getItemViewType(int position) { return position == 0 ? REFRESH_HEADER : 0; }
|
接着将onCreateViewHolder的逻辑改改:
1 2 3 4 5 6 7 8 9 10
| @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == REFRESH_HEADER) { return new HeadViewHolder(LayoutInflater.from(mContext) .inflate(R.layout.item_refresh_header, parent, false)); } else { return new HeadViewHolder(LayoutInflater.from(mContext) .inflate(R.layout.item_test, parent, false)); } }
|
看下啥样子
恩,不错,有点样子了,不过这里只是添加了一个item进去而已,并没有平时常见的下拉刷新的效果。关于如何实现这种效果,我刚开始以为是将这个refresh head放到屏幕之外,以前也尝试过ListView下拉刷新,不过忘得差不多了就是……看了一下XRecyclerView的实现方式,很巧妙,是将refresh head的初始高度设置为0,随着下拉高度而逐渐展现refrsh head。这个好啊~我果断也用这种方式实现了。
不过还是说明下,现在只是简单的实现,并非完善的代码,各位通过这个代码简单的了解如何实现就好了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
| package com.xiasuhuei321.test;
import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.os.Handler; import android.os.Message; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView;
public class MRecyclerView extends RecyclerView { private Context mContext; private static final int REFRESH_HEADER = 10086;
private float mLastY = -1; private int mMeasuredHeight = -1; private HeadViewHolder h; private boolean releaseToRefresh = false; private RefreshListener r; private AnimationDrawable drawable;
public MRecyclerView(Context context) { this(context, null); }
public MRecyclerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); }
public MRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setAdapter(new AdapterWrapper(null)); this.mContext = context;
}
private class AdapterWrapper extends Adapter {
private Adapter mAdapter;
AdapterWrapper(Adapter adapter) { this.mAdapter = adapter; }
@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == REFRESH_HEADER) { h = new HeadViewHolder(LayoutInflater.from(mContext) .inflate(R.layout.item_refresh_header, parent, false)); LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); h.itemView.setLayoutParams(lp); return h; } else { return new HeadViewHolder(LayoutInflater.from(mContext) .inflate(R.layout.item_test, parent, false)); } }
@Override public void onBindViewHolder(ViewHolder holder, int position) {
}
@Override public int getItemCount() { return 100; }
@Override public int getItemViewType(int position) { return position == 0 ? REFRESH_HEADER : 0; }
}
@Override public boolean onTouchEvent(MotionEvent e) { if (mLastY == -1) { mLastY = e.getRawY(); } if (mMeasuredHeight == -1) { mMeasuredHeight = h.getMeasureHeight(); } switch (e.getAction()) { case MotionEvent.ACTION_DOWN:
break; case MotionEvent.ACTION_MOVE: float deltaY = e.getRawY() - mLastY; mLastY = e.getRawY(); onMove(deltaY / 3); break; default: mLastY = -1; if (releaseToRefresh) refresh(); else refreshComplete(); break; } return super.onTouchEvent(e); }
class HeadViewHolder extends ViewHolder {
public HeadViewHolder(View itemView) { super(itemView);
}
public int getMeasureHeight() { itemView.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); return itemView.getMeasuredHeight(); }
public void setVisibleHeight(int height) { if (height < 0) height = 0; LayoutParams lp = (LayoutParams) itemView.getLayoutParams(); lp.height = height; itemView.setLayoutParams(lp);
}
public int getVisibleHeight() { LayoutParams lp = (LayoutParams) itemView.getLayoutParams(); return lp.height; }
public ImageView getArrowImg() { return (ImageView) itemView.findViewById(R.id.iv_progress); }
public ImageView getRefreshImg() { return (ImageView) itemView.findViewById(R.id.iv_refresh); }
public void changeText(String text) { TextView refreshTitle = (TextView) itemView.findViewById(R.id.tv_refresh_text); refreshTitle.setText(text); } }
public void refreshComplete() { if (releaseToRefresh) { ImageView arrowImg = h.getArrowImg(); ObjectAnimator rotate = ObjectAnimator.ofFloat(arrowImg, "rotation", 180f, 0f); rotate.setDuration(300); rotate.start(); } releaseToRefresh = false; handler.sendEmptyMessageDelayed(1, 800); }
Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: h.setVisibleHeight(0); h.changeText("下拉刷新"); break; case 2: if (drawable != null) drawable.stop(); ImageView refreshImg = h.getRefreshImg(); ImageView arrowImg = h.getArrowImg(); arrowImg.setVisibility(VISIBLE); refreshImg.setVisibility(GONE); h.changeText("刷新完成"); refreshComplete(); break; default:
break; } } };
public void onMove(float deltaY) { h.setVisibleHeight((int) deltaY + h.getVisibleHeight()); if (h.getVisibleHeight() > mMeasuredHeight) { if (!releaseToRefresh) { ImageView arrowImg = h.getArrowImg(); ObjectAnimator rotate = ObjectAnimator.ofFloat(arrowImg, "rotation", 0f, 180f); h.changeText("释放刷新"); rotate.setDuration(300); rotate.start(); releaseToRefresh = true; } } }
private void refresh() { ImageView refreshImg = h.getRefreshImg(); ImageView arrowImg = h.getArrowImg(); arrowImg.setVisibility(GONE); refreshImg.setVisibility(VISIBLE); drawable = (AnimationDrawable) refreshImg.getDrawable(); drawable.start(); h.changeText("刷新中..."); handler.sendEmptyMessageDelayed(2,3000); }
public void setPullToRefreshListener(RefreshListener r) { this.r = r; }
public interface RefreshListener { void refresh(); }
}
|
看看效果图:
后记
还有很多的细节没有完善和处理,比如适配不同的layoutmanage之类的,操作刷新布局也都是通过ViewHolder来的,其实是有必要抽出一个类的,真的还有非常多不完善的地方,不过只是简单实现和了解一下下拉刷新而已。我自己也简单的封装了一个MRecyclerView,感兴趣的可以看看。
https://github.com/ForgetAll/MRecyclerView
当然了,由于并非是想要做一个完善的三方,所以只是简单的实现,很多情况并没有考虑到其中。