移动架构18_ListView源码分析
来源:互联网 发布:软件模块接口参数 编辑:程序博客网 时间:2024/06/16 00:35
一、观察者模式在ListView中的运用
观察者模式(建议先熟悉观察者模式的角色定义和调用流程)
BaseAdapter是被观察者,ListView是观察者,被观察者一般有一个存储观察接口的集合,去源码中查找BaseAdapter中的这个集合:
//集合的封装private final DataSetObservable mDataSetObservable = new DataSetObservable();//找到注册观察者的方法:public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer);}ListView是观察者的话,那么得有observer对象(DataSetObserver)用于注册,查看ListView的setAdapter方法,找到了注册观察者的地方:
public void setAdapter(ListAdapter adapter) {...mDataSetObserver = new AdapterDataSetObserver();mAdapter.registerDataSetObserver(mDataSetObserver);...AdapterDataSetObserver在ListView的父类ABSListView中被定义,类中定义了被通知后调用的onChange方法:
class AdapterDataSetObserver { @Override public void onChanged() { ... }
那么onChange方法是在哪个地方被调用的呢?我们知道ListView通过Adapter的notifyDataSetChanged方法来刷新数据,进入到这个方法:
public void notifyDataSetChanged() { mDataSetObservable.notifyChanged();}
进入到mDataSetObservable的notifyChanged方法中:public void notifyChanged() { synchronized(mObservers) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } }
这就调用了注册者的onChange方法AdapterDataSetObserver的onChange方法调用,刷新UI:
public void onChanged() {super.onChanged();if (mFastScroll != null) {mFastScroll.onSectionsChanged();}}
二、ListView中的View重用机制
ListView 具有View重用、高度扩展(ListView、GridView都设置adapter)的特点,如果我们自己去定义一个ListView该如何去做呢?
首先肯定要继承ViewGroup,重写对应的方法:onLayout、onTouchEvent、onMeasure
显示与创建分离
数据适配器 + ViewGroup
数据适配器封装了类似BaseAdapter的功能,填充自定义ListView的View从数据适配器过来的,适配器肯定提供一个getView的方法。
数据适配器 + ViewGroup
数据适配器封装了类似BaseAdapter的功能,填充自定义ListView的View从数据适配器过来的,适配器肯定提供一个getView的方法。
但是View不能直接从Adapter的getView方法直接返回(需要重用机制),需要设计一个View的池子,专门回收View(生产消费模式)。View先从池子中获取,如果没有再去Adapter的getView方法中去获取,adapter.getView是生产者,getView返回View到池子中,这样设计,ListView获取View与数据适配器就耦合了,中间通过回收池关联。
为了证明这一点,到ListView的源码中去:
1、ListView的初始化中,加载第一屏数据
ListView--AbsListView--AdapterView
1) 先查找onLayout方法,ListView 中没有,在其父类AbsListView中找到,onLayout方法主要功能:
layoutChildren();
2)layoutChildren()是空实现,子类必须要实现这个方法(因为AbsListView的子类如ListView和GridView的摆放方式不同-模板模式:onLayout算法固定,layoutChildren让子类实现)
3)进入到ListView的layoutChildren方法中:
switch(mLayoutMode) 判断模式会走到默认default中去,刚开始childrenCount == 0
sel = fillFromTop(childrenTop);//从上往下进行填充
sel = fillFromTop(childrenTop);//从上往下进行填充
4)fillFromTop:
private View fillFromTop(intnextTop) {mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);mFirstPosition = Math.min(mFirstPosition, mItemCount -1);if(mFirstPosition <0) {mFirstPosition =0;}return fillDown(mFirstPosition, nextTop);//mFirstPosition为第一个可见元素的位置,当前的可见元素为0;
5)fillDown:
加载一屏数据
while(nextTop < end && pos < mItemCount) {
...
View child = makeAndAddView(pos, nextTop,true, mListPadding.left, selected);
nextTop = child.getBottom() + mDividerHeight;
//nextTop 进行累加,如果超出屏幕则跳出循环
加载一屏数据
while(nextTop < end && pos < mItemCount) {
...
View child = makeAndAddView(pos, nextTop,true, mListPadding.left, selected);
nextTop = child.getBottom() + mDividerHeight;
//nextTop 进行累加,如果超出屏幕则跳出循环
6)makeAndAddView:
->finalView child = obtainView(position, mIsScrap);
->AbsListView:obtainView
->第一次没有从回收池中拿View,而是通过Adapter的getView方法
finalView updatedView =mAdapter.getView(position, transientView,this);
obtainView返回child到makeAndAddView中去,此时还没有添加到ListView中
finalView child = obtainView(position, mIsScrap)
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
->finalView child = obtainView(position, mIsScrap);
->AbsListView:obtainView
->第一次没有从回收池中拿View,而是通过Adapter的getView方法
finalView updatedView =mAdapter.getView(position, transientView,this);
obtainView返回child到makeAndAddView中去,此时还没有添加到ListView中
finalView child = obtainView(position, mIsScrap)
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
7)setupChild()中:
...
addViewInLayout(child, flowDown ? -1: 0, p,true);//ViewGroup中定义
将child绑定到ListView中
...
addViewInLayout(child, flowDown ? -1: 0, p,true);//ViewGroup中定义
将child绑定到ListView中
2、View的复用
1)看ListView的onLayout(adapter.notify->观察者onChange->ListView重新绘制->onLayout),
跟上面一样,还是调用了layoutChildren方法
跟上面一样,还是调用了layoutChildren方法
2)layoutChildren中:
if (dataChanged) {for(inti = 0; i < childCount; i++) {//添加ViewrecycleBin.addScrapView(getChildAt(i), firstPosition+i);}} else{recycleBin.fillActiveViews(childCount, firstPosition);}a) recycleBin->AbsListView:RecycleBin;RecycleBin中有一个集合mScrapViews用来存放被滑出屏幕的item,b)addScrapView:scrap.dispatchStartTemporaryDetach();//View中的方法,解除View和parent之间的关系,否则View是不能被重复添加的,这是因为在ViewTree中所有的View只有一个parent:c) layoutChildren继续执行:sel = fillSpecific(mSelectedPosition,oldSel ==null? childrenTop : oldSel.getTop()); ->fillSpecific ->makeAndView//第三个参数为true:finalView activeView = mRecycler.getActiveView(position);activeView 不为null,执行setUpChild方法
3) setUpChild:
//将View和Parent重新关联起来
attachViewToParent(child, flowDown ? -1: 0, p);
综合一和二,第一次使用addViewParent(添加),复用时使用attachViewToParent(绑定,性能高);
attachViewToParent(child, flowDown ? -1: 0, p);
综合一和二,第一次使用addViewParent(添加),复用时使用attachViewToParent(绑定,性能高);
三、滑动过程中的实现
1、onTouchEvent:(AbsListView中实现),找Move事件
->onTouchEvent:switch(mTouchMode) {走TOUCH_MODE_SCROLL
scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
2、scrollIfNeeded:
int incrementalDeltaY =
mLastY!= Integer.MIN_VALUE? y - mLastY+ scrollConsumedCorrection : deltaY;
//判断向上还是向下滑
->trackMotionScroll
3、trackMotionScroll:
确定边界
final int firstTop = getChildAt(0).getTop();final int lastBottom = getChildAt(childCount - 1).getBottom();...final boolean down = incrementalDeltaY < 0;//表示向下滑..if(down){//向下for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getBottom() >= top) { break; } else {//child.getBottom()< top-->当前child滑出了 count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { child.clearAccessibilityFocus();//添加到回收池 mRecycler.addScrapView(child, position); } } }ele{//向上滑动int bottom = getHeight() - incrementalDeltaY;if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { bottom -= listPadding.bottom;}for (int i = childCount - 1; i >= 0; i--) { final View child = getChildAt(i); if (child.getTop() <= bottom) { break; } else {//child.getTop() > bottom start = i; count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. child.clearAccessibilityFocus(); mRecycler.addScrapView(child, position); } }}}if (count > 0) {//解绑 detachViewsFromParent(start, count); mRecycler.removeSkippedScrap();}...fillGap(down);4.fillGap:ListView中实现(模板模式)
->fillDown
->makeAndAddView(调用adapter.getView的),第三个参数为true
->setupChild
从而将View添加或关联到ListView中去
...
attachViewToParent(child, flowDown ? -1: 0, p);
...
else{
...
addViewInLayout(child, flowDown ? -1 : 0, p, true);
...
else{
...
addViewInLayout(child, flowDown ? -1 : 0, p, true);
阅读全文
0 0
- 移动架构18_ListView源码分析
- 移动架构21_动画框架源码分析
- 18_ListView实现分组效果
- live555 源码架构分析
- Tomcat源码分析 -- 架构
- tomcat架构及源码分析
- OpenNebula架构分析(源码)
- libvirt架构及源码分析
- tomcat源码分析之一《架构》
- Docker源码分析:Docker架构
- libvirt架构及源码分析
- libvirt架构及源码分析
- Tomcat源码分析--总体架构
- Hessian源码分析--总体架构
- SpringMVC架构及源码分析
- Mongodb 源码分析:整体架构
- Flume架构与源码分析-整体架构
- 主流移动开发平台架构分析
- Ubuntu 14.04 安装 chkconfig 服务管理程序
- HTML中的a标签实现点击下载
- 习题-1
- 学习过程
- 第4周项目5(2) 循环双链表应用
- 移动架构18_ListView源码分析
- hihoCoder 1054:滑动解锁(DFS)
- java IO笔记(DataInputStream/DataOutputStream)
- 双重检查模式 (DCL)与单例模式
- python3[爬虫实战] 使用selenium,xpath爬取京东手机(下)
- JQuery this和$(this)的区别及获取$(this)子元素对象的方法
- bootstrap学习笔记心得1
- 如何防止主机被ping
- GCC Command Options