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过程中。
自行维护了一个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过程中。