Tab 模块 (一)

来源:互联网 发布:有限元模型是算法吗 编辑:程序博客网 时间:2024/06/04 20:02
Tab 模块 (一)

MoveMenu extends FrameLayout, 可以响应手势或者按钮出现/消失.
提供了几种手势滑动方向,并且是可以set的并且在APP的setting中持久化.


<1>手势滑动方向被enum化:
UNDEFINED,
LEFT,
RIGHT,
LEFT_RIGHT,
FORBID_DRAG,
TOP
并保存为成员变量<M>
有private的setter<因为在某些情况下不能乱设>和public的getter
updateMode()会考虑到当前的orentation和setting中设置.


<2>在几种情况下,Menu不应该被显示:
有别的Menu出现
下面的MainView中ViewPager被touch
VKB出现
通过监听这些event来维护一个 mShouldAppear的flag


<3>对于自定义的View,要初始化指向其内部View的引用,onFinishInflate()是一个很好的时机,在super的onFinishInflate()
被调用以后,View对象都已经被构造,可以通过findViewById得到其引用了.
对于View来说,也可以在这个时候register listener<不过我更喜欢在 onAttachToWindow,FinishInflate不代表已经显示在window了>


<4>开放出Listen接口,提供对Menu一些行为(openBegin/openEnd/close等)的监控.


<5>此Menu内部有一个container<是通过与xml文件中在里面增加了一个FrameLayout作为container实现的,这样不好,在于code和layout
 xml 耦合了>,用于放置真正要显示的MenuView<这个MenuView有setter/getter>.


<6>public的show/hideMenu<输入参数指示是否需要渐变动画>/toggle<二元切换,包装函数>


<7>需要能够检测Menu是否已经显示,这里要用isShown()<Returns the visibility of this view and all of its ancestors>,
用VISIBLITY检测不能保证View的parent是可见的.当然还要检测menuView是不是null为前提.


<8>为了检测move,需要定一个moveThreshold,这个值是以dp单位的,在构造时获得了DisplayMetric的density后会转为px<在touch中的
x/y都以px为单位>.


<9>为了检测更为复杂的Touch操作,使用了GestureDetector,extends一个GestureDetector.SimpleOnGestureListener<非抽象类,
不用检测所有的TouchEvent,因此可以用这个,这个simple版提供了所有空实现,比实现接口方便>
作为构造函数就可以了.
为了进一步抽离逻辑,该Listener检测到的TouchEvent<down scroll fling SingleTapUp>全部转发给一个专门处理
Touch相关的TouchController<主力C>.
不过该Listener会做一些简单的无效Touch检测排除<检测null的motionEvent>,以及对disableMove flag的check.
禁止GestureDetector检测长按.


<10>一般为了实现比较复杂的手势操作,只override onTouchEvent是不够的, onInterceptTouchEvent也会参与,<dispatchTouchEvent
一般很少override>,onInterceptTouchEvent的意义在于改变了默认的touch处理流<子view优先>,使得View有机会比子view更早处理某个
Touch事务.
onInterceptTouchEvent中,如果当前MovingMenu是disable的<!isEnabled()>,那么就返回true,直接接管接下来的一串TouchEvent
<一次Touch事务>, 不过因为disable了,因此在onTouchEvent那边,也会做这个检查,如果disable直接返回true,吞了这一串Touch,啥事
不干.
(1)Action_Down, 取得event的rawX/y,如果发现这次Touch down是在允许Move的范围<rawX/y做匹配>内,那么就直接截获这一次Touch事务,
此次Touch事务全权交给了MovingMenu的onTouchEvent处理,之后属于一次事务的touch event全部直接交给onTouchEvent,
onInterceptEvent不再截获<API如此设计>否则返回false不截获,交给子View处理,不过以后还可以继续截获<API如此设计>
当然了,如果到子View走了一圈,最后没人吃,那么onTouchEvent还有一次机会处理<这时候会再做一次hitTest,如果没中,false回吐Touch
Down到parent>.
(2)Action_Move,如果之前没有截获down,就可以继续截获到move,如果这时候发现正在拖着Menu消失<已经到了要消失的临界点>,那么
将此move event通过MotionEvent.obtain(event)复制一份,然后将其设置为action_down,交给gestureDetector处理,
这个转换纯粹是为了模拟用户点击Menu外区域实现了Menu消失的效果
返回此event是否被gestureDetector吃了.
(3)其他情况,直接super.


<11>检测是否drag menu到可以menu消失的地步,使用rawX/Y,有几种例外情况,
如果本身正在drag显示中,如果本来就没显示,如果是landscape模式,这些都不认为drag应该hide Menu.
否则,先获取从down 到 现在的 move 的 x/y 的距离的绝对值, 如果没有突破threshold的话,也不管.
如果发现目前 moveX比moveY小,也认为是无效的<只支持X轴的>,
这些都通过了,才可以认为到可以消失Menu的程度了,可以通过 downX和MoveX的关系来判断是向左还是向右drag,
结合dragMode判断.


<12>检测是否down到了可以进行Move的区域,也要作判断,是否禁止了drag<哪里都有这样的检测,还没想到一个好的办法解决>,
以及这个区域是不是有效drag区域<比如最上面的url栏那一块是禁止drag出menu的,通过rawY判断>
同样是用rawX/Y, 为X制定一个临界区域,确切说,是两个,一个左侧的,一个右侧的,
左侧的,因为坐标系是以左上角为原点,因此只需x < T
右侧的,则是 X > dm.widthPixels<屏幕宽度> - T,
只有满足两者之一的,才会返回true表明down到了可drag menu的区域,后面的move可以触发进一步的menu drag<更新一个TouchInEdge
flag 为true>.
如果是同时允许left/right,那么还会记录实际的drag方向.


<13>onTouchEvent做完Enabled检测以后,如果是down,会做一次hitTest rawX/Y,检查是否点到了padding区域<无效,回吐Touch event给
parent>.
否则直接转交给GestureDetector.
如果是Move的,还要做一次draghiding的检查,
如果是UP,并且没有被之前的gestureDetector 处理,会尝试检测这次Touch事务是否
是一次fling<fling的话,在up以后,Menu还会惯性移动>, 之前的gestureDetector也会尝试识别fling.


<14>GetureListner内Override了四类操作:
(1) onDown, 判断完MotionEvent is null 和disable pass以后,会直接将MotionEvent的RawX/Y转交给 MoveController
(2) onFling, 同上.还会一并传递velocityX/Y
(3) onScroll 同上,只会传递 scroll目的地的MotionEvent的RawX/Y,一般不使用onScroll提供的distanceX/Y,而是自己记录每次Drag的结束和开始,自己计算位移量.
(4) onSingleTapUp 同上


<15>moveController封装了主要的对于TouchEvent的处理,
(1)down,如果Menu没有显示或者没有没有触及drag的初始开始区域,直接false回吐event,本次Touch事务的后继event也不再被接收<onInterceptTouchEvent也不再会被调到>.
如果pass,则标志着一次drag的开始<mBeginDrag>,并且要记录下这次down的 rawX, 同时也标志着现在不是在dragging状态
<mDragging,设计中,如果正在自主滑动,点击下去会导致滑动的停止>,都会有相应的标记flag.
down所作的事情少,只是一切的起点。
(2)drag,如果之前的down没有成功的标记mBeginDrag,那么drag也不会被处理,直接回吐event.
否则,
   如果当前不是dragging<mDragging>,那么会标记mDragging,这代表着dragding开始以后的第一次drag触发, 会记录本次的rawX/Y作为下次drag的startX/Y<这一次的drag直接移动到这里>,
   同时也会把rawX/Y记录为上一次Drag的结束X/Y, 开始初始化Menu的位置以及相关变量,
   而如果menu没有被显示,就开始根据当前drag的位置展现Menu,否则如果已经显示了就不做任何操作,返回true代表着可以消化本次Touch事务.
   如果是后继的drag<scroll>事件,那么可以根据上次drag结束的位置和这次drag结束的位置得到scroll的距离 dX/Y.
   得到了以后就可以移动Menu了,如果可以移动,那么返回true消化此事务,否则false.
   移动Menu的部分也作为一个单独函数抽离,代价是这个函数也在最前面做是否允许drag移动的check.
   
   第一次的onScroll/move/fling应该被看做一个特殊事件点.
   
<16>placeMenu函数负责将Menu的初始显示和收尾消失,通过输入参数 open来判定显示/消失.
如果有listener的话,还会通知Listener Menu的openEnd/close.
Menu的初始显示:
Menu的收尾消失:将Menu的container的VISIBLE设为不可见,其余部分是操作一些Menu消失时的收尾工作,当时没有将这些抽离出来,应该在这里只触发一个event的,不过这么做效率
高一点.


<17>改变Menu的位置是通过scrollTo实现的,不过scrollTo<位置>前要检查一下是不是overscroll了,不能超出一定的范围<根据drag的方向定>。


<18>在drag的时候,可以设置后面的View的scale,为了方便使用,将scale这种行为也封装为了一个scaler类,通过设定要scale的view以后,就可以方便快捷的设置scale,
这里都默认以View中心为缩放点,而因为android不同版本的scale方式不同,因此就将scaler作为一个接口,按照不同的版本分别实现接口,最后按版本生成对应类的实例。
这些类还封装了一些scale开始结束或者进行中的回调逻辑. 这种方式项目中也用的比较多。


<19>有时候会需要获取底层View的截图来做缩放,本模块的截图直接定制了一块Canvas,然后申请一块适应View长宽的Bitmap <Bitmap.createBitmap(view.getWidth(), view.getHeight(),
Bitmap.Config.RGB_565)>,再使用Canvas.setBitmap来将Canvas与此bitmap绑定<考虑到bitmap的构造成本,该bitmap会被cache起来以
重复draw,只有没有构造,或者长宽不符合的话,才会重新构造>,
最后直接调用要截图的view.draw(canvas),这样就将View的内容画在了bitmap上。


<20>在检测到fling时,会传入fling时 action up的rawX/Y, 以及velocityX/Y.
也需要检测是否是在dragging以及是否允许drag.
在fling以后,有可能会有两种后继行为:
一种是超过了某个threshold,Menu会惯性运动.
另一种则是Menu会恢复原状.
因为fling必定是以Action_UP为结束的,因此fling也是一次Touch事物的结束.
利用本次up的rawX/y和之前保存的上一次Drag/Down的end rawX/Y,就可以得到这次fling的moveX/Y
首先,如果fling的velocity超过了某个threshold,那么就视为是一次有效的可以被处理的fling,会根据dragMode<left/right>以及
velocity </> 0 来自做相应的Menu显示/消失.
如果velocity不够,但是moveX/Y突破了某个threshold,那么也认为是一次有效的fling,会显示出Menu,
否则,就尝试回复Menu<因为可能fling之前的down scroll已经把Menu拖出来了点>

<21>将Menu以渐变动画显示/消失的部分封装为了一个函数,指定了当前Menu的位置,最终要移动的位置,以及速度,显示/消失.
为了实现Animation,使用了ValueAnimator<为了兼容性,要使用nineoldandroids的库>,
ValueAnimator本身做的事情很简单,就是将一个数值渐变为另一个数值,而数值变化中的回调<AnimatorUpdateListener
.onAnimationUpdate()>带来了极大的可定制性,Menu的位置调整就是在这个回调中不断根据当前ValueAnimator的value<getAnimatedValue>
调整的<ValueAnimator的每一次update回调对应着Android的一桢>. ValueAnimator也是在start它的线程运行的,这里直接主线程start,
因为是平稳过渡,Animator的Interpolator使用LinearInterpolator.
为了防止Animator过程中的Touch干扰,直接将整个moveMenu setEnabled(false),在动画完毕以后恢复<这么做可以省掉很多的
例外考虑,并且在交互上也是可以接受的>,AnimatorListenerAdapter()的onAnimationEnd是动画结束的时机,
这个时机,除了将Meun的enable恢复外,还要做一些收尾工作,比如将dragging等flag 置为false, 以及根据是显示/消失 来设置Menu的
background与VISIBLITY.

这个函数就是为 Menu的惯性渐变位移准备的, 和 Touch中dragMenu是两个过程,但是原理一样,
前者算是对后者的一种模拟. 因此前者的最后也会触发stopDrag。

<22>为MI4专门设定了一个定制的DragEdge Limit, 因为测试反映MI4上drag很难触发.

<23>目前看的开源项目和android源码,将Touch部分处理的逻辑封装在一个Drag/Move/TouchController都是很常见的.

<24>对于SingleTap<也代表一次完整的Touch事务,之前会触发一次down>的处理,能轮到MoveMenu处理SinleTap,就说明Menu其他的View没有处理此Touch事务。这种情况下,可以停止drag,并且Menu消失.

<25>为了增加鲁棒性,会专门维护一个flag<mDown>代表 Touch已经down了,不过不一定up的时候会一定设为false,在某些fling以后的
惯性运动中,还会保持true,这个标记在drag的时候参与是否进行的判断,以后有drag<scroll>的前提是必须先down一下.

也会再维护一个dragging flag<在第一次scroll的时候会被设为true>,为了区分第一次和后继的scroll, 同样也会作为正在scroll的判断
标准,参与到drag/fling等的是否进行判断中.

<26> 因为GestureDetector的onScroll回调中提供的distance似乎不太可靠,一般都是自己记录维护上次scroll的endX/Y,自己推断scroll
的distanceX/Y,这部分数据,考虑其使用范围<只在处理Touch有用>,直接封装在MoveController中作为成员变量.

<27>后期做性能调整时,察看Menu滑动的帧率,也是通过ValueAnimator的onAnimationUpdate来粗略监控的<draw应该更精确>,因为理论上
是ValueAnimator每次onAnimationUpdate都对应一帧.

<28>每次开始drag的时候,会将Menu放在一个起始位置,其实就是scrollTo,这种方式,其实没有改变Menu的位置,只是将Menu呈现的
可视化内容移动了,不过够用了。

<29>MoveController会维护一个上次drag的位置变量LastDrag,在本次Drag时,结合变化更新,然后再保存新的值为LastDrag.

<30>在真正的Menu外边套一个MovingMenu这样的透明FrameLayout,目的就是为了实现对侧拉手势的支持,
不太想将这个侧拉手势的检测做在已经有的View/layout中,会引起某种大组件之间的耦合,
不过可以尝试将侧拉手势检测封装为一个类,由其他的View来使用,不过当时没有考虑这种思路,因为设计后来提出了在Menu滑动时背景
变化的需求.
目前这样做,多了一层layout,而对性能的考虑,layout层数越少越好。

<31>缩放ImageView的内容,可以直接通过setImageMatrix来实现,不过在setImageMatrix以后,要再次setImageBitmap
从ImageView获取对应的Matrix实例<一般不new>,然后reset设为identity,在通过SetScale来设置scale,
注意这样做因为没有指定pivot,而是用左上点作为原点,因此还需要对matrix postTranslate相应的距离以使其看起来是以自己的中心
进行scale的.
对于View的scale,则直接使用了nineoldandroids的ViewHelper的setScaleX/Y< >=  KITKAT>

和AnimatorProxy的wrap的setScaleX/Y,< >= JELLY_BEAN>


0 0
原创粉丝点击