当咱们使用RecyclerView
时,第一件事就是要继承于RecyclerView.Adapter
,实现其中的抽象方法,来处理数据的展现逻辑,今天,咱们就来介绍一下Adapter
中的相关方法。android
咱们从一个简单的线性列表布局开始,介绍RecyclerView.Adapter
的基础用法。 首先,须要导入远程依赖包:git
compile'com.android.support:recyclerview-v7:25.3.1'
复制代码
接着,继承于RecyclerView.Adapter
来实现自定义的NormalAdapter
:github
public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.NormalViewHolder> {
private List<String> mTitles = new ArrayList<>();
public NormalAdapter(List<String> titles) {
mTitles = titles;
}
@Override
public NormalViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_item, parent, false);
return new NormalViewHolder(itemView);
}
@Override
public void onBindViewHolder(NormalViewHolder holder, int position) {
holder.setTitle(mTitles.get(position));
}
@Override
public int getItemCount() {
return mTitles.size();
}
class NormalViewHolder extends RecyclerView.ViewHolder {
private TextView mTextView;
NormalViewHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.tv_title);
}
void setTitle(String title) {
mTextView.setText(title);
}
}
}
复制代码
当咱们实现本身的Adapter
时,至少要作四个工做:bash
RecyclerView.ViewHolder
,编写本身的ViewHolder
RecyclerView
中每一个Item
的布局以及和它关联的数据,它同时也是RecyclerView.Adapter<VH>
中须要指定的VH
类型。super(View view)
方法来传入Item
的跟布局来给基类中itemView
变量赋值,还应当提早执行findViewById
来得到其中的子View
以便咱们以后对它们进行更新。onCreateViewHolder(ViewGroup parent, int viewType)
RecyclerView
须要咱们提供类型为viewType
的新ViewHolder
时,会回调这个方法。Item
的根布局,并返回一个和它绑定的ViewHolder
。onBindViewHolder(VH viewHolder, int position)
RecyclerView
须要展现对应position
位置的数据时会回调这个方法。viewHolder
中持有的对应position
上的View
,咱们能够更新视图。getItemCount()
Item
的总数。在Activity
中,咱们给Adapter
传递数据,使用方法和ListView
基本相同,只是多了一句在设置LayoutManager
的操做,这个咱们后面再分析。ide
private void init() {
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
mTitles = new ArrayList<>();
for (int i = 0; i < 20; i++) {
mTitles.add("My name is " + i);
}
NormalAdapter normalAdapter = new NormalAdapter(mTitles);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(normalAdapter);
}
复制代码
这样,一个RecyclerView
的例子就完成了: 布局
ViewType
下的复用状况分析下面,咱们来分析一下两个关键方法的调用时机:post
onCreateViewHolder
onBindViewHolder
经过这两个方法回调的时机,咱们能够对RecyclerView
复用的机制有一个大概的了解。ui
刚开始进入界面的时候,咱们只展现了3
个Item
,此时这两个方法的调用状况以下,能够看到,RecyclerView
只实例化了屏幕内可见的ViewHolder
,而且onBindViewHolder
是在对应的onCreateViewHolder
调用完后当即调用的: this
当咱们手指触摸到屏幕,并开始向下滑动,咱们会发现,虽然position=3
的Item
尚未展现出来,可是这时候它的onCreateViewHolder
和onBindViewHolder
就被回调了,也就是说,咱们会预加载一个屏幕之外的Item
: spa
当咱们继续往下滑动,position=3
的Item
一被展现,那么position=4
的Item
的两个方法就会被回调。
当postion=6
的Item
被展现以后,按照前面的分析,这时候就应当回调position=7
的onCreateViewHolder
和onBindViewHolder
方法了,可是咱们发现,这时候只回调了onBindViewHolder
方法,而传入的ViewHolder
实际上是position=0
的ViewHolder
,也就是咱们所说的复用:
Items
的展示状况为:
Item
为
position=0,1,2
,因此,咱们能够得出结论:在单一布局的状况,
RecyclerView
在复用的时候,会取相反方向中超出显示范围的第
3
个
Item
来复用,而并非超出显示范围的第一个
Item
进行复用。
当咱们须要在列表当中展现不一样类型的Item
时,咱们通常须要重写下面的方法,告诉RecyclerView
在对应的position
上须要展现什么类型的Item
。
public int getItemViewType(int position)
RecyclerView
在回调onCreateViewHolder
的时候,同时也会把viewType
传递进来,咱们根据viewType
来建立不一样的布局。 下面,咱们就来演示一下它的用法,这里咱们返回三种不一样类型的item
:
public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.NormalViewHolder> {
private List<String> mTitles = new ArrayList<>();
public NormalAdapter(List<String> titles) {
mTitles = titles;
}
@Override
public NormalViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = null;
switch (viewType) {
case 0:
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_item_1, parent, false);
break;
case 1:
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_item_2, parent, false);
break;
case 2:
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_item_3, parent, false);
break;
}
NormalViewHolder viewHolder = new NormalViewHolder(itemView);
Log.d("NormalAdapter", "onCreateViewHolder, address=" + viewHolder.toString());
return viewHolder;
}
@Override
public void onBindViewHolder(NormalViewHolder holder, int position) {
Log.d("NormalAdapter", "onBindViewHolder, address=" + holder.toString() + ",position=" + position);
int viewType = getItemViewType(position);
String title = mTitles.get(position);
holder.setTitle1("title=" + title + ",viewType=" + viewType);
}
@Override
public int getItemCount() {
return mTitles.size();
}
@Override
public int getItemViewType(int position) {
return position % 3;
}
class NormalViewHolder extends RecyclerView.ViewHolder {
private TextView mTv1;
NormalViewHolder(View itemView) {
super(itemView);
mTv1 = (TextView) itemView.findViewById(R.id.tv_title_1);
}
void setTitle1(String title) {
mTv1.setText(title);
}
}
}
复制代码
最终,会获得下面的界面:
viewType
下的复用状况分析前面,咱们已经研究过一种viewType
下的复用状况,如今,咱们再来分析一下多种viewType
时候的复用状况。
此时,咱们屏幕中展现了postion=0~6
这七个Item
,onCreateViewHolder
和onBindViewHolder
的回调和以前相同,只会生成屏幕内可见的ViewHolder
这两种状况都和单个viewType
时相同,会预加载屏幕之外的一个Item
:
关键,咱们看一下什么时候会复用position=0/viewType=1
的Item
:
Item
为
position=4/viewType=1
,最下方的
Item
为
position=11/viewType=2
,按照以前的分析,
RecyclerView
会保留相反方向的
2
个
ViewHolder
,也就是保留
postion=2,3
的
ViewHolder
,并复用
position=1
的
ViewHolder
,可是如今
position=0
的
ViewHolder
的
viewType=1
,不能够复用,所以,会继续往上寻找,这时候就找到了
position=0
的
ViewHolder
进行复用。
当数据源发生变化的时候,咱们通常会经过Adatper. notifyDataSetChanged()
来进行界面的刷新,RecyclerView.Adapter
也提供了相同的方法:
public final void notifyDataSetChanged()
复制代码
除此以外,它还提供了下面几种方法,让咱们进行局部的刷新:
//position的数据变化
notifyItemChanged(int postion)
//在position的下方插入了一条数据
notifyItemInserted(int position)
//移除了position的数据
notifyItemRemoved(int postion)
//从position开始,往下n条数据发生了改变
notifyItemRangeChanged(int postion, int n)
//从position开始,插入了n条数据
notifyItemRangeInserted(int position, int n)
//从position开始,移除了n条数据
notifyItemRangeRemoved(int postion, int n)
复制代码
下面是一些简单的使用方法:
//在头部添加多个数据.
public void addItems() {
mTitles.add(0, "add Items, name=0");
mTitles.add(0, "add Items, name=1");
mNormalAdapter.notifyItemRangeInserted(0, 2);
}
//移除头部的多个数据.
public void removeItems() {
mTitles.remove(0);
mTitles.remove(0);
mNormalAdapter.notifyItemRangeRemoved(0, 2);
}
//移动数据.
public void moveItems() {
mTitles.remove(1);
mTitles.add(2, "move Items name=0");
mNormalAdapter.notifyItemMoved(1, 2);
}
复制代码
数据的更新分为两种:
Item changes
:除了Item
所对应的数据被更新外,没有其它的变化,对应notifyXXXChanged()
方法。Structural changes
:Items
在数据集中被插入、删除或者移动,对应notifyXXXInsert/Removed/Moved
方法。notifyDataSetChanged
会把当前全部的Item
和结构都视为已经失效的,所以它会让LayoutManager
从新绑定Items
,并对他们从新布局,这在咱们知道已经须要更新某个Item
的时候,实际上是没必要要的,这时候就能够选择进行局部更新来提升效率。
ViewHolder
的状态RecyclerView.Adapter
中还提供了一些回调,让咱们可以监听某个ViewHolder
的变化:
@Override
public void onViewRecycled(NormalViewHolder holder) {
Log.d("NormalAdapter", "onViewRecycled=" + holder);
super.onViewRecycled(holder);
}
@Override
public void onViewDetachedFromWindow(NormalViewHolder holder) {
Log.d("NormalAdapter", "onViewDetachedFromWindow=" + holder);
super.onViewDetachedFromWindow(holder);
}
@Override
public void onViewAttachedToWindow(NormalViewHolder holder) {
Log.d("NormalAdapter", "onViewAttachedToWindow=" + holder);
super.onViewAttachedToWindow(holder);
}
复制代码
下面,咱们就从实例来说解这几个方法的调用时机,初始时刻,咱们的界面为:
position=0~6
的onViewAttachedToWindow
被回调:
postion=7
可见时,它的onViewAttachedToWindow
被回调:
postion=0
被移出屏幕可视范围内,它的onViewDetachedFromWindow
被回调:
position=2
被移出屏幕以后,此时position=0
的onViewRecycled
被回调:
RecyclerView
最多会保留相反方向上的两个ViewHolder
,此时虽然position=1,2
不可见,可是依然须要保留它们,这时候会回收position=0
的ViewHolder
以备以后被复用。RecyclerView
和RecyclerView.Adapter
的关系RecyclerView
和Adapter
是经过setAdapter
方法来绑定的,所以在Adapter
中也经过了绑定的监听:
public void onAttachedToRecyclerView(RecyclerView recyclerView) {}
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {}
复制代码
这篇文章,主要总结了一些RecyclerView.Adapter
中平时咱们不常注意的细节问题,也经过实例了解到了关键方法的含义,最后,推荐一个Adapter
的开源库:BaseRecyclerViewAdapterHelper
。