想想还是有必要上一波官话的:
-
RecyclerView
是support:recyclerview-v7
中提供的控件,最低兼容到android 3.0
版本。 -
RecyclerView
是ListView
的更高级和灵活的版本。 - 提供了
LinearLayoutManager
(类listview
)、GridLayoutManager
(类gridview
)、StaggeredGridLayoutManager
(瀑布流)三种排列方式。
简单来说,RecyclerView
是控件里的C位也不为过了。
一、Why And What?
RecyclerView
是我平时开发工作中可以说是最常用的组件了,所以做适当并且适合的封装是必须的。
目前关于RecyclerView
的封装可以说不要太多哦,为什么我还要选择自己封装呢?
其实在做这个封装之前我也浏览过很多大神对于RecyclerView
的封装,但是普遍存在两个基本的问题:
- 只针对某一方面做封装
- 封装内容过多且复杂,很多都是我用不上的
我自己对于RecyclerView
的需求有哪些
1. 万用型的ViewHolder
,不用再每个界面写ViewHolder
。
2. 继承自RecyclerView.Adapter
的基类Adapter
,负责管理RecyclerView
的数据。
3. 下拉刷新和上拉加载(这个麻烦的地方在于要有一定的特效,虽然很多封装都实现了,但是大多简陋,优美的少,很影响用户体验)。
4. 多种布局的实现,即列表中存在不同的布局。
5. 简洁地实现item中数据和view的绑定。
6. 将一个RecyclerView
置于一个BaseFragment
中,通过BaseFragment
来实现一个最简单RecyclerView
的页面。
二、ViewHolder和RecyclerView.Adapter
public class ViewHolderRocket extends RecyclerView.ViewHolder {
private SparseArray<View> views;
private View mItemView;
public ViewHolderRocket(View itemView) {
super(itemView);
views = new SparseArray<>();
mItemView = itemView;
}
public View getmItemView() {
return mItemView;
}
public View getView(int resId) {
return retrieveView(resId);
}
protected <V extends View> V retrieveView(int viewId){
View view = views.get(viewId);
if(view == null){
view = mItemView.findViewById(viewId);
views.put(viewId,view);
}
return (V) view;
}
public TextView getTextView(int resId){
return retrieveView(resId);
}
public ImageView getImageView(int resId){
return retrieveView(resId);
}
public Button getButton(int resId){
return retrieveView(resId);
}
public LinearLayout getLinearLayout(int resId){return retrieveView(resId);}
public void setText(int resId,CharSequence text){
getTextView(resId).setText(text);
}
public void setText(int resId,int strId){
getTextView(resId).setText(strId);
}
}
ViewHolderRocket
继承自RecyclerView.ViewHolder
,将item
的view
存储在SparseArray
数组中,避免了重复的findViewById
,提高了效率和性能。
关于BaseAdapter,最主要的作用就是对于数据的操作,包括初始设置、新增、删除和清除所有。
一下代码展示了除了这些之外的另一个重要作用,那就是用于不同布局的实现:
public class RVBaseAdapter<C extends RVCell> extends RecyclerView.Adapter<ViewHolderRocket> {
private static final String TAG = "RVBaseAdapter";
protected List<C> mData;
public RVBaseAdapter() {
mData = new ArrayList<>();
}
public List<C> getData() {
return mData;
}
@Override
public int getItemViewType(int position) {
return mData.get(position).getItemType();
}
@Override
public ViewHolderRocket onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolderRocket(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false));
}
@Override
public void onBindViewHolder(ViewHolderRocket holder, int position) {
mData.get(position).bindViewHolder(holder, position);
}
@Override
public void onViewDetachedFromWindow(ViewHolderRocket holder) {
super.onViewDetachedFromWindow(holder);
Log.e(TAG, "onViewDetachedFromWindow invoke...");
//释放资源
int position = holder.getAdapterPosition();
//越界检查
if (position < 0 || position >= mData.size()) {
return;
}
mData.get(position).releaseResource();
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
/**
* add one cell
*
* @param cell
*/
public void add(C cell) {
mData.add(cell);
int index = mData.indexOf(cell);
notifyItemInserted(index);
}
public void add(int index, C cell) {
mData.add(index, cell);
notifyItemInserted(index);
}
/**
* remove a cell
*
* @param cell
*/
public void remove(C cell) {
int indexOfCell = mData.indexOf(cell);
remove(indexOfCell);
}
public void remove(int index) {
mData.remove(index);
notifyItemRemoved(index);
}
/**
* @param start
* @param count
*/
public void remove(int start, int count) {
if ((start + count) > mData.size()) {
return;
}
mData.subList(start, start + count).clear();
notifyItemRangeRemoved(start, count);
}
/**
* add a cell list
*
* @param cells
*/
public void addAll(List<C> cells) {
if (cells == null || cells.size() == 0) {
return;
}
Log.e(TAG, "addAll cell size:" + cells.size());
mData.addAll(cells);
notifyItemRangeInserted(mData.size() - cells.size(), mData.size());
}
public void addAll(int index, List<C> cells) {
if (cells == null || cells.size() == 0) {
return;
}
mData.addAll(index, cells);
notifyItemRangeInserted(index, index + cells.size());
}
public void clear() {
mData.clear();
notifyDataSetChanged();
}
}
关于不同布局的实现,我们留到后面再讲。
三、下拉刷新和上拉加载
想要实现下拉刷新和上拉加载,说简单也简单,Google
官方自己提供了SwipeRefreshLayout
控件来帮助我们实现下拉刷新,上拉加载也可以通过监控列表中最后一个数据的位置来判断是否要加载。
但是效果也就是一般般,对于已经习惯了微博、微信、支付宝那些强大的刷新加载功能的用户来说,他们当然不会满足于这种最原始的方式。
想要做的酷炫,就要做出炫酷的动画特效,自己做的话实在是力不从心啊,没有扎实的view基础,真的望洋兴叹的感觉。
还有有大神帮我们解决了这个问题:
强大且易用的SmartRefreshLayout
在RvFragment
中有可以设置Header和Footer的方法,以及设置是否支持下拉刷新和上拉加载的方法:
/**
* 是否启用下拉刷新功能
*
* @param enable
*/
protected void setEnableRefresh(boolean enable) {
refreshLayout.setEnableRefresh(enable);
}
/**
* 是否启用上拉加载功能
*
* @param enable
*/
protected void setEnableLoadMore(boolean enable) {
refreshLayout.setEnableLoadMore(enable);
}
/**
* 设置下拉刷新Header
*
* @param header
*/
protected void setRefreshHeader(RefreshHeader header) {
refreshLayout.setRefreshHeader(header);
}
/**
* 设置上拉加载Footer
* @param footer
*/
protected void setRefreshFooter(RefreshFooter footer) {
refreshLayout.setRefreshFooter(footer);
}
感兴趣的同学可以看看,有很强大的Header
和Footer
样式。
四、多种布局的实现
这本来是产品经理提出的需求
- 用在数据页面,能够在列表里面根据某些特殊的数据来展示不同的页面
- 用在首页,一个
RecyclerView
就展示全复杂的主页
public interface Cell {
/**
* 回收资源
*/
void releaseResource();
/**
* 获取viewType
*
* @return
*/
int getItemType();
/**
* 绑定ViewHolder
*
* @param holder
* @param position
* @return
*/
void bindViewHolder(RVBaseViewHolder holder, int position);
}
public abstract class RVCell<T> implements Cell {
public T mData;
public RVCell(T t) {
mData = t;
}
}
实现:
public class TestCell extends RVCell<PersonBean> {
public TestCell(PersonBean bean) {
super(bean);
}
@Override
public void releaseResource() {
}
@Override
public int getItemType() {
return R.layout.rv_item;
}
@Override
public void bindViewHolder(final ViewHolderRocket holder, final int position) {
holder.setText(R.id.store_name_tv, mData.getName());
holder.setText(R.id.store_address_tv, mData.getAge());
holder.setText(R.id.store_owner_tv, mData.getPhone());
final View mItemView = holder.getmItemView();
mItemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText( mItemView.getContext(), position + "", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(mItemView.getContext(), MainActivity.class);
mItemView.getContext().startActivity(intent);
}
});
}
}
这时候我们再回头看上面的RVBaseAdapter
RVBaseAdapter<C extends RVBaseCell> extends RecyclerView.Adapter<RVBaseViewHolder>{
protected List<C> mData;
public RVBaseAdapter() {
mData = new ArrayList<>();
}
}
RVBaseCell
持有数据mData
和视图holder
,即一个Item
。
接下来我们看RVAdapter
的三个重要方法:
@Override
public int getItemViewType(int position) {
return mData.get(position).getItemType();
}
@Override
public ViewHolderRocket onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolderRocket(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false));
}
@Override
public void onBindViewHolder(ViewHolderRocket holder, int position) {
mData.get(position).bindViewHolder(holder, position);
}
-
Cell
的getItemType()
方法返回的是item
的布局layout
。 - 在
RVAdapter
的getItemViewType
方法,return
“布局的id
”。 -
onCreateViewHolder(ViewGroup parent, int viewType)
中的viewType
即是getItemViewType
方法返回的布局id
,根据这个id获取视图,并返回ViewHolderRocket
。 - 在
onBindViewHolder
中调用mData
的bindViewHolder
方法,然后在TestCell
(实际的实现方式)实现该方法。 - 在
mData
的bindViewHolder
方法中实现item
中数据和view
的绑定。
五、基于RecyclerView
的RvBaseFragment
public abstract class RvFragment<T> extends Fragment {
public static final String TAG = "RvFragment";
private FrameLayout titleLayout;
protected RecyclerView mRecyclerView;
protected RVAdapter mBaseAdapter;
protected SmartRefreshLayout refreshLayout;
protected int pageNumber = 1;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.rv_base_fragment_layout, null);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initView(view);
initRefreshListener();
initRecycleView();
onRecyclerViewInitialized();
}
private void initView(View view) {
titleLayout = view.findViewById(R.id.title_layout);
refreshLayout = view.findViewById(R.id.refreshLayout);
mRecyclerView = view.findViewById(R.id.base_fragment_rv);
mRecyclerView.setLayoutManager(initLayoutManger());
}
private void initRefreshListener() {
refreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshlayout) { //下拉刷新
refreshlayout.finishRefresh(500);
pageNumber = 1;
RvFragment.this.onRefresh();
}
});
refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore(@NonNull RefreshLayout refreshlayout) { //上拉加载
refreshlayout.finishLoadMore(500);
pageNumber++;
RvFragment.this.onLoadMore();
}
});
refreshLayout.setRefreshHeader(new ClassicsHeader(this.getActivity()).setPrimaryColor(colorUtil.getColorPrimary(this.getActivity())));
refreshLayout.setRefreshFooter(new ClassicsFooter(this.getActivity()).setPrimaryColor(getResources().getColor(R.color.material_gray_300)));
}
/**
* 设置下拉刷新Header
*
* @param header
*/
protected void setRefreshHeader(RefreshHeader header) {
refreshLayout.setRefreshHeader(header);
}
/**
* 设置上拉加载Footer
* @param footer
*/
protected void setRefreshFooter(RefreshFooter footer) {
refreshLayout.setRefreshFooter(footer);
}
private void initRecycleView() {
mBaseAdapter = initAdapter();
mRecyclerView.setAdapter(mBaseAdapter);
}
/**
* 设置头布局,不在列表中
*
* @param view
*/
protected void addTitleView(View view) {
if (view == null) {
return;
}
titleLayout.addView(view);
}
/**
* 子类可以自己指定Adapter,如果不指定默认RVSimpleAdapter
*
* @return
*/
protected RVAdapter initAdapter() {
return new RVAdapter();
}
/**
* 子类自己指定RecyclerView的LayoutManager,如果不指定,默认为LinearLayoutManager,VERTICAL 方向
*
* @return
*/
protected RecyclerView.LayoutManager initLayoutManger() {
LinearLayoutManager manager = new LinearLayoutManager(getContext());
manager.setOrientation(LinearLayoutManager.VERTICAL);
return manager;
}
/**
* 是否启用下拉刷新功能
*
* @param enable
*/
protected void setEnableRefresh(boolean enable) {
refreshLayout.setEnableRefresh(enable);
}
/**
* 是否启用上拉加载功能
*
* @param enable
*/
protected void setEnableLoadMore(boolean enable) {
refreshLayout.setEnableLoadMore(enable);
}
/**
* RecyclerView 初始化完毕,可以在这个方法里绑定数据
*/
public abstract void onRecyclerViewInitialized();
/**
* 下拉刷新
*/
public abstract void onRefresh();
/**
* 上拉加载更多
*/
public abstract void onLoadMore();
/**
* 根据实体生成对应的Cell
*
* @param list 实体列表
* @return cell列表
*/
protected abstract List<Cell> getCells(List<T> list);
}
实际使用:
public class RecyclerViewFragment extends RvFragment {
public static RecyclerViewFragment newInstance() {
RecyclerViewFragment fragment = new RecyclerViewFragment();
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onRecyclerViewInitialized() {
mBaseAdapter.showLoading();
ArrayList<PersonBean> dataList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
PersonBean bean = new PersonBean();
bean.setName("姓名" + i + "");
bean.setAge("20");
bean.setPhone("18956321458");
dataList.add(bean);
}
mBaseAdapter.removeLoading();
mBaseAdapter.addAll(getCells(dataList));
}
@Override
public void onRefresh() {
mBaseAdapter.clear();
ArrayList<PersonBean> dataList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
PersonBean bean = new PersonBean();
bean.setName("姓名" + i + "");
bean.setAge("20");
bean.setPhone("18956321458");
dataList.add(bean);
}
mBaseAdapter.addAll(getCells(dataList));
Toast.makeText(this.getActivity(), "更新啦", Toast.LENGTH_SHORT).show();
}
@Override
public void onLoadMore() {
ArrayList<PersonBean> dataList = new ArrayList<>();
for (int i = 10 * (pageNumber - 1); i < pageNumber * 10; i++) {
PersonBean bean = new PersonBean();
bean.setName("姓名" + i + "");
bean.setAge("20");
bean.setPhone("18956321458");
dataList.add(bean);
}
mBaseAdapter.addAll(getCells(dataList));
}
@Override
protected List<Cell> getCells(List list) {
List<Cell> cells = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
PersonBean bean = (PersonBean) list.get(i);
cells.add(new TestCell(bean));
}
return cells;
}
}