Android——RecyclerView入门学习之RecyclerView.Adapter

    xiaoxiao2021-03-25  264

    学习资料: 鸿洋大神为RecyclerView打造通用Adapter让RecyclerView更加好用鸿洋大神Android优雅的为RecyclerView添加HeaderView和FooterView

    之前使用RecyclerView.Adapter,基本就类似套用公式,死步骤,对Adapter感到既熟悉又陌生。从去年我开始接触学习Android之时,RecyclerView已经开始大量被运用,逐步取代ListView。遂,正好,那就先直接学习RecyclerView.Adapter相关知识


    1. RecyclerView.Adapter适配器

    RecyclerView.Adapter,一个抽象类,并支持泛型

    public static abstract class Adapter<VH extends ViewHolder> { ... }

    定义一个MyRecyclerViewAdapter继承RecyclerView.Adapter后,Android Stuido提醒需要重写3个方法,在重写3个方法前,一般会先定义一个Holder继承RecycelrView.ViewHolder,之后直接在MyRecyclerViewAdapter上,指定泛型就是RecyclerHolder

    3个需要必须重写的方法:

    方法1public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 方法2:public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) 方法3:public int getItemCount()

    在指定了泛型为RecyclerHoler后,方法2也会根据泛型改变onBindViewHolder(RecyclerHolder holder, int position)


    1.1 onCreateViewHolder(ViewGroup parent, int viewType)创建Holder

    源码:

    /** * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent an item. * * @param parent The ViewGroup into which the new View will be added after it is bound to an adapter position. * @param viewType The view type of the new View. * * @return A new ViewHolder that holds a View of the given view type. */ public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); ViewGroup parent:可以简单理解为item的根ViewGroup,item的子控件加载在其中int viewType:item的类型,可以根据viewType来创建不同的ViewHolder,来加载不同的类型的item

    这个方法就是用来创建出一个新的ViewHolder,可以根据需求的itemType,创建出多个ViewHolder。创建多个itemType时,需要getItemViewType(int position)方法配合


    1.2 onBindViewHolder(RecyclerHolder holder, int position)绑定ViewHolder

    源码:

    ** *Called by RecyclerView to display the data at the specified position. *This method should update the contents of the {@link ViewHolder#itemView} to reflect the item at the given position. * *@param holder The ViewHolder which should be updated to represent the contents of the item at the given position in the data set. *@param position The position of the item within the adapter's data set. */ public abstract void onBindViewHolder(VH holder, int position); VH holder:就是在onCreateViewHolder()方法中,创建的ViewHolderint position:item对应的DataList数据源集合的postion

    postion就是adapter position,RecycelrView中item的数量,就是根据DataList数据源集合的数量来创建的


    1.3 getItemCount()获取Item的数目

    源码:

    /** * Returns the total number of items in the data set held by the adapter. * * @return The total number of items in this adapter. */ public abstract int getItemCount();

    这个方法的返回值,便是RecyclerView中实际item的数量。有些情况下,当增加了HeaderView或者FooterView后,需要注意考虑这个返回值


    1.4 简单实用

    一个最简单的RecyclerViewAdapter

    public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.RecyclerHolder> { private Context mContext; private List<String> dataList = new ArrayList<>(); public MyRecyclerViewAdapter(RecyclerView recyclerView) { this.mContext = recyclerView.getContext(); } public void setData(List<String> dataList) { if (null != dataList) { this.dataList.clear(); this.dataList.addAll(dataList); notifyDataSetChanged(); } } @Override public RecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout, parent, false); return new RecyclerHolder(view); } @Override public void onBindViewHolder(RecyclerHolder holder, int position) { holder.textView.setText(dataList.get(position)); } @Override public int getItemCount() { return dataList.size(); } class RecyclerHolder extends RecyclerView.ViewHolder { TextView textView; private RecyclerHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.tv__id_item_layout); } } }

    我的个人习惯是单独使用一个setData()方法将DataList传递进Adapter,看到网上有一些博客中会通过构造方法传递。我一般会在网络请求前就初始化Adapter,当异步网络请求拿到解析过的JSON数据后,调用这个方法将数据加载进Adapter,即使做了分页,也可以比较方。但感觉这种方法终究会浪费一点性能

    注意,在 onCreateViewHolder()方法中:

    View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout, parent, false);

    inflate()方法使用的是3个参数的方法。


    1.4.1 问题

    以前使用2个参数的方法inflate(@LayoutRes int resource, @Nullable ViewGroup root),下面的形式

    View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout,null);

    遇到的一个问题

    2个参数方法遇到的问题

    两种方法,使用的是同一套布局

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/tv__id_item_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorAccent" android:textAllCaps="false" android:textColor="@android:color/white" android:textSize="20sp" /> </LinearLayout>

    item内的TextView的宽是match_parent,但使用两个参数的方法时,看起来却是wrap_content的效果


    1.4.2尝试从源码中找问题

    两个参数的方法源码

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }

    两个参数的方法内部调用了3个参数的方法,此时inflate(resourceId, null, false),root为null,attachToRoot为false

    最终来到了这里,只保留了部分代码:

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { ... View result = root; ... // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; ... if (root != null) { ... params = root.generateLayoutParams(attrs); ... } ... rInflateChildren(parser, temp, attrs, true); ... if (root == null || !attachToRoot) { result = temp; } }

    使用两个参数的inflate()方法,ViewGroup.LayoutParams params最终为null;而如果使用3个参数的方法,最终params = params = root.generateLayoutParams(attrs)

    这里为了减少出现问题的出现,就使用3个参数的方法inflate(R.layout.id_rv_item_layout, parent, false)

    这里看源码也就看了这小段一段,inflate()方法的完整过程还是比较复杂的,比较浅显的知道问题出在哪里后,没有深挖


    1.4 点击事件

    完整代码:

    public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.RecyclerHolder> { private Context mContext; private List<String> dataList = new ArrayList<>(); private onRecyclerItemClickerListener mListener; public MyRecyclerViewAdapter(RecyclerView recyclerView) { this.mContext = recyclerView.getContext(); } /** * 增加点击监听 */ public void setItemListener(onRecyclerItemClickerListener mListener) { this.mListener = mListener; } /** * 设置数据源 */ public void setData(List<String> dataList) { if (null != dataList) { this.dataList.clear(); this.dataList.addAll(dataList); notifyDataSetChanged(); } } public List<String> getDataList() { return dataList; } @Override public RecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout, parent, false); // View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout,null); return new RecyclerHolder(view); } @Override public void onBindViewHolder(RecyclerHolder holder, int position) { holder.textView.setText(dataList.get(position)); holder.textView.setOnClickListener(getOnClickListener(position)); } private View.OnClickListener getOnClickListener(final int position) { return new View.OnClickListener() { @Override public void onClick(View v) { if (null != mListener && null != v) { mListener.onRecyclerItemClick(v, dataList.get(position), position); } } }; } @Override public int getItemCount() { return dataList.size(); } class RecyclerHolder extends RecyclerView.ViewHolder { TextView textView; private RecyclerHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.tv__id_item_layout); } } /** * 点击监听回调接口 */ public interface onRecyclerItemClickerListener { void onRecyclerItemClick(View view, Object data, int position); } }

    定义一个接口onRecyclerItemClickerListener,这样可以在Actiivty设置监听对象,之后为TextView设置点击监听事件,在TextView的点击事件方法中,使用onRecyclerItemClick()进行回调


    在Activity中使用:

    //设置点击事件 adapter.setItemListener(new MyRecyclerViewAdapter.onRecyclerItemClickerListener() { @Override public void onRecyclerItemClick(View view, Object data, int position) { String s = (String) data; adapter.getDataList().set(position, s + "---->hi"); adapter.notifyItemChanged(position); } });

    在监控对象回调方法中,使用了notifyItemChanged(position)来进行局部刷新

    点击进行局部刷新

    但这种方式会new出一大堆View.OnClickListener,还有一种思路是利用RecycelrView的onTouchListener和GestureDetector手势来进行设置,可以看看三种方式实现RecyclerView的Item点击事件


    1.5 一系列的notifyData方法

    一共有10个方法

    方法作用notifyDataSetChanged()通知RecycelrView进行全局刷新notifyItemChanged(int position)通知RecycelrView在adapter position处局进行部刷新notifyItemRemoved(int position)通知RecyclerView移除在adapter position处的itemnotifyItemMoved(int fromPosition, int toPosition)通知RecyclerView移除从fromPosition到toPosition的itemnotifyItemRangeRemoved(int positionStart, int itemCount)通知RecyclerView移除从positionStart开始的itemCount个itemnotifyItemChanged(int position, Object payload)通知RecyclerView改变指定position的item的objectnotifyItemRangeChanged(int positionStart,int itemCount)通知RecyclerView从positionStart开始改变itemCount个itemnotifyItemRangeChanged(int positionStart,int itemCount,Object payload)通知RecyclerView从positionStart开始改变itemCount个item的对象notifyItemInserted(int position)通知RecyclerView在position处插入一个itemnotifyItemRangeInserted(int positionStart, int itemCount)通知RecyclerView从positionStart开始插入itemCount个item

    有些情况下,方法需要考虑组合使用,否则可能出现position错乱,例如

    在Adapter中移除或者插入item

    /** * 移除指定Position的Item */ public void remove(int position) { if (dataList.size() == 0) return; dataList.remove(position); notifyItemRemoved(position); notifyItemRangeChanged(position, dataList.size() - position); } //在Activity中使用 ,设置点击事件 adapter.setItemListener(new MyRecyclerViewAdapter.onRecyclerItemClickerListener() { @Override public void onRecyclerItemClick(View view, Object data, int position) { adapter.remove(position); // String s = (String) data; // adapter.inserted(position,s+"---->hi"); } }); //在指定位置插入一个item public void inserted(int position, String s) { if (dataList.size() == 0) return; dataList.add(position, s); notifyItemInserted(position); notifyItemRangeChanged(position, dataList.size() - position); }

    notifyItemRemoved(position)虽然通知移除了RecycelrView在position位置上的itemA,但itemA之后的一系列item也需要进行改变,也需要通知RecyclerView进行改变

    但这两个方法性能上都有问题,卡顿比较明显,应该会有更好的动态改变或者动态插入item的方法,以后学到了再补充


    1.5 简易的封装

    通用的ViewHolder:

    public class BaseViewHolder extends RecyclerView.ViewHolder { private final SparseArray<View> sparseArray; public BaseViewHolder(View itemView) { super(itemView); this.sparseArray = new SparseArray<>(8); //一般一个Item 不会超过8种控件 } public <T extends View> T getView(int viewId) { View view = sparseArray.get(viewId); if (view == null) { view = itemView.findViewById(viewId); sparseArray.put(viewId, view); } return (T) view; } public BaseViewHolder setText(int viewId, String text) { TextView tv = getView(viewId); if (tv != null) { tv.setText(text); } return this; } }

    主要思路就是使用SparseArray<View>将控件存起来


    适配器:

    public abstract class CommonBaseAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> { protected List<T> data = new ArrayList<>(); protected int itemLayoutId; protected Context mContext; private onRecyclerItemClickerListener mListener; public CommonBaseAdapter(RecyclerView rv, @LayoutRes int itemLayoutId) { this.itemLayoutId = itemLayoutId; this.mContext = rv.getContext(); } public void setData(List<T> data) { if (data != null) { this.data.clear(); this.data.addAll(data); notifyDataSetChanged(); } } /** * 增加点击监听 */ public void setItemListener(onRecyclerItemClickerListener mListener) { this.mListener = mListener; } @Override public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //这里使用3个参数的方法 View view = LayoutInflater.from(mContext).inflate(itemLayoutId, parent, false); return new BaseViewHolder(view); } @Override public void onBindViewHolder(BaseViewHolder holder, int position) { bindViewData(holder, data.get(position), position); holder.itemView.setOnClickListener(getOnClickListener(position)); } private View.OnClickListener getOnClickListener(final int position) { return new View.OnClickListener() { @Override public void onClick(View v) { if (mListener != null && v != null) { mListener.onRecyclerItemClick(v, data.get(position), position); } } }; } @Override public int getItemCount() { return this.data.size(); } public abstract void bindViewData(BaseViewHolder holder, T item, int position); interface onRecyclerItemClickerListener { void onRecyclerItemClick(View view, Object data, int position); } }

    内部提供一个抽象方法bindViewData(),子类重写抽象方法来做一些具体的操作。

    封装的很简单,但平常学习使用也能减少一些重复代码。网上有很多强大的封装,可以再深入学习一下为RecyclerView打造通用Adapter让RecyclerView更加好用


    使用:

    public class RecyclerViewAdapter extends CommonBaseAdapter<String> { public RecyclerViewAdapter(RecyclerView rv, @LayoutRes int itemLayoutId, @IdRes int resId) { super(rv, itemLayoutId); } @Override public void bindViewData(BaseViewHolder holder, String item, int position) { holder.setText(R.id.textViewId, item); } }

    在Activity中就可以进行使用

    这个简易的封装并没有对添加加载图片的方法。加载图片的方法一开始也我封装在了这个CommonBaseAdapter中,但后来发现直接封装在这里并不是好的思路

    图片需要做的处理比较多,而且主流的库有3个,为了易于维护,还是将图片的操作单独再封装在一个工具类中,在CommonBaseAdapter中使用操作图片的工具类比较好


    1.6 添加HeaderView和FooterViewiew

    学的鸿洋大神的代码和思路,涉及到了装饰模式。还有一种添加方式是直接通过使用多种item直接在现有的CommonBaseAdapter来修改,但感觉这种思路需要对CommonBaseAdapter改动的代码太多,点击事件的position也需要考虑,不如鸿洋大神的这种思路易于开发和维护

    代码:

    public class HeaderAndFooterAdapter extends RecyclerView.Adapter<BaseViewHolder> { private CommonBaseAdapter mAdapter; private static final int HEADER_VIEW_TYPE = 2 << 6; private static final int FOOTER_VIEW_TYPE = 2 << 5; private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>(); private SparseArrayCompat<View> mFooterViews = new SparseArrayCompat<>(); public HeaderAndFooterAdapter(CommonBaseAdapter mAdapter) { this.mAdapter = mAdapter; } @Override public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (null != mHeaderViews.get(viewType)) { return new HeaderAndFooterHolder(mHeaderViews.get(viewType)); } else if (null != mFooterViews.get(viewType)) { return new HeaderAndFooterHolder(mFooterViews.get(viewType)); } return mAdapter.onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(BaseViewHolder holder, int position) { if (isHeaderViewPosition(position)) return; if (isFooterViewPosition(position)) return; mAdapter.onBindViewHolder(holder, position - getHeaderViewCount()); } @Override public int getItemViewType(int position) { if (isHeaderViewPosition(position)) { return mHeaderViews.keyAt(position); } else if (isFooterViewPosition(position)) { return mFooterViews.keyAt(position-getHeaderViewCount()-getAdapterItemCount()); } return mAdapter.getItemViewType(position - getHeaderViewCount()); } @Override public int getItemCount() { return getHeaderViewCount() + getFooterViewCount() + getAdapterItemCount(); } /** * 加入HeaderView */ public void addHeaderView(View view) { mHeaderViews.put(mHeaderViews.size() + HEADER_VIEW_TYPE, view); } /** * 加入FooterView */ public void addFootView(View view) { mFooterViews.put(mFooterViews.size() + FOOTER_VIEW_TYPE, view); } /** * HeaderView 的数目 */ public int getHeaderViewCount() { return mHeaderViews.size(); } /** * FooterView 的数目 */ public int getFooterViewCount() { return mFooterViews.size(); } /** * 是不是HeaderView的Position */ private boolean isHeaderViewPosition(int position) { return position < getHeaderViewCount(); } /** * 是不是FooterView的Position */ private boolean isFooterViewPosition(int position) { return position >= getHeaderViewCount() + getAdapterItemCount(); } /** * 得到Adapter中Item的数目 */ private int getAdapterItemCount() { return mAdapter.getItemCount(); } private class HeaderAndFooterHolder extends BaseViewHolder { private HeaderAndFooterHolder(View itemView) { super(itemView); } } }

    封装的思路: 将add进来的view进行保存,当加载item时,利用itemType对view进行类型判断,如果是HeaderView或者FooterView就创建HeaderAndFooterHolder,然后绑定只是用来显示并没有对HeaderView或者FooterView做其他更多事件的处理


    使用也比较方便:

    //数据适配器 RecyclerViewAdapter adapter = new RecyclerViewAdapter(rv, R.layout.id_rv_item_layout, R.id.tv__id_item_layout); //头View适配器 HeaderAndFooterAdapter headerAndFooterAdapter = new HeaderAndFooterAdapter(adapter); //HeaderView TextView headerView = new TextView(this); headerView.setBackgroundColor(Color.BLACK); headerView.setTextColor(Color.WHITE); headerView.setWidth(1080); headerView.setTextSize(50); headerView.setText("我是头"); headerAndFooterAdapter.addHeaderView(headerView); //设置适配器 rv.setAdapter(headerAndFooterAdapter); //添加数据 addData(adapter); 添加HeaderView

    RecyelrView设置的适配器是headerAndFooterAdapter,而添加数据使用的是adapter。HeaderAndFooterAdapter是支持添加多个HeaderView的


    1.6.1 GridLayoutManger和StaggeredGridLayoutManager跨列问题

    上面添加HeaderView时,使用的LinerLayoutManager,当使用GridLayoutManger时,便会有问题

    HeaderView不能单独占据一行

    GridLayoutManager遇到问题

    加入针对GridLayoutManager跨列处理的代码:

    /** *当RecyelrView开始观察Adapter会被回调 */ @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { mAdapter.onAttachedToRecyclerView(recyclerView); final RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager instanceof GridLayoutManager) { final GridLayoutManager gridLayoutManager = (GridLayoutManager) manager; gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { int viewType = getItemViewType(position); //如果是HeaderView或者是FooterView,设置占据gridLayoutManager.getSpanCount()列 if (null != mHeaderViews.get(viewType) || null != mFooterViews.get(viewType)) { return gridLayoutManager.getSpanCount(); } return 1; } }); } } 跨列处理

    当布局管理器为GridLayouManger时,对当前要的添加的item进行判断,如果是HeaderView或者是FooterView,就进行跨列处理,单独占据一行


    加入针对StaggeredGridLayoutManager跨列处理的代码:

    /** * 一个item通过adapter开始显示会被回调 */ @Override public void onViewAttachedToWindow(BaseViewHolder holder) { super.onViewAttachedToWindow(holder); int position = holder.getLayoutPosition(); if (isHeaderViewPosition(position)||isFooterViewPosition(position)){ ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (null != lp && lp instanceof StaggeredGridLayoutManager.LayoutParams){ StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(true);//占满一行 } } } 瀑布流跨列

    当布局管理器为StaggeredGridLayoutManager时,对当前要的添加的item进行判断,如果是HeaderView或者是FooterView,就设置setFullSpan(true),占满一行

    转载请注明原文地址: https://ju.6miu.com/read-58.html

    最新回复(0)