Tab 模块 总结

来源:互联网 发布:扒一扒淘宝靠谱的代购 编辑:程序博客网 时间:2024/06/14 15:38
Tab 模块 总结


从整体上对TabMenu这个模块的一些设计分析:


<1>为了实现全屏推拉TabMenu,MovingMenu是叠在mainUI上面的,两者都在一个FrameLayout中,并且MovingMenu都是
match_parent来实现了全屏覆盖.
MovingMenu作为FrameLayout,本身没有设置backGround,因此是透明的,而里面的两个View组件,behindView和
TabMenu平常也是gone的,这样对肉眼观察没有造成影响。
TabMenu做为一个View的坐标原点,一直没有变化过,也就是MovingMenu的坐标原点. 造成视觉上拖动变化的是TabMenu的scroll
<从未离开,如影随形>




<2>几个组件的角色分工:
MovingMenu:负责整体菜单出现/消失的Touch/button交互.
TabMenu: 负责了与其他模块的Tab管理交互<真正维护Tab信息的不在这一模块中>,
         作为整个TabMenu的对外代理,提供操作接口.
         以及在上下层之间的消息传递和将用户对V的操作的后果对外广播.
         还有一些click的处理逻辑.
TabContainer: 分担了TabMenu的Tab管理逻辑,以及实现UI上的gravity<整个TabList的>效果,也有上下层消息传递的作用.
TabViewListView: 真正的UI展示,以及单个TabView的Touch交互逻辑和整个List的Touch滑动逻辑.
TabListViewAdapter: 和正常的Adapter角色一致, View的生成,管理 和 更改设置.


<3>因为这个模块的整体角色定位是V,因此不会做任何直接操作Tab信息的操作,操作针对的都是V层面的View,
在用户通过V增加/删除了Tab以后,TabMenu做的也只是将这个作为消息发出去<这个消息是由TabViewListView发出去的,其实
TabMenu发出来可能更好>,由专门的模块来根据消息维护M,然后TabMenu才会增加/删除相应的View.
该模块的TabMenu也会监听Tab M的变化,onTabAdd/Move的时候会转发消息到TabViewListView,由其完成对V的更新.


<4>开发过程中有点混乱了,造成了消息传递机制没有统一<既有直接调用,也有Listener实现,还有EventBus插一脚>


<5>MotionEvent的RawX/Y在使用时,如果要转换为相对某个parentView的X/Y<或者margin/padding这些相对值>,
减去parentView的getLocationOnScreen()就可以.
不使用getLocationInWindow,因为它是基于View所在的Window,而MotionEvent的RawX/Y则是基于Screen的
<screen >= window,比如最上面的状态栏就属于screen而不属于App的MainActivity的Window>.


<6>要注意相对坐标参数和绝对坐标参数,在两者混用时一定要小心<还要考虑到scroll的影响>.


<7>Adapter中的TabList也不是一开始构造的时候就设置了<不需要这么做>,
而是在TabMenu第一次show的时候,如果发现还没有初始化这部分<一个flag标示>,
那么就为TabViewListView 设置当前的TabList,随后这个M的改动会反应到List上去.


<8>TabViewListView中scrollY的下限为0<下拖最多到第一个TabView>,现在则在layout的时候,
求出所有childView+divider的height和 和 List的height做对比,多出的值,
就是允许scorllY的上限<上拖最多到最后一个TabView>,如果比height还小,那么也是0,就是禁止scroll<不用拖就可以完全展现了>.

<9>在TabViewListView中的onMeasure提供的输入参数,其中的widthMeasureSpec/heightMeasureSpec,
通过MeasureSpec.getSize(widthMeasureSpec/heightMeasureSpec)就可以得到List的包裹Layout TabContainer的width和height,
在Portrait的情况下,TabContainer的长度<Height>就是List能展现给用户的长度,而TabContainer的Height在layout的设置中不能
是fill_parent<这样会把下面的tool button遮住>,也不能是wrap_content,因为需要TabConatiner占据除了tool_button之外的所有
height,这就需要将TabContainer的height设为0dp,使用layout_weight了<只在LinearLayout下有效>,这样就可以达到目的.
在List中的onMeasure,的heightMeasureSpec的size就是TabContainer的height<当然mode可能是AT_MOST或者EXACTLY,我们不用管,
onMeasure由自己实现就是这样的好处>,在orention变化以后,从竖向List变成了横向List,那么这时候,List能展现的最大width就是
TabContainer的最大width,而在因为布局文件是针对Portait写的,这时候就需要重新调整TabConatainer的LayoutParams,
原来是height=0dp,layout_weight=1来实现占领剩余的height,而在横向列表中,因为不存在tool bar 在横向和TabContainer抢占空间,
因此直接将width从原来的wrap_content调整为fill_parent就可以.

<10>ListView在layout文件中就android:scrollbars="none"取消scrollBar的显示。

<11>写函数的时候,在前面做一下null/越界之类以及模块内部的一些相关flag的判断不多余<尤其是public/package access的>.

<12>在TabViewListView中的onMeasure,在portrait/landscape模式下的 width/height取的是child的measuredWidth/height<在生成View
的已经measure了>,而因为TabContainer是wrap_content的,因为Container取得也会是这个值.
TabMenu本身是fill_parent的,而tabContainer则在portrait/landscape模式 width/height没有占据全屏,
所谓拖动TabMenu,其实一开始是将TabMenu先scroll到某个值,然后慢慢归0.
而通过使用TabMenu的gravity,可以使tabContainer在从右边拉时在TabMenu内靠右,从左边拉的时候在TabMenu内靠左.

<13>对处理Touch事件的一些比较通用的规程:
(1)mHasDown<已经down下>, mDragging<从第一次scroll开始为true>,这两个flag一般都有存在的价值,
除了标示当前Touch事务的状态外<已经down了么,开始scroll了么>,还可以用在真正执行位移函数的预前判断中,
来决定是否真的执行位移<比如scroll,调整padding, LayoutParam之类的>,这样通过这两个flag,就可以很方便的控制位移
是否真正的进行.
(2)down时的X/Y(rawX/Y)或者MotionEvent<obtainNoHistory轻量级复制>可以保存下来,作为整个Touch事务的起点,
在scroll时可以获得当前scroll的终点距离down点的距离.这个距离很多时候都会被用到.
(3)scroll的时候,可以保存一份X/Y(rawX/Y)或者MotionEvent记录上一次scroll的终点坐标<如果是第一次,那么就用down的坐标>,
用这个结合本次scroll的终点坐标,可以得到本次scroll的真正distance<onScroll提供的一般不使用,除非是没有上面信息的情况下>,
每次scroll处理完更新.
(3)第一次scroll的起点和终点坐标也可以考虑记住.
(4)如果对Touch具体位置不敏感,只对Touch的绝对位移或者fling的速度有兴趣,那么推荐使用RawX/Y.
(5)对Touch的处理逻辑一般都封装在被操作View的一个单独内部类中.
(6)如果要支持复数个不同形式/层次的Touch操作,那么将这些不同的操作区分出优先级,每个TouchEvent都按照优先级<职责链>
进行处理,没被高优先级处理,才转交给低优先级处理.
(7)如果parentView在某些情况下对某些Touch操作比ChildView更有优先级,那么使用onInterceptTouchEvent.

<14>辅助TabView是和TabMenu一层的元素,同处于一个FrameLayout,这里是通过调整辅助TabView的topMargin和leftMargin来
实现辅助TabView的位移的,一个前提是辅助TabView自己的gravity<不是layout_gravity,layout_gravity是自己相对parent的,
不过因为Framelayout默认就是左上为gravity,所以不用调>
会被调整为Gravity.LEFT | Gravity.TOP,这样的目的是为了使辅助View的Margin能够生效<Margin生效的前提是gravity被设置>.

<15>在结合RawX/Y调整辅助TabView的Margin<Margin是相对parent来说的>时,
要考虑到辅助TabView外层FrameLayout的原点不在屏幕原点的情况,
通过getLocationOnScreen<不是window>可以得到外层FrameLayout的原点坐标,
在开发中,最好在纸上画出layout图,方便分析,否则光凭脑子挺tough的<至少对我来说>.
梳理一下down的时候设置辅助TabView位置的过程:
(1)得到down的rawX/Y, X/Y: downRawX/Y downX/Y
(2)得到List的scrollX/Y: scrollX/Y
(3)得到被down到的TabView的Left/Top: downViewLeft/Top
(4)获取被down到的TabView的原点的RawX/Y: downViewRawLeft/Top
(5)就是从down的RawX/Y减去down的点在TabView的相对X/Y: downTabViewX/Y
(6)downTabViewX/Y = downX/Y - (downViewLeft/downViewTop - scrollX/Y) <结合图就很容易相同了>
(7)然后downViewRawLeft/Top  = downRawX/Y - downTabViewX/Y
(8)然后要考虑到之前说的包裹辅助TabView的FrameLayout的screenLocation:parentRawX/Y
(9)downViewRawLeft/Top - parentRawX/Y 就是辅助View应该MarginLeft/Top了.
不断的在相对坐标和绝对坐标系之间做切换.不过在思考时,用Raw坐标更简单一些.
在开始位置确认以后,有了移动方向和移动距离,就是简单的加减运算了.
在开发时,每次位移以后保存的是被Drag TabView的原点RawX/Y,
每次重新减去包裹FrameLayout的ScreenX/Y.

<16>使用scroller的fling时,用List当前的scrollX/Y作为startX/Y, 一个注意点:
方便起见只说Y轴的<X轴同理>:Touch的fling在Velocity>0时是向下,Velocity>0时是向上,
但是如果对于scrollY来说,向下scrollY是减小的,向上scrollY才是增大的,
那么scroller的VelocityY需要传递Touch的fling得到的VelocityY的负数才能实现模拟ScrollY的在fling中的正确变化.
scroller fling的MinX/Y就是0. MaxX/Y则是实际List长度<这个在UI上是不存在的>与List显示出来的长度的差<如果<0,那么=0>

<17>一直scroll到松开<Action_UP>, 这个操作可以视为一个velocity为0<因为慢到了没有被认为是fling>,但是Move 距离够大的
fling,一般可以在OnTouchEvent的ACTION_UP情况下,在GestureDetector没有处理的情况下<没有被其识别为fling>,再作为一个

特殊的fling处理一下,因为这也算是一个特殊的Touch事件.


<18>TabListView本身对Tab这个M具体怎么添加和删除成员不care,只是负责在Tab M变化的时候,利用新的M来刷新 V, 以及在UI交互某个TabView被移除时,

除了将View移走外,还会通知M,注意,这里虽然感觉VIew已经刷新了,但是因为M被通知变化了,因此在M更新完自己以后,会重新出发TabListView的V 刷新。因此Tab index之类的可能会全部改变。某种意义上是一种比较低效的刷新机制。最开始这么设计,后来察觉到,TabView其实和某个Tab之前不必完全耦合的,在每次刷新以后,

更新每个TabView对应的Tab index即可,刷新一次tabView的显示,这个TabView就成为了新的Tab的V了。


<19>Tab删除和添加的步骤正好反过来,Tab添加是 M 层已经添加了,  新的TabView也被加入了TabListView并且最开始height被调整为0, 然后在layout的时候才开始run动画。而Tab的删除则是V这边是先把动画跑了,然后才会remove view以及通知Tab M.

0 0
原创粉丝点击