前言
自从RecyclerVIew
出来后就尝试着用它,效果真的是比ListView
好多了。
但是发现了一个问题,由于RecyclerView
的Adapter
必须继承自RecyclerView.Adapter
,并且指定我们写的ViewHolder
为泛型,为了达到万能的效果,我们把需要传入的Java Bean
属性直接用一个泛型T
指代,我们都在不停的重复写这几个方法①,为何不像之前ListView
一样,封装一下呢?
- getItemViewType(int position)
- getItemCount()
- onCreateViewHolder(ViewGroup parent, int viewType)
- onBindViewHolder(RecyclerView.ViewHolder holder, int position)
- 以及定义一个单击事件的接口 OnItemClickListener
那么还有一个问题,为了给用户一个友好的提示,加载更多该怎么做呢。下面带你来封装一下RecyclerView.Adapter
,先看看效果图:
使用方法
先看一下封装后的 BaseRecyclerViewAdapter 代码
BaseRecyclerViewAdapter
public abstract class BaseRecyclerViewAdapter<T>
extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static final int TYPE_ITEM = 0;
public static final int TYPE_FOOTER = 1;
private int mItemLayoutRes = -1;
protected Context mContext;
protected List<T> mList = new ArrayList<>();
protected OnItemClickListener<T> mOnItemClickListener;
protected OnReloadClickListener mOnReloadClickListener;
private FooterViewHolder mFooterViewHolder;
public BaseRecyclerViewAdapter(Context context, @LayoutRes int itemLayoutRes) {
this.mContext = context;
this.mItemLayoutRes = itemLayoutRes;
}
public void setOnItemClickListener(OnItemClickListener<T> onItemClickListener) {
mOnItemClickListener = onItemClickListener;
}
public void setOnReloadClickListener(OnReloadClickListener onReloadClickListener) {
this.mOnReloadClickListener = onReloadClickListener;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_FOOTER) {
View view = getView(parent, R.layout.rv_item_footer);
return mFooterViewHolder = new FooterViewHolder(view);
} else {
View view = getView(parent, mItemLayoutRes);
final BaseViewHolder baseViewHolder = new BaseViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mOnItemClickListener != null) {
int position = baseViewHolder.getLayoutPosition();
mOnItemClickListener.onItemClick(view, mList.get(position));
}
}
});
return baseViewHolder;
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof BaseViewHolder && mList.size() != 0 && mList.size() != position) {
BaseViewHolder viewHolder = (BaseViewHolder) holder;
onBindViewHolder(viewHolder, mList.get(position), position);
} else {
ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams != null && layoutParams instanceof
StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams params =
(StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
params.setFullSpan(true);
}
}
}
/**
* item
*
* @param holder ViewHolder
* @param data 实体数据
* @param position 索引
*/
protected abstract void onBindViewHolder(BaseViewHolder holder, T data, int position);
protected View getView(ViewGroup parent, int layoutId) {
return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
}
@Override
public int getItemCount() {
int itemCount = mList.size();
return itemCount == 0 ? 0 : itemCount + 1;
}
@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount() && mList.size() > 0) {
return TYPE_FOOTER;
}
return TYPE_ITEM;
}
public void setData(List<T> data) {
mList.clear();
mList.addAll(data);
notifyDataSetChanged();
}
public void addMoreData(List<T> data) {
mList.addAll(data);
notifyDataSetChanged();
}
public void deleteData(int position) {
mList.remove(position);
notifyItemRemoved(position);
}
private class FooterViewHolder extends RecyclerView.ViewHolder {
ProgressBar progressBar;
TextView prompt;
public FooterViewHolder(View itemView) {
super(itemView);
progressBar = itemView.findViewById(R.id.pb_loading);
prompt = itemView.findViewById(R.id.tv_prompt);
}
}
public void setLoading() {
mFooterViewHolder.prompt.setText("正在加载更多");
mFooterViewHolder.prompt.setVisibility(View.VISIBLE);
mFooterViewHolder.progressBar.setVisibility(View.VISIBLE);
}
public void setNotMore() {
mFooterViewHolder.prompt.setText("没有更多了");
mFooterViewHolder.progressBar.setVisibility(View.GONE);
}
public void setNetError() {
mFooterViewHolder.prompt.setText("加载失败,点击重试");
mFooterViewHolder.prompt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mOnReloadClickListener != null) {
mOnReloadClickListener.onClick();
}
}
});
mFooterViewHolder.progressBar.setVisibility(View.GONE);
}
}
创建MainAdapter
public class MainAdapter extends BaseRecyclerViewAdapter<String>{
public MainAdapter(Context context) {
super(context, R.layout.rv_item_main);
}
@Override
protected void onBindViewHolder(BaseViewHolder holder, String data, int position) {
holder.setTextView(R.id.item_text_view,data);
}
}
继承BaseRecyclerViewAdapter
后面是泛型实体类,继承后需要实现MainAdapter
构造函数,必须调用super(context, R.layout.rv_item_main)
②,实际上是为了初始化父类BaseRecyclerViewAdapter
中的一个构造函数
public BaseRecyclerViewAdapter(Context context, @LayoutRes int itemLayoutRes) {
this.mContext = context;
this.mItemLayoutRes = itemLayoutRes;
}
②:super
中的参数
- context 上下文
- item布局id,其实这里也可以在父类中抽象一个 getItemLayoutRes(),返回一个布局id也也可以。
在Activity中使用
public class MainActivity extends AppCompatActivity {
private static final int COUNT = 15;
private MainAdapter mAdapter;
private SwipeRefreshLayout mSwipeRefreshLayout;
private RecyclerView mRecyclerView;
private int page = 1;
private boolean isRefresh;
private boolean isLoadMore;
private boolean isFailed = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initSwipeRefreshLayout();
initRecyclerView();
requestData(COUNT, page);
}
private void initView() {
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
}
private void initSwipeRefreshLayout() {
mSwipeRefreshLayout.setRefreshing(true);
mSwipeRefreshLayout.setColorSchemeColors(getResources()
.getIntArray(R.array.swipeRefreshLayoutColor));
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
page = 1;
isRefresh = true;
requestData(COUNT, page);
}
});
}
private void initRecyclerView() {
mAdapter = new MainAdapter(this);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mAdapter);
// 单击事件
mAdapter.setOnItemClickListener(new OnItemClickListener<String>() {
@Override
public void onItemClick(View view, String data) {
Toast.makeText(MainActivity.this, "点击了" + data, Toast.LENGTH_SHORT).show();
}
});
// 请求异常重试监听
mAdapter.setOnReloadClickListener(new OnReloadClickListener() {
@Override
public void onClick() {
mAdapter.setLoading();
requestData(COUNT, page);
}
});
// 滑到底部监听事件
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager();
int totalItemCount = recyclerView.getAdapter().getItemCount();
int lastVisibleItemPosition = lm.findLastVisibleItemPosition();
int visibleItemCount = recyclerView.getChildCount();
if (!isRefresh && !isLoadMore && newState == RecyclerView.SCROLL_STATE_IDLE &&
lastVisibleItemPosition == totalItemCount - 1 && visibleItemCount > 0) {
isLoadMore = true;
isRefresh = false;
mAdapter.setLoading();
requestData(COUNT, page);
}
}
});
}
/**
* 模拟网络请求数据
*
* @param count 请求数量
* @param page 请求页码
*/
private void requestData(int count, final int page) {
final List<String> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
list.add("item " + i);
}
// 延迟执行
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// 模拟请求失败时情况
if (isFailed && page == 3) {
onFailed("无网络链接");
isFailed = false;
return;
}else if (page == 4){
list.clear();
}
isFailed = true;
resultData(list);
}
}, 1500);
}
/**
* 模拟请求数据成功后返回的数据
*
* @param list 数据源
*/
private void resultData(List<String> list) {
closeRefreshing();
if (isRefresh) {
if (list != null && list.size() > 0) {
mAdapter.setData(list);
page++;
} else {
Toast.makeText(this, "暂无数据", Toast.LENGTH_SHORT).show();
}
} else {
if (list != null && list.size() > 0) {
Log.d("wz", "加载更多");
mAdapter.addMoreData(list);
page++;
} else {
mAdapter.setNotMore();
}
}
isRefresh = false;
isLoadMore = false;
}
/**
* 模拟数据请求失败结果
*
* @param errorMsg msg
*/
public void onFailed(String errorMsg) {
closeRefreshing();
if (mAdapter.getItemCount() == 0) {
Toast.makeText(this, "加载出错:" + errorMsg, Toast.LENGTH_SHORT).show();
} else {
mAdapter.setNetError();
}
}
/**
* 关闭 SwipeRefreshLayout 下拉动画
*/
private void closeRefreshing() {
if (mSwipeRefreshLayout.isRefreshing()) {
mSwipeRefreshLayout.setRefreshing(false);
}
}
}
这里我是模拟测试,真正使用没那么复杂,按照自己的想法去做就行。
BaseViewHolder
RecyclerView要求必须使用ViewHolder模式,一般我们在使用过程中,都需要去建立一个新的ViewHolder然后作为泛型传入Adapter。那么想要建立通用的Adapter,必须有个通用的ViewHolder。
首先我们确定下ViewHolder的主要的作用,实际上是通过成员变量存储对应的convertView中需要操作的字View,避免每次findViewById,从而提升运行的效率。
那么既然是通用的View,那么对于不同的ItemType肯定没有办法确定创建哪些成员变量View,取而代之的只能是个集合来存储了。
代码如下:
public class BaseViewHolder extends RecyclerView.ViewHolder {
private SparseArray<View> mViews;
private View mConvertView;
public BaseViewHolder(View itemView) {
super(itemView);
mConvertView = itemView;
mViews = new SparseArray<View>();
}
public static BaseViewHolder get(ViewGroup parent, int layoutId) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
return get(itemView);
}
public static BaseViewHolder get(View view){
return new BaseViewHolder(view);
}
/**
* 通过viewId获取控件
*
* @param viewId
* @return
*/
public <T extends View> T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
public BaseViewHolder setTextView(int viewId, String text) {
TextView view = getView(viewId);
view.setText(text);
return this;
}
}
代码很简单,我们的ViewHolder继承自RecyclerView.ViewHolder,内部通过SparseArray来缓存我们itemView内部的子View,从而得到一个通用的ViewHolder。每次需要创建ViewHolder只需要传入我们的view即可。
itemView单击事件
public interface OnItemClickListener<T> {
/**
* 单击事件
* @param view itemView
* @param data 实体类数据
*/
void onItemClick(View view, T data);
}
这里定义了一个范型接口,对应 BaseRecyclerViewAdapter 中的范型T类。
底部重试点击事件
public interface OnReloadClickListener {
/**
* 重试点击事件
*/
void onClick();
}
只有在底部加载失败的情况下,点击item触发该onClick()