Tab 模块 (五)

134 篇文章 0 订阅
87 篇文章 1 订阅
Tab 模块 (五)


TabViewListAdapter extends BaseAdapter.
Adapter承载了M和C的角色, 同时因为自己实现的ListView没有实现convertView机制,
因此在Adapter里面搞了一套View cache机制来提升性能.
而由此也引入了ViewHolder这个优化手段,
直接将View的每个子View的引用也用这种方式cache起来.

<1>ViewHolder,其实就是一个struct,里面是各种View的引用,在通过findViewById得到某个View的所有<或者需要的>childView以后,
会建一个ViewHolder类将这些引用都cache起来<不会再变化>, 然后,利用View的setTag/getTag<很好用的一对方法,可以为某个View对象挂接一些Object>,
将ViewHolder设置为View的tag, 在以后想得到某个View的某个childView时,就不必findViewById了,而是直接getTag得到ViewHolder
<要做类型转换>然后就可以直接取得
已经保存的ChildView的引用.

<2>既然Adapter承载了M的角色,就要有M的数据,一个Tab的ArrayList代表着Tab的信息, Adapter的任务就是在这些Tab和ListView之间建立起联系.

<3>因为Adapter只是一个Adapter, 因此没有像View/Activity一样直接自带context,所以可以自己保存一个context引用或者一个静态全局类保存Application/Activity
然后当成Context用.

<4>Adapter的一个构造参数是要构造的TabView的layout的ResId, 这样就有了某种灵活性<其实这种灵活性很有限,因为这个layout的元素的id都已经和Adapter的逻辑耦合了>.

<5>因为要动态频繁的inflate出View,因此可以构造的时候就保存一个Inflator的引用.
(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE)

<6>同样因为要保持对orentation的响应,会有一个setOrentation的响应函数和内部维持一个当前orentation的flag.
对Orentaion的响应也就是挨个的调整每个tabView的参数.

<7>涉及到动态生成TabView以后,要对每个TabView内部的childView进行size等信息调整,而这些值对每个TabView都是一样的,因此在可以将这些值直接cache起来.

<8>因为之前load bitmap截图是由TabContainer实现的,因此还需要维护一个TabContainer的引用<这种跨了一层的其实不好,不过就如同之前说的,tabContainer本身就比较尴尬>.

<9>一个为了优化TabMenu第一次启动速度的trick: 因为第一次启动,必然没有现成的TabView,而为了展现一个TabView,就一定会要inflate一个,
inflate其实是一个比较耗时的操作,因此,就在Adapter构造时就直接inflate一个TabView,然后在真正要展现TabMenu<就是List向Adapter要View>的时候就可以直接用.
<其实最后也没剩太多时间,大概7,80ms,不过性能优化,大多时候本来就是一点一点抠的>

<10>在需要更新M的时候,只提供了一个接口,输入参数只能是一个当前更新过的Tab的list, 然后将保存的TabList clear,将输入全部addAll<这一部分是因为项目其他模块的限制>,
并且这个修改M只是单纯的修改M,而没有触发UI更新<交给了调用者,虽然M改动过以后,应该刷新UI,但是出于灵活性,没有将修改M 和 刷新UI绑定在一个函数中>.
这里其实M是复制了一份另外模块反映Tab信息的M,而不是直接引用过去,是因为这里的M代表了UI映射的M,和另外模块的Tab的M的行为不是完全一致的<当然直接引用也不是不行
很多时候,有很多方法,并且也不会有特别大的差距>.

<11>要Override的几个方法:
getCount() 直接Tab List的size
getItemId(int position) 直接返回position
getItem(int position) 返回对应的Tab
getView(int position, View convertView, ViewGroup parent) 返回构造/cache的View.
areAllItemsEnabled() 直接返回true
isEnabled(int position) 直接返回true

<12>需要一个List来保存当前显示在List上的TabView的引用<CurrentViewList>,方便随时根据Id获取View来调整参数.

<13>为了提升性能,并且UI上TabView的创建和删除很频繁,inflate一个TabView也比较费事,因此建立一个TabView的回收池,在创建一个TabView时,先查找回收池有没有可用的,
在删除一个TabView时,其实是放在了回收池中<当然,有一定大小>. 空间换时间.

<14>getView函数不一定每次都要创建一个View,如果getView给的position是本来就存在View的一个position, 那么只需要返回当前这个position的View就可以了<CurrentViewList.get(position)>,
只有在不存在时,才会去创建<当然这里因为有回收池,也不一定>,然后加入到CurrentViewList的position中.

<15>创建一个TabView:
通过inflate得到一个TabView对象后,此时这个对象还没有跟任何一个ViewGroup关联,也没有attach到任何Window上,
是一个显示外的View<不过其内部ChildView的分布则是OK的,因为layout文件指定了>,这种View显然是没有ViewHodler的,
通过findViewById将ViewHolder设置完以后,就可以setTag将其与TabView对象绑定,然后返回新建的对象.
从回收池取就更简单了,直接Remove(0).

<16>增加某个Item到List上要通过Adapter的addItem, 指定增加的location<注意,这里的增加不是增加一个Tab,而是Tab已经存在,要在List上增加其对应的TabView,新建或recycle>,
将取得TabView放入到CurrentViewList中,然后要对新增加的TabView的Params进行调整.
因为设计中TabView会随着数量的增加而不断变窄,因此需要先算出当前的Tab数量情况下应该的宽度,
这里通过setLayoutParams将TabView中的thumbnailView调整为期望值<其实size可变的是thumbnailView>,
设置的过程中都要考虑orentation的因素.

<17>update某个TabView的内容,没有涉及到size的变化,
主要是View上显示的内容的变化<没有measure和layout,只有draw>.
整个TabMenu的updateTabData,其实最终调的就是这个办法.
这也是常规的设计思路:
update函数的本质是将V按照当前M重新设置一遍,
那么显然M变化以后调用此函数才会将M的改动反映到V上去,
一些额外的方法会是专门针对某个View组件的通知和update,这样提高效率。
否则只有一个小元素变化导致了整个UI全部重设一遍,有点不能接受,这是一个粒度粗细的问题.

<18>一个layout和size方面的trick:在开发时,为了实现一个TabView上的dimmer<其实就是一个透明偏暗的ImageView>,
还要在外边再套一个FrameLayout将TabView本身和dimmer给包起来,这样dimmer就叠在TabView上面了,
但是这就有一个问题了, 首先,TabView的宽/高是根据不一样的情况调整的,外套的FrameLayout这种情况下一般都设成了
wrap_content, 即由里面的内容决定自己有多大,里面的TabView的实际部分会在生成的时候就设置好<当然了,也可以
直接设置外套FrameLayout的size,这样里面的就好办了>, 这时候就会有个问题,dimmer该设置多大,首先dimmer不能是
fill_parent<和parent的wrap_content结合起来就是做死>,也不能是wrap_content<里面有毛内容?>,那么显然dimmer也需要
在创建TabView的时候为其设置合适的大小<就是和TabView一样大>, 在刚创建完TabView时<就是刚inflate出来>, 里面的size
其实都没定,在被attach到某个WIndow时,才会measure layout确定,那么在没有attach之前,为了获得TabView内容的大小,
就要手动对其进行一次measure,这里measure要给的参数是
MeasureSpec.makeMeasureSpec(Context.getResources().getDisplayMetrics().width/heightPixels, MeasureSpec.AT_MOST),
咋一看会以为会是直接搞全屏,但是要注意后面是MeasureSpec.AT_MOST,这是告诉tabView你最大只能这么多,而TabView的layout_width/
height其实设的是wrap_content,也就意味着,TabView不会贪心的占据整个屏幕,只按自己的需求取就可以了,这样就measure了TabView,
就可以得到TabView的measuredWidth/height了,将dimmer设为这样的大小就可以满足正好覆盖TabView的需求了.
一般建议调用View的measure前调用一次View的foreceLayout将之前的measured数据 invalidate<4.4不需要,不过之前的不保证>.
不能通过给TabView设置LayoutParams<其实layout文件就已经提供一份了>的方式来达到限制或修改其measureHeight/Width的目的,
因为LayoutParams对某个View本身来说没啥用,而是供其parentView在measure的时候作为参考的,
因此给TabView设置LayoutParams然后调用measure其实不会反应出
新的LayoutParams的作用,而是调用其parentView的measure才能显出这个修改来.

<19> 出于性能考虑,提供了Sync和Async的刷新所有TabView内容的函数,
Sync的很简单,直接遍历调用TabView的updateContent,
而Async则是先刷新在可视范围内所有TabView的content,然后post两个runnable到下一个slice,每个runnable
从前/向后挨个刷新TabView content,每刷新一个,post刷新顺序的下一个的content刷新到下一个slice.

<20>Adapter的add/removeItem()操作的只是M,不会去影响V<TabViewListView>,其实应该是TabViewListView的
add/remove某个TabView造成了Adapter的add/removeItem(). 用户通过对V的交互影响M.

<21>因为之前的add/remove TabView造成的尺寸调整和腾出空间是由TabViewListView实现的,因此在Adapter中对要增加的
新View的尺寸进行调整时,直接按照现在TabView的数量得到一个合适的尺寸赋给new View,并最后将new View的visiblity设为true.

<22>Adapter在这里的角色类似于一个View的装配调整车间+仓库,
可以按需求组装新的View,
可以按照需求调整现有的某个View,
为所有出库的View维护一份索引,在生产TabView时,添加索引,在调整TabView时,根据索引找到View,在UI上删除TabView时,将其索引删掉.
Adapter是一个被动的角色.

被TabViewListView的UI交互所驱动和使用.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值