相信大家在开发过程中,肯定用过ListView,那么关于ListView的面试题有哪些呢,我们一起来看看。
一、什么是ListView
当面试官问你什么是ListView,你的脑海中是不是只有图像没有语言,所以给大家讲一下什么是ListView。
ListView就是可以把数据集合以滚动的方式展示到屏幕上的一个view。
两个关键点:一个数据集合,一个滚动的方式。
二、 ListView适配器模式
提到ListView,大家肯定会想到Adapter,Adapter叫做适配器,它会给每一条数据源制作一个View,然后交由ListView来展示,所以适配器就像是数据源和ListView之间的桥梁一样,把两者联系在一起。
三、ListView中的RecycleBin机制
那大家有没有想过为什么ListView就可以实现滑动的效果呢?先给大家放一张图。
上面这张图就很好的解释了为什么我们的ListView可以实现滑动的效果。原来我们的ListView中的每一个元素就是一个Item,当这个Item滑出屏幕的时候,它会被添加到一个叫RecycleBin的东西里面,而新进入屏幕的元素则会从RecycleBin里面取出一个Item然后装载数据显示到ListView上,这样就实现了一个循环,只要有数据源就可以不停的往RecycleBin中加入Item,取出Item。
那说了这么久的RecycleBin,它到底是个啥嘞?
点开它的源码我们可以看到,它当中有很多的数组变量,我们当然不会关注那么多,大家根据上面的图想一想,我们是不是只要知道三个变量就行了,第一个就是当前ListView中能看到的所有元素,第二个就是所有已经被加入到RecycleBin中的元素,第三个就是当前正要准备加入到RecycleBin中的元素。
private View[] mActiveViews = new View[0]; //存储的是活动的Views,即显示在屏幕上的Views,可以被直接复用
private ArrayList<View>[] mScrapViews;//二维数组,表示所有被废弃类型的View的集合
private ArrayList<View> mCurrentScrap;//表示当前被废弃的View的集合
当然了,ListView中的数据有很多种类型,比如纯文字的,文字加一张图的,文字加两张图的等等,每一种数据源只能复用相同的Item,否则就会出现排版错乱。所以RecycleBin通过下面这个方法为我们每一种数据类型建立了一个RecycleBin机制,让后面的元素选择。也就是说RecycleBin只有一个,但是它里面有很多的集合,每一个集合就是一个数据类型。
//为ListView中的每一个类型的数据项建立一个recycleBin机制
//默认为1,只有一种数据类型
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>(); //创建viewTypeCount个集合
}
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}
滑出屏幕的Item通过下面这个方法加入到RecycleBin中:
//把当前要废弃的View添加到ScrapView数组当中
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;
}
新滑入的Item想要显示就要从RecycleBin中去取。
/**
* @param childCount 要存储的View的数量
* @param firstActivePosition ListView中第一个可见元素的position值
*/
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;
}
}
}
/**
和fillActiveViews配套使用,用于获取屏幕上显示的View
* @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]; //将可见元素的position值作为可见view集合的角标使用
activeViews[index] = null; //设为null,所以屏幕上的View不能被重复利用
return match;
}
return null;
}
通过这两个方法,就可以让新滑入的元素显示到屏幕上。
好了,关于ListView的源码就分析到这里了,主要就是三个变量,一个是当前屏幕上显示的所有元素的集合,一个就是所以已经被废弃的元素的集合,还要一个就是当前正要废弃的被废弃的元素。然后就是把要废弃的元素加入到RecycleBin中,新滑入的元素从RecycleBin中取出并显示到ListView中。但是有一点要注意就是,只有被加入到RecycleBin中的Item才可以复用,正在屏幕上显示的Item是不可以复用的。
四、ListView的优化
1、Item显示优化
我们都知道ListView每一次要显示一个新元素的时候都要销毁旧元素,再创建新元素,频繁的销毁创建会导致ListView卡顿,也会让整个界面显得不流畅,所以针对ListView,我们可以进行一些优化,来让它不那么频繁的销毁创建,而是复用之前的Item。
使用convertView和ViewHolder来配合。
每当有view移出界面的时候,它就会变成convertView,所以,在调用getView()方法时,我们先判断一下convertView是否为空,如果为空的话,我们就新创建一个布局和viewHolder,并使用viewHolder来绑定convertView中的控件,并且把viewHolder保存在convertView中。如果convertView不为空,那么就直接取出其中保存的viewHolder,再进行其他的操作。viewHolder是一个内部类,它里面保存了ListView中所有控件的信息,减少了findViewById的次数。
这样我们就完成了item显示的优化。我们知道ListView中很多时候都要显示图片,所以针对图片,我们也可以进行一些优化。
2、图片显示优化
可以用三级缓存来展示图片;在geiView方法中做耗时操作会导致界面的卡顿,所以我们可以为ListView设置一个滑动监听,界面滑动的时候我们不去加载图片,界面停止的时候我们才去加载图片。
3、硬件加速
开启硬件加速,也可以提高ListView展示的效率。
另外还有一些其他的方法,比如设置半透明元素等等,这里就不多说了,主要的方法就是上面第一点和第二点。
总结
好了,总结一下吧,ListView就是一个可以将数据集合以滚动的方式展示到屏幕上的view。每一个Item都是通过Adapter来创建View的,Adapter就像是数据源和ListView之间的桥梁。它实现滚动的方式,是因为它的内部有一个RecycleBin,可以存放滑出屏幕的元素的Item,需要显示的元素从RecycleBin中取出Item并加载自己的数据来让ListView显示。针对ListView我们还做了很多的优化来提高性能,比如复用convertView和内部类viewHolder,比如图片的三级缓存,比如设置滑动监听等等。
好了,继续学习喽~~~