结构
ListView的继承树是AbsListView-AdapterView-ViewGroup-View。主要可以从三个方面理解:作为一个View的行为,与Adapter的交互以及子View的回收和复用。
作为一个View
- 构造函数:
- 使用entries,构建一个使用ArrayAdapter的ListView
- Divider、OverScrollHeader、footer都是可以设置Drawable的
- measure:
- 直接使用View.measure()
- 只有在forceLayout、大小有变化、spec有变化时才会进行measure
if (forceLayout || !matchingSize &&
(widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec)) - 在measure时,只有在forceLayout或没有缓存时才调用onMeasure
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache)
- 只有在forceLayout、大小有变化、spec有变化时才会进行measure
- 直接使用View.measure()
- onMeasure:
- AbsListView:
- List的padding是设置的padding+selector的padding
- ListView:
- AbsListView.obtainView优先找transientView
- 会设置子View的layout,但不会attach到listView上
- 调用ViewGroup.getChildMeasureSpec来获取子View的measureSpec
- 在measureHeightOfChildren时,会获取每个 View并measure
- measure子View时,Height如下:
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
- measure子View时,Height如下:
- AbsListView:
- layout:
- 最终调的是View.layout,除了onLayout,通知回调之外没什么
- onLayout:
- AdapterView:
- 更新总高度
- AbsListView:
- 强制每个子View重新layout
- ListView.layoutChildren:
- 使用一个boolean值阻断layout请求,在finally中释放。由于都是主线程,相当于上锁
- invalidate最终是POST了一个重绘请求,不会立即执行
- 此时会对Item调用ViewGroup.attachViewToParent,对Header调用ViewGroup.addViewInLayout,添加到界面,均非AddView。区别
- 其中管理了focus和selection
- 使用state模式控制具体行为
- AdapterView:
- 管理子View:
- 引用存储在ViewGroup中
与Adapter交互
- 在ListView.onMeasure中会调用AbsListView.obtainView,进而调用Adapter.getView
- setAdapter:
- ListView:
- 注册Observer,notifyDataChange就是在这里注册的
- 给有Header的加个壳,所以要在setAdapter之前addHeader/Footer
- 会调一次requestLayout
- AbsListView:
- 初始化与选择有关的stableId
- ListView:
- obtainView与RecycleBin交互,其他子View的宏观状态都是Adapter维护。ViewGroup中维护的View包含Header/Footer
复用及RecycleBin代码
- 是AbsListView的内部类!GridView也是继承自AbsListView。看起来AbsListView是所有滑动切换、无相互覆盖、无特殊行为(spinner)的AdapterView的基类
- setAdapter时恢复原样
- onMeasure时从bin里取view,量完就放回去
- Detach的时候会释放bin
- Java中使用final关键字:
- 对于函数,类似于inline关键字,可以减少调用。最近不需要final也会有相应优化了
- 对于基本数据类型变量,final相对于宏定义,可以在编译过程中进行替换,加速。借鉴
- ListView Item重用时有三个机制:
- 如果有StableID,直接保存ID与View的映射,复用ID相同View
- 如果数据没变,保存位置与View的映射,复用位置相同的View
- 按ItemType复用
- 前两种需要View是setHasTransientState(true):
- 用来告诉框架试图保存View的各种State
- setHasTransientState(true)需要与setHasTransientState(false)成对调用
- 最好不要在经常变化的View上使用
- 对于每个Type的Item,最多保存当前显示Item个数个
- 对于数据变化的List,优先复用Active的View
- 每次使用完都会调用scrapActiveViews,保证回收完全。Good for object pool
ListView
- 关于SetSelection和SetScroll:SetSelection会调用requestLayout,而setScroll直接使用了父类的方法,不会重绘,会有空白
- TouchEvent一旦传到ListView,只有两种可能会传回给父节点:
- 当前ListView是Disabled,且本身不响应任何点击/长按事件
if (!isEnabled()) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return isClickable() || isLongClickable();
} - ListView正在被加到Window上
if (mIsDetaching || !isAttachedToWindow()) {
// Something isn't right.
// Since we rely on being attached to get data set change notifications,
// don't risk doing anything where we might try to resync and find things
// in a bogus state.
return false;
}
所以在这个里面的PullToRefresh是不能有连贯动作的。换言之,PullToRefresh的List一定是要重写List的。
- 当前ListView是Disabled,且本身不响应任何点击/长按事件