ListView源码

来源:互联网 发布:star走心机编程 编辑:程序博客网 时间:2024/06/02 17:43

结构

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)
  • 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);
          }
  • 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模式控制具体行为
  • 管理子View:
    • 引用存储在ViewGroup中

与Adapter交互

  • 在ListView.onMeasure中会调用AbsListView.obtainView,进而调用Adapter.getView
  • setAdapter:
    • ListView:
      • 注册Observer,notifyDataChange就是在这里注册的
      • 给有Header的加个壳,所以要在setAdapter之前addHeader/Footer
      • 会调一次requestLayout
    • AbsListView:
      • 初始化与选择有关的stableId
  • 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的。
0 0
原创粉丝点击