标签:不同的 显示 含义 方向 hang 工作 例子 加载 roi
当我们使用RecyclerView
时,第一件事就是要继承于RecyclerView.Adapter
,实现其中的抽象方法,来处理数据的展示逻辑,今天,我们就来介绍一下Adapter
中的相关方法。
我们从一个简单的线性列表布局开始,介绍RecyclerView.Adapter
的基础用法。
首先,需要导入远程依赖包:
compile‘com.android.support:recyclerview-v7:25.3.1‘
接着,继承于RecyclerView.Adapter
来实现自定义的NormalAdapter
:
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
时,至少要做四个工作:
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
的操作,这个我们后面再分析。
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
下的复用情况分析下面,我们来分析一下两个关键方法的调用时机:
onCreateViewHolder
onBindViewHolder
通过这两个方法回调的时机,我们可以对RecyclerView
复用的机制有一个大概的了解。
刚开始进入界面的时候,我们只展示了3
个Item
,此时这两个方法的调用情况如下,可以看到,RecyclerView
只实例化了屏幕内可见的ViewHolder
,并且onBindViewHolder
是在对应的onCreateViewHolder
调用完后立即调用的:
当我们手指触摸到屏幕,并开始向下滑动,我们会发现,虽然position=3
的Item
还没有展示出来,但是这时候它的onCreateViewHolder
和onBindViewHolder
就被回调了,也就是说,我们会预加载一个屏幕以外的Item
:
当我们继续往下滑动,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
。
RecyclerView 知识梳理(2) - Adapter
标签:不同的 显示 含义 方向 hang 工作 例子 加载 roi
原文地址:http://www.cnblogs.com/youseiraws/p/7071147.html