LIstView相关问题总结
什么是ListView
ListView是一个能数据集合以动态滚动的方式展示到用户界面上的view
ListView适配器模式
ListView只是一个垂直显示的列表而已,最关心的是把view准确无误的显示到它所在的item上。
ListView和数据是分开的,不直接接触,所以说只能通过adapter这个适配器,把数据加载到ListView上;
adapter是数据源和ListView之间的桥梁,负责为每一个数据制造view并显示在ListView上;
adapter保证了数据和view的分离,这也是mvc的设计模式。
同时由于adapter的接口是统一的,ListView不用关心数据适配方面的问题。
由于adapter是一个接口,可以通过子类去实现自己的逻辑,去完成特定的功能
具体实现
这段代码简单实现一个MyAdapter,交给ListView;
在MyAdapter中最重要的是两个方法:
一个是getCount返回数据的总数;
一个是getView,为每个数据创建view,用于显示到ListView的item上;
ListView的recycleBin机制
public class ListView extends AbsListView {
...
}
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback {
class RecycleBin {
...
/**
* Views that were on screen at the start of layout. This array is populated at the start of
* layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
* Views in mActiveViews represent a contiguous range of Views, with position of the first
* view store in mFirstActivePosition.
*/
private View[] mActiveViews = new View[0];
/**
* Unsorted views that can be used by the adapter as a convert view.
*/
private ArrayList<View>[] mScrapViews;
private ArrayList<View> mCurrentScrap;
...
|
|
ListView 继承自 AbsListView, 而 AbsListView 包含一个内部类 RecycleBin;
RecycleBin 的三个重要变量:
-
mActiveViews: 存储的是活动的view,表示listview中在屏幕上可见的view,这些view是可以被直接复用的;
-
mScrapViews: 表示所有废弃类型view的list,当你的listview滑出屏幕的view就变成scrapview,所有的scrapview组成mScrapViews;
-
mCurrentScrap: 表示当前废弃的item
当item滑出屏幕时,会被回收,不在进行绘制,而被回收的view放到RecycleBin中管理
RecycleBin 中几个重要方法:
class RecycleBin {
...
public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//noinspection unchecked
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}
/**
* Fill ActiveViews with all of the children of the AbsListView.
*
* @param childCount The minimum number of views mActiveViews should hold
* @param firstActivePosition The position of the first view that will be stored in
* mActiveViews
*/
void fillActiveViews(int childCount, int firstActivePosition) {
if (mActiveViews.length < childCount) {
mActiveViews = new View[childCount];
}
mFirstActivePosition = firstActivePosition;
//noinspection MismatchedReadAndWriteOfArray
final View[] activeViews = mActiveViews;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
// Don't put header or footer views into the scrap heap
if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
// However, we will NOT place them into scrap views.
activeViews[i] = child;
// Remember the position so that setupChild() doesn't reset state.
lp.scrappedFromPosition = firstActivePosition + i;
}
}
}
/**
* Get the view corresponding to the specified position. The view will be removed from
* mActiveViews if it is found.
*
* @param position The position to look up in mActiveViews
* @return The view if it is found, null otherwise
*/
View getActiveView(int position) {
int index = position - mFirstActivePosition;
final View[] activeViews = mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
activeViews[index] = null;
return match;
}
return null;
}
/**
* Puts a view into the list of scrap views.
* <p>
* If the list data hasn't changed or the adapter has stable IDs, views
* with transient state will be preserved for later retrieval.
*
* @param scrap The view to add
* @param position The view's position within its parent
*/
void addScrapView(View scrap, int position) {
final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
// Can't recycle, but we don't know anything about the view.
// Ignore it completely.
return;
}
lp.scrappedFromPosition = position;
// Remove but don't scrap header or footer views, or views that
// should otherwise not be recycled.
final int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
// Can't recycle. If it's not a header or footer, which have
// special handling and should be ignored, then skip the scrap
// heap and we'll fully detach the view later.
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
getSkippedScrap().add(scrap);
}
return;
}
scrap.dispatchStartTemporaryDetach();
// The the accessibility state of the view may change while temporary
// detached and we do not allow detached views to fire accessibility
// events. So we are announcing that the subtree changed giving a chance
// to clients holding on to a view in this subtree to refresh it.
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
// Don't scrap views that have transient state.
final boolean scrapHasTransientState = scrap.hasTransientState();
if (scrapHasTransientState) {
if (mAdapter != null && mAdapterHasStableIds) {
// If the adapter has stable IDs, we can reuse the view for
// the same data.
if (mTransientStateViewsById == null) {
mTransientStateViewsById = new LongSparseArray<>();
}
mTransientStateViewsById.put(lp.itemId, scrap);
} else if (!mDataChanged) {
// If the data hasn't changed, we can reuse the views at
// their old positions.
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray<>();
}
mTransientStateViews.put(position, scrap);
} else {
// Otherwise, we'll have to remove the view and start over.
clearScrapForRebind(scrap);
getSkippedScrap().add(scrap);
}
} else {
clearScrapForRebind(scrap);
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
}
...
|
setViewTypeCount方法:
为listview中的每个类型的数据项建立RecycleBin机制,listview可以绑定多种类型的数据,为每一种类型都建立RecycleBin机制;
在listview中设定不同类型的item,每个item有不同的样式,对每种样式都要设定RecycleBin机制;
fillActiveViews方法:
第一个参数 childCount : 表示存储view的数量;
第二个参数 firstActivePosition : 表示listview中第一个可见元素的position值;
调用这个方法后,会根据参数,将listview当中的指定元素,存储到mActiveViews中;
getActiveView方法:
和 fillActiveViews 方法是对应的,fillActiveViews方法是填充,是获取相应的view;
首先会将传入得position转成 mActiveViews 数组对应的下标;
最重要的是拿到view之后会将 activeViews 数组对应的下标值改为null,下次获取同样位置的view,返回的将是null,也就是说屏幕上显示的 mActiveViews 是不能被重复利用的;
addScrapView方法:
将废弃的view进行缓存的重要方法;
RecycleBin就是调用这个方法,将滑出屏幕的view进行缓存的;
第一个参数 scrap : 表示将要被添加到废弃数组 mScrapViews 中的view;
总结一下 RecycleBIn 回收机制:
这张图解释了,为什么Listview当中如果存储了几万行也不会造成OOM的原因。
也就是说只有显示在屏幕中的元素1到元素5是存储在内存中的。
比如当元素0滑出屏幕时,通过addScrapView方法,将元素0缓存起来,
而当元素6将要显示到屏幕上的时候,而这个缓存的item就会通过getActiveView方法获取到元素0的位置,达到复用的效果
元素0滑出屏幕时会被保存在RecycleBin机制中,而当元素6将要进入屏幕显示时,会去复用元素0的item.
ListView的优化
convertview重用
adapter中的getView方法,有个参数是convertView:
它的作用就是缓存,只有缓存convertView为null的时候,才去创建相应的view,如果存在缓存,可以调用已有的view
但是在初次显示时,每显示一个item都会创建view,当它移出屏幕的时候convertView就不为null了。
所以convertView是listview性能优化最重要的点;
viewholder
使用ViewHolder可以避免多次的findViewById
所有的view都是树形结构,每次遍历非常耗时,利用viewholder就能减少每次遍历的耗时
三级缓存
比如图片加载的时候用到缓存机制
监听滑动事件
getView方法中少做耗时操作,这是为了保障listview滑动的流畅性
如果一定要做耗时操作可以监听滑动事件,当滑动停止的时候在去加载比如图片等
item布局中尽量避免半透明的元素
因为半透明的绘制比透明更耗时