1. 为了在手势操作的阈值判断上和系统保持一致,在init()的时候据根据传入的context取得了ViewConfiguration:
• final ViewConfiguration configuration = ViewConfiguration.get(getContext()), 这个函数就是根据context对应的display的dimension/density(当前的实现中是density)获得在ViewConfiguration的静态cache Configurations中保存的ViewConfiguration对象, 然后将得到的ViewConfiguration的getScaledTouchSlop()作为自己的mTouchSlop,做为自己的getScaledMaximumFlingVelocity().
• getScaledTouchSlop(): Distance in pixels a touch can wander before we think the user is scrolling. 判断是否是一次scroll.
• getScaledMaximumFlingVelocity(): Maximum velocity to initiate a fling, as measured in pixels per second 判断是否是一次fling.
2. 在回收View的时候,会将其从Parent上detach(detachViewFromParent(v)), 这种应用应该是参考了AbsListView的回收View时的处理, 而detach这个操作主要干的就是两件事:
• 这个View的parent设为null
• 将这个View从Parent的child view数组中remove.
• 可以看到这两个操作如同注释所说是轻量的(light-weight), 被detach的View就像一个隐形人,一定不会被Parent在meausre/layout中被consider(因为已经不是child了), 但是呢,其本身还是存在的, 还可以attach(attachViewToParent(..))或者彻底被删除(removeDetachedView(…)), 如果一个View要被回收使用,那么显然是需要进行Detach的:不会影响parent的布局和显示,但是呢,这个对象还是存在的可以被重用, 注释说一个View被detach以后,在一个draw周期内,必须后面接着attach或者removeDetached, 这个原因是如果后面没有这些操作,这个View就等于被遗忘了(可能就没有引用指向了),会被回收,但是View本身还需要做一些清理和callback操作才能彻底被回收(也就是removeDetachedView(…)函数所做的),而这里因为被recycle了,会被recycler保存起来,所以不必遵守注释所说的
• removeDetachedView(…)做的操作:
• 这一步本质上和removeViewInternal差不多,但是因为之前已经detach了,所以有些差异
• LayoutTransition mTransition这个对象是为了实现android系统自带的layoutchange动画效果, 如果有的话,会执行removeChild(this, child).
• 如果View本身有动画效果的话,那么这个View会被加入到一个addDisappearingView()中以实现渐变消失.否则直接调用View的dispatchDetachedFromWindow(): VISIBLITY设为GONE,回调onDetachedFromWindow以及其他的工作
• 然后处理一下View的TransientState
• 最后回调onViewRemoved来调用mOnHierarchyChangeListener的onViewRemoved.
• 从上面看,一个View被完全从parent干掉的时候回调的是onDetachedFromWindow, 但是被detachFromParent的时候不会回调这个,因为还存在, attach/add的区别,以及detach/remove的区别也大概有一个概念: attach/detach更像一种暂态, 而add/remove则是一个完成的到达终点的过程
• detach/attach 默认都不会触发重新的layout/draw, 因此需要调用者自己主动触发.
3. ViewFlow的展示原理很简单,就是在当前显示的VIew的前后再增加siderBuffer个规定的View,通过scroll来实现View的切换,其在init的时候,就初始化了一个Scroller.
• computeScroll()这个函数就被Override了,这个函数的作用是在View的draw()里面被调用,一般自定的会在这个函数里改变View的mScrollX/Y来达到View展示移动的目的
• ViewFlow中的这个函数的实现是:
• 首先调用了mScroller的computeScrollOffset(),这个函数会取得当前View应该scroll的X/Y, 并且只有在scroll过程没有完的时候才会返回true, 如果是true,就会调用View的scrollTo(…)来修改mScrollX/Y(mScrollX/Y也是可以直接修改的,不过ScrollTo还做了一些别的操作), 然后会postInvalidate()(其实直接invalidte()应该也可在,反正是在View的UI线程上, 并且感觉这个postInvalidate()可能都没有必要调用,因为ScrollTo也是会触发invalidate的,虽然是通过scrollBar->postInvalidateOnAnimation()这种更为优先的方式)
• 否则如果Scroller表明当前没有正在scroll, 检测mNextScreen是不是INVALID_SCREEN,如果还不是,那么改变一下mCurrentScreen, 然后将mNextScreen设置为INVALID_SCREEN, 然后post一个postViewSwitched(mLastScrollDirection).
4. mLoadedViews基本指的就是当前作为ViewFlow的child存在的View集合. 而mRecycledViews很直白了,就是被从parent 中detach保存在这个数组中的View.
5. onMeasure(int widthMeasureSpec, int heightMeasureSpec): 作用不解释了,重点是过程:
• 首先获取parent给予的size和mode.
• 然后获取ViewFlow的padding.
• 然后检查mAdapter是否有效以及是否确实提供了View. 如果提供了View,那么就需要得到View的尺寸了, 因为每个View都是一样的尺寸,所以直接取第一个就可以,然后使用Parent传入的MeasureSpec对这个ChildView进行measure(measureChild(…)),在measure完以后,会取得child的measuredWidth/Height/State(State其实就是size+mode), 然后将这个Child加入到mRecycledViews中, 得到了ChildView的size,才能结合padding确定ViewFlow自己的size.
• 然后就是根据Mode来进行处理了:
• MeasureSpec.UNSPECIFIED: 没规定,那就按最大的来, padding(已经×2了) + child的size
• MeasureSpec.AT_MOST: 想多大就多大,那么会在上面的基础上附加childView的MeasureSpecMode
• MeasureSpec.EXACTLY: 如果发现给的size小于padding+child的size,那么会使用给的size但是会打上MEASURED_STATE_TOO_SMALL这个mode flag。
6. measureChild(…):
• 得到child自己的LayoutParams,然后结合传入的parentWidt/HeighthMeasureSpec 调用getChildMeasureSpec(…)(ViewGroup实现, 很复杂, The goal is to combine information from our MeasureSpec with the, LayoutParams of the child to get the best possible results.)得到一个合适的Width/HeightSpec并将其measure到child上.
7. onLayout函数就很简单了:
• 遍历当前的所有childView, layout的X的坐标的起点是getPaddingLeft() + getHorizontalFadingEdgeLength()(Returns the size of the horizontal faded edges used to indicate that more content in this view is visible.), 依次水平递增Layout下去即可, Y坐标也会考虑到padding.
8. recycleViews()和recycleView(View v):
• 前者将mLoadedViews中的View全部通过recycleView(View v)进行recycle.
9. obtainView(int position)的逻辑也是参考了AbsLIstView的做法,在传给Adapter的RecycledView没有被用到时,就会将RecycledView重新放入到Recycler中, 并且对于new的View会为其设置FILL_PARENT的LayoutParams.
10. setupChild(…)同样参考了AbsLIstView的做法, 会根据child的width/height对child进行一次meausre, 然后如果child是recycle的,那么就attach,如果是new的,那么addViewInLayout(…), 两者都不会触发layout, 一般后面会有一次兜底的requestLayout来确保这些改动生效
11. ViewFlow比ViewPager好的地方就在于它支持的View的数量是可变(更确切的是一种基于流的方式,不断的有新的View生成)的,不需要通过改变Adapter的方式来实现,而是自己内部定义的AdapterDataSetObserver extends DataSetObserver来实现对这个功能的支持:
• 主要实现就是在AdapterDataSetObserver的onChange()里,这是表明Adapter所对应的Data发生了变化,那么View的内容和数量也需要更新, 获得的当前显示给用户的那个View在Adapter中的index值并保存在mCurrentAdapterIndex,然后调用resetFocus().
• resetFocus()函数会首先将当前所有的LoadedView中的View全部回收,然后将childView全部remove(removeAllViewsInLayout())注意这里虽然将Child都remove了,但是他们还都存在,保存在Recycler中, 其实被detach的View就已经不会受removeAllViewsInLayout()的影响了,
• 将LazyInit的所有enum都加入到mLazyInit这个EnumSet中.
• 然后以之前保存的mCurrentAdapterIndex为中心,将左右的sideBuffer数量内的View全部makeAndAddView(…)这些View也会都会被保存在mLoadedViews中, 然后会有一次之前说的兜底的requestLayout()操作将改变的内容刷新展示出来. 在添加的过程中,还会更新mCurrentBufferIndex,代表的是在当前展示的View在mLoaderViews这个buffer中的index(之前说个这个差异), 并会调用mViewInitializeListener的onViewLazyInitialize
• 从上面的流程可见,在data发生变化的过程中, 当前展示的View对应的Item在Adapter的index是UI重新构成时基于的标的(以其为中心重新生成和布置View), 假设的是这个index对应的内容应该是不变的,或者说对用户来说是不变或者变了也可以接受的
12. snapToDestination()是在Action_up并且没有被认定为fling的情况下的ViewFlow的自动回复到合适位置的操作:
• 这个合适位置的判断也很简单,根据当前的scrollX, 判断当前离哪一个childView更近,然后调用 snapToScreen(…) 渐变移动到那个合适的位置.
13. snapToScreen(int whichScreen)这个实现也很简单,在考虑了越界情况以后基于输入的whichScreen得到要scroll到的新的X,然后i调用scroller的startScroll开始进行这次Scroll操作的模拟和计算,并且触发invalidate()来实现不断的重绘刷新scroll.
14. screen 这个概念代表是 当前存在的childView.
15. setVisibleView(int indexInBuffer, boolean uiThread)的作用就是scroll以展示出指定的indexInBuffer的View.
ViewFlow 源码笔记(2)
原创
©著作权归作者所有:来自51CTO博客作者fyfcauc的原创作品,请联系作者获取转载授权,否则将追究法律责任
上一篇:__gnu_cxx
提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
ActiveAndroid 源码阅读笔记 (2)
5.TableInfo:
android 初始化 App List -
Redis源码阅读笔记(2)-- 对象robj
熟悉redis的同学都应该知道,redis中主要的数据结构包括简单动态字符串sds、双端链
redis 对象 源码 #define 数据结构