AndroidListView重难点解析
⼀、基本使⽤
我们先来看看 ListView 的 Adapter ⼀般是怎么写的:
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {
super(context, textViewResourceId, objects);
resourceId = textViewResourceId;
}
/*  由系统调⽤,获取⼀个View对象,作为ListView的条⽬,屏幕上能显⽰多少个条⽬,getView⽅法就会被调⽤多少次
*  position:代表该条⽬在整个ListView中所处的位置,从0开始
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//重写适配器的getItem()⽅法
Fruit fruit = getItem(position);
View view;
ViewHolder viewHolder;
if (convertView == null) { //若没有缓存布局,则加载
//⾸先获取布局填充器,然后使⽤布局填充器填充布局⽂件
view = LayoutInflater.from(getContext()).inflate(resourceId, null);
viewHolder = new ViewHolder();
/
/存储⼦项布局中⼦控件对象
viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
// 将内部类对象存储到View对象中
view.setTag(viewHolder);
} else { //若有缓存布局,则直接⽤缓存(利⽤的是缓存的布局,利⽤的不是缓存布局中的数据)
view = convertView;
viewHolder = (ViewHolder) Tag();
}
viewHolder.fruitImage.ImageId());
viewHolder.fruitName.Name());
return view;
}
//内部类,⽤于存储ListView⼦项布局中的控件对象
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
}
如上所⽰,在实现 Adapter 的时候,我们⼀般会加上 ViewHolder 这个东西。ViewHolder 和复⽤机制的原理是⽆关的,他的主要⽬的是持有 Item 中控件的引⽤,从⽽减少 findViewById() 的次数,因为 findViewById() ⽅法也是会影响效率的,因此在复⽤的时候他起的作⽤是这个,减少⽅法执⾏次数增加效率。
点击事件代码:
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Fruit fruit = (position);
// 加⼊逻辑代码
}
});
ListView 对象使⽤ setAdapter(ListAdapter la) ⽅法给 ListView 控件设置适配器。
⼆、ListView 的缓存机制
ListView 的缓存主要是在 ListView 的内部类 RecycleBin 中,它包含了两级缓存(Active View、Scrap View):
Active View、Scrap View 分别是什么呢,我们继续看下⾯这张图:
可以看到,Active View 其实就是在屏幕上可见的视图,也是与⽤户进⾏交互的 View,那么这些 View 会通过 RecycleBin 直接存储到 mActiveView 数组当中,以便为了直接复⽤。
那么当我们滑动 ListView 的时候,有些 View 被滑动到屏幕之外 ,那么这些 View 就成为了 Scrap View,也就是废弃的 View,已经⽆法与⽤户进⾏交互了,这样在 UI 视图改变的时候就没有绘制这些⽆⽤视图的必要了。它将会被 RecycleBin 存储到 mScrapView 数组当中,⽬的是为了⼆次复⽤,也就是间接复⽤。
当新的 View 需要显⽰的时候,先判断 mActiveView 中是否存在,如果存在那么我们就可以从 mActiveView 数组当中直接取出复⽤,也就是直接复⽤,否则的话从 mScrapView 数组当中进⾏判断,如果存在则间接复⽤当前的视图然后调⽤ getView ⽅法,如果不存在,那么就需要创建新的 View 了。
它们都是在 RecycleBin 类中的:
class RecycleBin {
private RecyclerListener mRecyclerListener;
//第⼀个可见的View存储的位置
private int mFirstActivePosition;
//可见的View数组
private View[] mActiveViews = new View[0];
//不可见的的View数组,是⼀个集合数组,每⼀种type的item都有⼀个集合来缓存
private ArrayList<View>[] mScrapViews;
//View的Type的数量
private int mViewTypeCount;
//viewType为1的集合或者说mScrapViews的第⼀个元素
private ArrayList<View> mCurrentScrap;
}
可以看到 mScrapViews 是⼀个 ArrayList 的数组,不知道⼤家有没有这样的⼀个疑惑,负责缓存的 mScrapViews 数组的容量是谁来确定的?当我们需要 ListView ⽀持多类型复⽤时,往往要覆盖这两个⽅法:
getViewTypeCount() 就决定了 mScrapViews 数组的长度
getItemViewType() 就决定了相同类型的 View 投放到哪个坐标下。这句话的意思就是相同类型的 View 需要返回相同的值,并且它的值必须是从 0 开始依次递增的。
当我们使⽤同类型加载数据的ListView时,这两个⽅法我们不必去理会。
ListView 将 Item 显⽰出来的核⼼部分也就是 makeAndAddView() ⽅法,这个部分涉及到了 ListView 的复⽤:
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
//判断数据源是否发⽣了变化.
if (!mDataChanged) {
// Try to use an exsiting view for this position
//如果mActivityView[]数组中存在可以直接复⽤的View,那么直接获取,然后重新布局.
child = ActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
// Make a new view for this position, or convert an unused view if possible
/**
*如果mActivityView[]数组中没有可⽤的View,那么尝试从mScrapView数组中读取.然后重新布局.
*如果可以从mScrapView数组中可以获取到,那么直接返回调⽤View(position,scrapView,this);
*如果获取不到那么执⾏View(position,null,this)⽅法.
*/
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
这⾥可以看到如果数据源没有变化的时候,会从 mActiveView 数组中判断是否存在可以直接复⽤的 View,可能很多读者都不太明⽩直接复⽤到底是怎么个过程,举个例⼦,⽐如说我们 ListView ⼀页可以
显⽰ 10 条数据,那么我们在这个时候滑动⼀个 Item 的距离,也就是说把 position = 0 的 Item 移除屏幕,将 position = 10 的 Item 移⼊屏幕,那么 position = 1 的 Item 是不是就直接能够从mActiveView 数组中拿到呢?这是可以的,我们在第⼀次加载 Item 数据的时候,已经将 position = 0~9 的 Item 加⼊到了mActiveView 数组当中,那么在第⼆次加载的时候,由于 position = 1 的 Item 还是 ActiveView,那么这⾥就可以直接从数组中获取。这⾥也就表⽰的是 Item 的直接复⽤。
接下来跟进去看看 obtainView ⽅法:
View obtainView(int position, boolean[] isScrap) {
isScrap[0] = false;
View scrapView;
// 根据position调⽤getItemViewType(position)⽅法可以获得View在缓存池的位置
scrapView = ScrapView(position);
View child;
if (scrapView != null) {
// 如果不为null,我们就可以利⽤convertView进⾏复⽤操作
child = View(position, scrapView, this);
if (child != scrapView) {
// 如果返回的View和我们从缓存池中拿出的View不同,则把它重新存进去
mRecycler.addScrapView(scrapView);
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
} else {
isScrap[0] = true;
dispatchFinishTemporaryDetach(child);
}
} else {
// 当缓存池中没有时,传递convertView为null
child = View(position, null, this);
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
}
return child;
}
这也是 ListView 中最核⼼的⽅法,⽤来获取 scrap view 缓存,我将难点都⽤注释标注了应该很好理解。
三、总结
listview控件在哪里与 RecyclerView 缓存 RecyclerView.ViewHolder 不同,ListView 缓存的是 View。
是否需要回调createView 是否需要回调
bindView
⽣命周期备注
mActiveViews否否onLayout函数周期内⽤于屏幕内ItemView快速重⽤。容量为⼀屏能展⽰的ItemView的个数
mScrapViews否是与mAdapter⼀致,当mAdapter被
更换时它被清空
容量不限

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。