为了节约你的时间,上面的图是整个文章的最终图,包括了以下部分问题的答案。
目录
1.为什么需要使用Adapter?
RecycleBin是什么?
2.ListView的绘制过程是怎样的?
3.ListView的回收机制是怎么样的?为什么可以显示100个View而不崩呢?
4.ListView里面的观察者模式是什么?
5.惯性滑动(fling)是怎么实现的?
获取ListView视图对象,设置adapter。
ListView lv = new ListView(context);
lv.setAdapter(new SimpleAdapter);
ListView集成自absListView,同级的还有GridView。实际上我们可以发现,ListView是一个ViewGroup,提供把每个子View按照纵向布局在一起,然后提供滑动和fling功能。
从下面的图我们可以看到,左边是ListView,右边是data,中间是adapter,在这里ListView并不关心数据(data),是从数据库的cursor获取还是是从数组里面获取,我们只需要通过适配器Adapter统一一个协议就行。
试想一下不管是GridView还是ListView又或者是RecyclerView,无一不是需要知道总共几个子View,获取每个子View的视图,如果有多种类型的子View,还需要ItemViewType。所以我们其实就可以定义几个方法来获取这几个值,你看getView(获取子视图)、getCount(总共有几个子视图)、getItemViewType(每个子视图的type),这几个方法熟悉吗??我们实现这几个接口就能用adapter适配数据了。
那么有同学说,既然adapter隔离了数据,那么数据变动刷新ListView怎么操作呢?
你看1.1 register先注册观察者、1.2 notifyDataSetChanged 1.3 notifyChanged 不是典型的观察者模式吗? 这样就实现了adatper通知ListView刷新的功能了。
RecycleBin是用于ListView子元素的缓存。里面有一个View[] mActiviesViews和ArrayList
图片左边是RecycleBin,右边是ListView,ListView中的所有元素,都是存在mActiviesViews里面一份的,所以当ListView刷新时,可以很快速的从内存中取出View来布局。当ListView上滑并且元素0完全移出屏幕时,会回收到RecycleBin的mScrapViews里面的ItemType类型为索引的ArrayList里面。
ListView也是ViewGroup,所以它的流程也是measure、layout、draw。ViewGroup的measure里面其实就是调用子布局的mesure,没什么特殊的。draw的话,其实也是调用子布局的绘制方法。所以我们分析layout过程,layout过程分为2种情况。
看图!!!
当我们向上滑动时,ListView的onTouchEvent中的move判断条件调用,判断元素0的getBottom()小于滑动距离,也就是元素0即将移出屏幕,这个时候ListView认为元素0没有用了,然后就把元素0放入RecycleBin的mScrapViews里面。然后判断元素6要进入屏幕,就从RecycleBin的mScrapViews的mScrapViews里面取出元素0刚刚的视图元素,作为元素6布局出来。
所以我们能够得出结论:
试想一下,我们刷新ListView是做的?没有直接调用ListView的方法吧,而是调用adapter.notifyDataSetChanged,那么adapter又是为什么能够刷新ListView的呢?
想想一下,如果adapter是被观察对象,它的数据变动回去通知观察者,那么只要把ListView作为adapter的观察者不就行了吗?
看图!!!
实际上可以看到adapter里面有一个dataSetObservable,然后第一步ListView在adapter上面注册一个回调,之后数据变化的时候,adapter调用notifyDataSetChanged,实际上就会调用回调的notifyChanged方法,然后就实现了adapter数据变化通知ListView刷新的功能。下面我们来具体分析下代码是怎么实现的?
首先我们从mDataSetObserver对象开始看,可以看到它是在absListView在onAttachedToWindow中注册的,我们看下mDataSetObserver对象的代码。
protected void onAttachedToWindow() {
...
if (mAdapter != null && mDataSetObserver == null) {
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
...
}
}
可以看到他是调用他父类AdapterView
class AdapterDataSetObserver extends AdapterView.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
...
}
...
}
可以看到,最后是调用ListView的requestLayout(),那么requestLayout又是怎么实现刷新的呢?
@Override
public void onChanged() {
...
requestLayout();
}
我们看到requestLayout里面去调用mParent的requestLayout,那么父布局又会去查找他的mParent,然后执行requestLayout ,最终调用到DecorView的requestLayout。根布局为什么叫DecorView,我们在5分钟告诉你,Activity的视图绘制流程(onMeasure、onLayout、onDraw的调用和参数解释)讲过。在这个过程中,请注意第6、7行代码, 我们把关键字设置为PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED,这是为了测量和布局的时候不走缓存。然后DecorView的mParent又是什么呢?其实是ViewRootImpl。
这篇文章关于View中mParent的来龙去脉介绍了
所以就其实requestLayout最后就成了调用ViewRootImpl的requestLayout方法。我们去看看这个方法。
public void requestLayout() {
//清除测量缓存
if (mMeasureCache != null) mMeasureCache.clear();
...省略
//修改关键字
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
//很重要。。
mParent.requestLayout();
}
}
可以看到在 5分钟告诉你,Activity的视图绘制流程(onMeasure、onLayout、onDraw的调用和参数解释中,第6行ViewRootImpl.scheduleTraversals()实际会调用performTraversals,然后调用performMeasure、performLayout、performDraw,然后执行刷新操作。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
我们思考一下惯性滑动是怎么操作的?我们手指先按住ListView,然后快速的滑动,然后手指离开ListView,这个时候ListView就会自动滑动,直到停止或者触碰到边界,触发边界的水波纹效果。所以我们可以看到,其实是在手指抬起时触发fling操作的,那么我们就去onTouchEvent里面的Action_UP看一下。
可以看到事件处理里面处理了ACTION_UP和ACTION_POINTER_UP,ACTION_POINTER_UP是第二个手指的处理,这里我们只分析ACTION_UP一个手指的情况。再跟进onTouchUp(ev)看下。
public boolean onTouchEvent(MotionEvent ev) {
....省略
switch (actionMasked) {
....
case MotionEvent.ACTION_UP: {
onTouchUp(ev);
break;
}
case MotionEvent.ACTION_POINTER_UP: {
onSecondaryPointerUp(ev);
....
break;
}
}
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(vtev);
}
return true;
}
我们可以看到代码比较多,我们简单分析下。
private void onTouchUp(MotionEvent ev) {
switch (mTouchMode) {
....
case TOUCH_MODE_SCROLL:
if (childCount > 0) {
....
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
final int initialVelocity = (int)
(velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
//是否满足惯性滑动的速度并且没有滑动边界上
boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
if (flingVelocity &&
!((mFirstPosition == 0 &&
firstChildTop == contentTop - mOverscrollDistance) ||
(mFirstPosition + childCount == mItemCount &&
lastChildBottom == contentBottom + mOverscrollDistance))) {
//不分发嵌套fling
if (!dispatchNestedPreFling(0, -initialVelocity)) {
//很关键
if (mFlingRunnable == null) {
mFlingRunnable = new FlingRunnable();
}
reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
mFlingRunnable.start(-initialVelocity);
dispatchNestedFling(0, -initialVelocity, true);
} else {
mTouchMode = TOUCH_MODE_REST;
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
}
} else {
//已经在边界上了,直接当做重置状态
mTouchMode = TOUCH_MODE_REST;
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
if (mFlingRunnable != null) {
mFlingRunnable.endFling();
}
if (mPositionScroller != null) {
mPositionScroller.stop();
}
if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
dispatchNestedFling(0, -initialVelocity, false);
}
}
}
} else {
//没有子元素了,设置到Reset状态
mTouchMode = TOUCH_MODE_REST;
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
}
break;
case TOUCH_MODE_OVERSCROLL:
.....
//速度大于最小速度
if (Math.abs(initialVelocity) > mMinimumVelocity) {
mFlingRunnable.startOverfling(-initialVelocity);
} else {
mFlingRunnable.startSpringback();
}
break;
}
第7行到 13行,主要是判断速度有没有达到惯性滑动的速度。
!((mFirstPosition == 0 && firstChildTop == contentTop - mOverscrollDistance) ||
(mFirstPosition + childCount == mItemCount && lastChildBottom == contentBottom + mOverscrollDistance))
第14行到17行,其实是惯性滑动的条件,我们可以看到第一个条件 mFirstPosition==0代表现在ListView处于第一个位置,firstChildTop是第一个元素顶部距离ListView顶部的距离,那么contentTop又是mListPadding.top,那么mListPadding是什么?
final int firstChildTop = getChildAt(0).getTop(); //第一个元素顶部距离屏幕顶部的距离
final int contentTop = mListPadding.top; //0
mFirstPosition == 0 && firstChildTop == contentTop - mOverscrollDistance
可以看到是在onMeasure给mListPadding赋值的,跟到最后发现结果为0,所以可以看到就是判断ListView是否滑到顶了。同样,下面的条件是判断是否滑到底部了。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
.....
final Rect listPadding = mListPadding;
listPadding.left = mSelectionLeftPadding + mPaddingLeft;
listPadding.top = mSelectionTopPadding + mPaddingTop;
listPadding.right = mSelectionRightPadding + mPaddingRight;
listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
.....
}
22行到25行,是在不嵌套fling的情况下,直接调用mFlingRunnable去执行。我们看下mFlingRunnable.start(-initialVelocity)代码。
void start(int initialVelocity) {
int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
mLastFlingY = initialY;
mScroller.setInterpolator(null);
mScroller.fling(0, initialY, 0, initialVelocity,
0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
mTouchMode = TOUCH_MODE_FLING;
mSuppressIdleStateChangeCall = false;
postOnAnimation(this);
.......
}
可以看到第5行,mScroller.fling方法,mScroller又是什么类型呢?可以看到是OverScroller类型,它是Scroller的高级版,可以支持边界的绘制。所以说惯性滑动,其实就是OverScroller的fling方法来实现的。
从源码角度分析Activity的生命周期怎么触发的(onCreate onStart onResume onPause onStop onDestroy)(附测试代码)
基于AIDL的 Activity、Service跨进程观察者模式实现与源码解读
走进源码,Android面试最常见Handler、Looper、Message问题总结与解答
Android面试---ListView原理及fling分析
5分钟告诉你,Activity的视图绘制流程(onMeasure、onLayout、onDraw的调用和参数解释)
参考:
关于View中mParent的来龙去脉
Android ListView工作原理完全解析,带你从源码的角度彻底理解