您的位置:首页 > 移动开发 > Android开发

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

2017-04-04 12:08 363 查看
学习资料:
鸿洋大神为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个需要必须重写的方法:

方法1:public 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()
方法中,创建的
ViewHolder

int 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
处的item
notifyItemMoved(int fromPosition, int toPosition)
通知
RecyclerView
移除从
fromPosition
toPosition
item
notifyItemRangeRemoved(int positionStart, int itemCount)
通知
RecyclerView
移除从
positionStart
开始的
itemCount
item
notifyItemChanged(int position, Object payload)
通知
RecyclerView
改变指定
position
item
object
notifyItemRangeChanged(int positionStart,int itemCount)
通知
RecyclerView
positionStart
开始改变
itemCount
item
notifyItemRangeChanged(int positionStart,int itemCount,Object payload)
通知
RecyclerView
positionStart
开始改变
itemCount
item
的对象
notifyItemInserted(int position)
通知
RecyclerView
position
处插入一个
item
notifyItemRangeInserted(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)
,占满一行
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: