ViewFlow 源码阅读笔记(1)

动态视图流是一种继承自AdapterView的组件,能够动态添加视图并优化用户体验。它内部维护了一个SideBuffer,用于缓存加载的视图,可通过XML属性设置缓冲区大小。该组件包含两个View的holder和Scroller,支持滚动功能。在添加或滚动视图时,会调用detachViewFromParent()方法临时断开视图与父级的连接,并在需要时重新连接或回收。此外,`setSelection()`函数用于选择特定位置的视图,包括回收和添加新视图,同时会调整缓冲区以优化显示效果。滚动过程涉及缓冲区更新、布局请求和回调。实现过程中遇到的bug已被修正,确保了视图流的稳定性和性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. ViewFlow, 继承自AdapterView, 基本和ViewPager差不多,不过可以动态的增加View,
   自行维护了一个SideBuffer*2大小的view buffer, 提升用户体验,可以作为xml属性设置。
   A horizontally scrollable ViewGroup with items populated from an
   Adapter. The ViewFlow uses a buffer to store loaded Views in.
   The default size of the buffer is 3 elements on both sides of the currently
   visible View, making up a total buffer size of 3 * 2 + 1 = 7. The
   buffer size can be changed using the sidebuffer xml attribute.

2. 有两个两个 View的holder: LinkedList<View> mLoadedViews, LinkedList<View> mRecycledViews
   在构造初始化时会new并赋引用。
   还有一个Scroller来提供对于scroll的支持。
 
3. 将一个View添加到RecycledViews中会另外的调用detachViewFromParent(),
   detachViewFromParent的使用注释:
   Detaches a view from its parent. Detaching a view should be followed
   either by a call to
   attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
   or a call to removeDetachedView(View, boolean). Detachment should only be
   temporary; reattachment or removal should happen within the same drawing cycle as
   detachment. When a view is detached, its parent is null and cannot be retrieved by a
   call to getChildAt(int).

4. setSelection(int position) 该函数override AdapterView的方法。首先停止scroll<mScroller.forceFinished(true);>
   如果没有有效的Adapter或者Adapter提供的View为0,那么也直接return, 最后还要check position的越界,进行clamp.
   然后将mLoadedViews中的View全部Recycle,以position作为参数,并且addToEnd来make并add一个View.
   makeAndAddView(...) 会先进行obtainView(position), obtainView()先从RecyleViews中获取一个被cache起来的View,
   将此Cached View作为convertView参数调用Adapter的getView(....)函数,如果得到的View并不是convertView,并且convertView
   也不是null, 那么重新将此converView回收,维护一个变量mLastObtainedViewWasRecycled = (view == convertView)
   即表示最后一个obtain到的View是不是之前回收的View的循环使用。为了正常使用此View,还需要尝试获取此View的LayoutParam,
   如果没有,那么还需要new一个LayoutParam<FILL_PARENT,FILL_PARENT>并set给此View.
   Add的操作则是由setupChild(view, addToEnd, mLastObtainedViewWasRecycled)完成的,
   该函数的操作: 通过measure方法为ChildView精确指定了尺寸<getChildWidth/Height() + MeasureSpec.EXACTLY>,
   然后如果ChildView是之前被recycle使用的,那么使用attachViewToParent(),否则使用addViewInLayout().
   <两者的区别还需要细表>。 在make并且add View以后,会将此View加入到mLoadedViews中。如果有ViewInitListener的话,
   会调用此Listener的callback来告知currentView已经init完毕的消息。
   然后还会进行一次SideBuffer的调整,从当前select的position从左右方向开始,最多向左/右进行sideBuffer的
   长度<leftIndex/rightIndex>。在确保在确保left/rightIndex不越界的前提下,会重新进行BUffer View的
   添加<也是通过makeAndAddView,这也是为什么前面会有一次recycleViews()将所有loaded的View回收,因为要完全重新调整>,
   mCurrentBufferIndex获取当前的currentView在mLoadedViews中的index<其实就是当前ViewFlow真实存在的View列表中的index>,
   而mCurrentAdapterIndex则代表currentView在Adapter中的position<Adapter是一个静态的全局观,所有的View都存在,0,1,2...排
   列,而真正ViewFlow中的View则是一个围绕中心currrentView动态调整的View集合>。 上面这些操作都不会引起重新layout,
   因此需要显式的requestLayout().因为涉及到布局重构,因此还需要一个步骤来scroll到被select的View<可以平滑scroll>,
   这个函数就是setVisibleView(int indexInBuffer, boolean uiThread),scroll之前会clamp一下传入的indexInBuffer<这里就是
   mCurrentBufferIndex,因为scroll针对的是真正存在在ViewFlow中的View,因此会以getChildCount()作为上限>。
   scroll的过程就用到了scroller以及invalidate()/postInvalidate().
   最后,如果有相关的Indicator和ViewSwitchListener,那么还需要调用一下相关的callback.
5. postViewSwitched(int direction)这个函数其实就是滑动一下<direction决定了 <0向左 >0向右>,
   如果direction是0,直接return.
   考虑向右Switch的case: 首先mCurrentAdapterIndex<在全局Adapter视野中当前View的pos>和
   mCurrentBufferIndex<在当前真正ViewFlow中存在的View的pos>都会+1,一个Enum的Set会remove LazyInit.LEFT,
   加入LazyInit.RIGHT,
   如果发现 mCurrentAdapterIndex已经大于了mSideBuffer<做这个判断是因为在mCurrentAdapterIndex <= mSideBuffer
   时,是不需要回收前面的View,因为还没有超过SideBuffer规定的界限>,
   那么会将LoadedViews的第一个View rmove并进行回收,那么相应的
   mCurrentBufferIndex需要-1,因为前面的View少了一个。
   因为是向右滑了一位,那么需要相应在右边增加一个Buffer View<这里要判断是否会越界mAdapter.getCount()>,
   向左滑同理,不赘述。
   同样在做完之后,需要一次requestLayout,以及setVisibleView()来scroll到新的当前View,
   最后同样需要调用相关indicator和ViewSwitchListener的callback.
   这里有一个ViewFlow的bug<也不完全确定,但是确实这里遇到了out of bound exception>, 在调用mLoadedViews.get(mCurrentBufferIndex)时没有check越界
   <mCurrentAdapterIndex也没有>。需要clamp一下。
   已经到了最左/右,再继续Switch,应该是Switch到原来的位置不变,code的其他部分似乎已经做了这个clamp
   <比如setVisibleView()/getSelectedView()等>,但是在这两个callback的地方没做。

   
   postViewSwitched的唯一调用点 是 computeScroll(),在scroller驱动的 scroll过程中。
   
 
   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值