ListView已经用了很多年了,后来又有了RecyclerView,基本可以代替ListView/GridView了,还有瀑布流的模式,加上各种特效,于是就尝试用RecyclerView替代listview。
如果UI要求不严格,那么有一个很简单的方式实现下拉刷新:SwipeRefreshLayout (具体介绍参考:http://blog.csdn.net/dalancon/article/details/46125667),然后基本你就可以开开心心做完了。
但是如果你们的UI或者产品说,我们要一个什么什么样的功能(反正就是不造轮子不行的需求),那只能自己做了。
先看一下效果:
简单说一下思路:
下拉刷新:
1.自定义一个线性布局linearlayout,里面先添加一个header,然后添加recycclerview。
2.计算出header的高度h1,然后设置linearlayout布局的的padding为-h1(也可以设置margin为-h,都可以实现,本文采用的是padding)
3.利用消息传递机制及对recyclerview的滚动监听来判断是否可以下拉刷新。
4.根据用户的手势移动来设置padding值,再加上一些回滚效果就ok了。
加载更多:
1.首先写一个BaseAdapter抽象类继承
import android.support.v7.widget.RecyclerView.Adapter;
2.在BaseAdapter中添加一个footerViewHolder,并设置开关,如果有加载更多的时候,就显示。如果没有,则不显示。
3.监听RecyclerView的滚动,如果滚动到末尾并且有加载更多,则调用加载更多的方法。
有了上面的思路写起来就简单多了。
接下来分析代码:
下拉刷新:
public class RefreshRecyclerView extends LinearLayout{
public static final long ONE_MINUTE = 60 * 1000;//一分钟的毫秒值,用于判断上次的更新时间
public static final long ONE_HOUR = 60 * ONE_MINUTE;//一小时的毫秒值,用于判断上次的更新时间
public static final long ONE_DAY = 24 * ONE_HOUR;//一天的毫秒值,用于判断上次的更新时间
public static final long ONE_MONTH = 30 * ONE_DAY;//一月的毫秒值,用于判断上次的更新时间
public static final long ONE_YEAR = 12 * ONE_MONTH;//一年的毫秒值,用于判断上次的更新时间
private String MYRECYCLERVIEW = "MyRecyclerView";//保存用的
private int id;//用来区分不同页面
private LinearLayout headerView;//刷新头部布局
public static final int SCROLL_SPEED = -10;//下拉头部回滚的速度
private int topPadding;//距离顶部的padding值
private RecyclerView mRecyclerView;//列表控件
private LinearLayoutManager mLayoutManager;//RecyclerView布局管理器
private int firstVisibleItemPostion;//第一个可见item的position
private int mHeaderHeight;//头部高度
private boolean hasMore;//设置是否有加载更多(用户设置)
private boolean hasRefresh;//设置是否有下拉刷新(用户设置)
private boolean canRefresh;//是否可以下拉刷新(逻辑判断)
private boolean isLoading;//列表是否正在刷新或加载更多中
private boolean canScroll;//列表是否可以滚动(刷新加载中禁止用户进行列表操作)
private LoadLinsteners loadLinsteners;//加载监听器
private ScrollLinsteners scrollLinsteners;//滚动监听器
private BaseAdapter mAdapter;//Item适配器
//头部相关
private TextView description, updated_at;//描述
private Long lastUpdateTime;//上次更新时间
private ImageView arrow;//箭头
private ProgressBar progress_bar;
private ImageView iv_head;//头部图片
private Context context;
private int readCount;//判断当前列表最大滚动位置
public RefreshRecyclerView(Context context) {
this(context, null);
}
public RefreshRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
initContents();
}
/**
* 初始化
*/
private void initContents() {
//默认可下拉刷新,无加载更多
canRefresh = true;
hasMore = false;
hasRefresh = true;
isLoading = false;
canScroll = true;
//头部
headerView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.item_recyler_header, null);
iv_head = (ImageView)headerView.findViewById(R.id.iv_head);
description = (TextView)headerView.findViewById(R.id.description);
updated_at = (TextView)headerView.findViewById(R.id.updated_at);
arrow = (ImageView)headerView.findViewById(R.id.arrow);
progress_bar = (ProgressBar)headerView.findViewById(R.id.progress_bar);
//测量并获取头部高度
measureView(headerView);
mHeaderHeight = headerView.getMeasuredHeight();
//列表
mRecyclerView = new RecyclerView(context);
mRecyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
//创建默认的线性LayoutManager
mLayoutManager = new LinearLayoutManager(getContext());
mRecyclerView.setLayoutManager(mLayoutManager);
//添加布局
setOrientation(VERTICAL);
addView(headerView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
addView(mRecyclerView, new LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
//隐藏头部
setTopPadding(-mHeaderHeight);
setLinsteners();
/**
* 添加旋转动画
*/
Animation animation= AnimationUtils.loadAnimation(context, R.anim.anim_rotate_earth);
LinearInterpolator lin = new LinearInterpolator();
animation.setInterpolator(lin);
iv_head.startAnimation(animation);
}
/**
* 设置布局管理器
* @param layoutManager
*/
public void setmLayoutManager(LinearLayoutManager layoutManager){
this.mLayoutManager = layoutManager;
mRecyclerView.setLayoutManager(mLayoutManager);
}
/**
* 获取布局管理器
* @return
*/
public RecyclerView.LayoutManager getmLayoutManager(){
return mLayoutManager;
}
/**
* 设置adapter
* @param mAdapter
*/
public void setAdapter(BaseAdapter mAdapter){
this.mAdapter = mAdapter;
mRecyclerView.setAdapter(mAdapter);
}
/**
* 获取recyclerview
* @return
*/
public RecyclerView getmRecyclerView(){
return mRecyclerView;
}
/**
* 加载监听器
*/
public interface LoadLinsteners{
void onLoadMore();
void onRefresh();
}
/**
* 设置监听器
* @param loadLinsteners
* @param id
*/
public void setLoadLinsteners(LoadLinsteners loadLinsteners, int id){
this.loadLinsteners = loadLinsteners;
this.id = id;
refreshUpdatedAtValue();
}
/**
* 设置监听器
* @param loadLinsteners
*/
public void setLoadLinsteners(LoadLinsteners loadLinsteners){
this.loadLinsteners = loadLinsteners;
this.id = -1;
refreshUpdatedAtValue();
}
/**
* 滚动监听接口
*/
public interface ScrollLinsteners{
void onScrolled(int firstVisibleItem, int dx, int dy);
}
/**
* 设置滚动监听
* @param scrollLinsteners
*/
public void setScrollLinsteners(ScrollLinsteners scrollLinsteners){
this.scrollLinsteners = scrollLinsteners;
}
/**
* 获取最大滚动位置
* @return
*/
public int getReadCount(){
return readCount;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(!canScroll) {//禁止事件传递
return true;
}else {
return false;
}
}
private float moveY, startY = -1, dY;
private void setLinsteners() {
/**
* 添加触摸监听,实现下拉效果
*/
mRecyclerView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (canRefresh && firstVisibleItemPostion == 0 && !isLoading && hasRefresh) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = -1;
Log.i("move---", "down:-1");
break;
case MotionEvent.ACTION_MOVE:
moveY = event.getRawY();
Log.i("move----1", "startY:"+ startY + " moveY:" + moveY + " dY:" + dY + " headerHeight:" + mHeaderHeight);
if(startY == -1) {
startY = moveY;
}
dY = moveY - startY;
Log.i("move----2", "startY:"+ startY + " moveY:" + moveY + " dY:" + dY + " headerHeight:" + mHeaderHeight);
if (dY > 30) {
dY = dY - 30;
int maxLength = mHeaderHeight * 6;
if (dY < maxLength && dY >= 30) {
description.setText(R.string.pull_to_refresh);
} else if (dY >= maxLength) {
description.setText(R.string.release_to_refresh);
dY = maxLength;
}
if(dY > mHeaderHeight){
dY = (dY + mHeaderHeight)/2;
}
setTopPadding((int)(dY - mHeaderHeight));
} else {
Log.i("move---", "reset");
setTopPadding(- mHeaderHeight);
return false;
}
break;
case MotionEvent.ACTION_UP:
Log.i("move---", "up:-1");
if (dY > mHeaderHeight / 2) {
isLoading = true;
canScroll = false;
canRefresh = false;
description.setText(R.string.refreshing);
arrow.setVisibility(View.GONE);
progress_bar.setVisibility(View.VISIBLE);
new RefreshingTask().execute();
if(loadLinsteners != null) {
loadLinsteners.onRefresh();
}
} else {
startY = -1;
new HideHeaderTask().execute();
return false;
}
dY = 0;
startY = -1;
moveY = 0;
break;
}
return true;
}
return false;
}
});
/**
* 添加滚动监听,判断加载更多
*/
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
firstVisibleItemPostion = mLayoutManager.findFirstVisibleItemPosition();
int lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
int totalItemCount = mLayoutManager.getItemCount();
if (readCount <= lastVisibleItem) {
readCount = lastVisibleItem;
}
//lastVisibleItem >= totalItemCount - 4 表示剩下4个item自动加载
// dy > 0 表示向下滑动
if (lastVisibleItem >= totalItemCount - 4 && dy >= 0) {
if (hasMore && !isLoading) {//可以下拉刷新并且没有加载中
isLoading = true;
canScroll = true;
if (loadLinsteners != null) {
loadLinsteners.onLoadMore();
}
}
}
//如果列表在头部
if (firstVisibleItemPostion == 0 && mLayoutManager.getChildAt(0).getTop() == 0 && hasRefresh) {
Log.i("move---", "recyclerview:reset:-1");
canRefresh = true;
startY = -1;
} else {
canRefresh = false;
}
if (scrollLinsteners != null) {
scrollLinsteners.onScrolled(firstVisibleItemPostion, dx, dy);
}
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});
}
/**
* 设置顶部padding值
*/
public void setTopPadding(int top){
topPadding = top;
setPadding(0, top, 0, 0);
}
/**
* 停止下拉刷新
*/
public void onStopRefresh(){
new HideHeaderTask().execute();
canRefresh = true;
isLoading = false;
canScroll = true;
arrow.setVisibility(View.GONE);
progress_bar.setVisibility(View.GONE);
saveTv_refresh_time();
}
/**
* 停止加载更多
*/
public void onStopMore(){
isLoading = false;
}
/**
* 保存刷新时间
*/
public void saveTv_refresh_time(){
if(id == -1)return;
saveSPLongData(MYRECYCLERVIEW + id, System.currentTimeMillis());
refreshUpdatedAtValue();
}
public void setId(int id){
this.id = id;
}
/**
* 是否可以加载更多
* @param hasMore
*/
public void setHasMore(boolean hasMore){
this.hasMore = hasMore;
if(mAdapter != null) {
mAdapter.setHasMore(hasMore);
}
}
/**
* 是否可以下拉刷新
* @param hasRefresh
*/
public void setHasRefresh(boolean hasRefresh){
this.hasRefresh = hasRefresh;
}
/**
* 测量布局
*/
private void measureView(View child) {
ViewGroup.LayoutParams params = child.getLayoutParams();
if (params == null) {
params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, params.width);
int lpHeight = params.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
/**
* 刷新下拉头中上次更新时间的文字描述。
*/
private void refreshUpdatedAtValue() {
if(id == -1)return;
lastUpdateTime = getSPLongData(MYRECYCLERVIEW + id);
long currentTime = System.currentTimeMillis();
long timePassed = currentTime - lastUpdateTime;
long timeIntoFormat;
String updateAtValue;
if (lastUpdateTime == -1) {
updateAtValue = getResources().getString(R.string.not_updated_yet);
} else if (timePassed < 0) {
updateAtValue = getResources().getString(R.string.time_error);
} else if (timePassed < ONE_MINUTE) {
updateAtValue = getResources().getString(R.string.updated_just_now);
} else if (timePassed < ONE_HOUR) {
timeIntoFormat = timePassed / ONE_MINUTE;
String value = timeIntoFormat + "分钟";
updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
} else if (timePassed < ONE_DAY) {
timeIntoFormat = timePassed / ONE_HOUR;
String value = timeIntoFormat + "小时";
updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
} else if (timePassed < ONE_MONTH) {
timeIntoFormat = timePassed / ONE_DAY;
String value = timeIntoFormat + "天";
updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
} else if (timePassed < ONE_YEAR) {
timeIntoFormat = timePassed / ONE_MONTH;
String value = timeIntoFormat + "个月";
updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
} else {
timeIntoFormat = timePassed / ONE_YEAR;
String value = timeIntoFormat + "年";
updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
}
updated_at.setText(updateAtValue);
}
/**
* 正在刷新的任务,在此任务中会去回调注册进来的下拉刷新监听器。
*/
class RefreshingTask extends AsyncTask {
@Override
protected Integer doInBackground(Void... params) {
int tp = topPadding;
long curMills = System.currentTimeMillis();
while (true) {
long nowMills = System.currentTimeMillis();
if(nowMills - curMills >= 10){
curMills = nowMills;
tp = tp + SCROLL_SPEED * 2;
if (tp <= 0) {
tp = 0;
break;
}
publishProgress(tp);
}
}
return tp;
}
@Override
protected void onProgressUpdate(Integer... tp) {
setTopPadding(tp[0]);
}
@Override
protected void onPostExecute(Integer tp) {
setTopPadding(tp);
}
}
/**
* 隐藏下拉头的任务,当未进行下拉刷新或下拉刷新完成后,此任务将会使下拉头重新隐藏。
*/
class HideHeaderTask extends AsyncTask {
@Override
protected Integer doInBackground(Void... params) {
int tp = topPadding;
long curMills = System.currentTimeMillis();
while (true) {
long nowMills = System.currentTimeMillis();
if(nowMills - curMills >= 10) {
curMills = nowMills;
tp = tp + SCROLL_SPEED;
if (tp <= -mHeaderHeight) {
tp = -mHeaderHeight;
break;
}
publishProgress(tp);
}
}
return tp;
}
@Override
protected void onProgressUpdate(Integer... tp) {
setTopPadding(tp[0]);
}
@Override
protected void onPostExecute(Integer tp) {
setTopPadding(tp);
}
}
/**
* 获取账户管理sp
* @return
*/
private SharedPreferences getAccountSP() {
return context.getSharedPreferences("account", Context.MODE_PRIVATE);
}
/**
* 保存Long值
* @param key
* @param value
*/
public void saveSPLongData(String key, long value) {
getAccountSP().edit().putLong(key, value).commit();
}
/**
* 获取存储的Long值
* @param key
* @return
*/
public Long getSPLongData(String key) {
return getAccountSP().getLong(key, -1);
}
}
(已经很详细的注释了,就不多说了,这里有一个注意点就是:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:design:23.2.0'
}
红色区域是需要注意的,如果你用的是23.3.0会有问题。。。原因未知。。。其它版本未测试)
加载更多:
public abstract class BaseAdapter extends Adapter {
protected final LayoutInflater layoutInflater;
public List mMessages;
private static final int TYPE_FOOTER = -2;
public boolean hasMore;
public Context context;
private int mResources;
private ItemOnclickLinstener itemOnclickLinstener;
public BaseAdapter(Context context, List mMessages, int mResources) {
this.mMessages = mMessages;
this.context = context;
this.mResources = mResources;
layoutInflater = LayoutInflater.from(context);
hasMore = false;
}
@Override
public K onCreateViewHolder(ViewGroup parent, int viewType) {
// type == TYPE_FOOTER 返回footerView
if (viewType == TYPE_FOOTER) {
View view = layoutInflater.inflate(R.layout.item_footer, null);
view.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
return (K)new FooterViewHolder(view);
}else {
final K vh = createViewHolder(viewType, parent);
vh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(itemOnclickLinstener != null) {
itemOnclickLinstener.setItemOnclickLinsteners(v, vh.getLayoutPosition(), vh.getItemViewType());
}
}
});
return vh;
}
}
@Override
public void onBindViewHolder(final K viewHolder, final int position) {
if(!(hasMore && position == getItemCount() -1)){
setOnBindViewHolder(viewHolder, position);
}
}
@Override
public int getItemCount() {
int size = 0;
if(mMessages != null){
size = mMessages.size();
}
return hasMore ? size + 1 : size;
}
@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount() && hasMore) {
return TYPE_FOOTER;
} else {
return setItemViewType(position);
}
}
/**
* footer ViewHolder
*/
class FooterViewHolder extends ViewHolder {
public FooterViewHolder(View view) {
super(view);
}
}
/**
* 是否显示加载更多
* @param hasMore
*/
public void setHasMore(boolean hasMore){
this.hasMore = hasMore;
}
/**
* 创建ViewHolder
* @param viewType
* @return
*/
protected abstract K createViewHolder(int viewType, ViewGroup parent);
/**
* 设置ViewHolder类型
* @param position
* @return
*/
protected abstract int setItemViewType(int position);
/**
* 数据填充
*/
protected abstract void setOnBindViewHolder(K viewHolder, int position);
/**
* Item点击事件
* @param
* @return
*/
public void setItemOnClickLinsteners(ItemOnclickLinstener itemOnclickLinstener){
this.itemOnclickLinstener = itemOnclickLinstener;
}
/**
* item点击接口
*/
public interface ItemOnclickLinstener{
void setItemOnclickLinsteners(View v, int postion, int type);
}
protected View getView(int resource){
View view = layoutInflater.inflate(resource, null);
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
return view;
}
protected View getView(){
View view = layoutInflater.inflate(mResources, null);
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
return view;
}
protected View getView(ViewGroup parent){
View view = layoutInflater.inflate(mResources, parent, false);
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
return view;
}
}
RecyclerView卡顿优化(1)主要介绍了基于此进行的进一步优化,源码中也已经更新了最新的优化功能。
源码地址:RefreshRecyclerView源码