自定义View和事件分发_分析理解

131 阅读9分钟

1 自定义view

Android 中需要自定义view的时候,先要判断是 view 还是viewgroup,如果是view则只需要重写onDraw方法就可以了,viewgroup的话则从onMeasure开始,第二步onlayout.
onMeasure中通常第一步是通过MeasureSpec.getSize()和MeasureSpec.getMode() 获取控件的模式和长度,最后在通过setMeasuredDimension()设置viewgroup最后的的长和宽,如果继承的是已有的viewgroup是类似relativelayout 或 linearlayout等,这可以一不重写onMeasure和onlayout ,只需要重写ondraw处理自己的逻辑就可以了,因为父view本身就实现了很多的功能,
宽和值 和宽的mode 可以通过MeasureSpec.getSize()和MeasureSpec.getMode() 获取,也可以通过MeasureSpec.makeMeasureSpec(size,mode)把值和mode 合并起来,
viewgroup设置宽高是一般是通过把所有的子view的宽高都加起来得来的,所以要循环测量宽和高,通过getChildCount();
如果是简单的测量 可以通过measureChild方法,内部传,childView,父widthMeasureSpec,父heightMeasureSpec 三个参数.该方法测量完子view之后就可以通过,childview.getMeasureWidth(),childview.getMesureHeight()获取到ziview的宽高, 获取宽高后可用于计算父 view的宽高.

子View在计算值和模式时 ,如果
Fuview是MeasureSpec.EXACTLY
子View的layoutParams.MATCH_PARENT 则 子View的size 是 父view的值,子view的模式是EXACTLY
子View的layoutParams.WRAP_CONTENT 则子view的size是父view的值,子view的模式是AT_MOST
子view的值为20dp等具体值时,ziview的值就是具体值,子view的模式是EXACTLY
Fuview是MeasureSpec.AT_MOST
子View的layoutParams.MATCH_PARENT 则 子View的size 是 父view的值,子view的模式是AT_MOST
子View的layoutParams.WRAP_CONTENT 则子view的size是父view的值,子view的模式是AT_MOST
子view的值为20dp等具体值时,ziview的值就是具体值,子view的模式是EXACTLY
ziview的值是具体的值 则模式就是EXACTLY
子view的值如果不是具体的值,那么就是父view的值 只是他们的模式不同,父view是AT_MOST 则子view都是AT_MOST
父view是EXACTLY 则只有在子view是WARP_CONTENT是才是AT_MOST ,其他的都是EXACTLY,

onLayout 则 简单一些,通过一些自定view的初始想法,计算view的left,top,right,bottom.即可

margin和padding
自定义view的时候 如果需要计算margin和padding的值时 padding 在子view中就已经计算过了,如果是自己自定义的view则需要在ondraw()方法中自行计算,可以通过getPaddingTop() getPaddingLeft()等方法等添加
父viewgroup中计算margin值 重写下面四个方法 和 类
1 需要继承MarginLayoutParams
2 重写 generateLayoutParams的两个方法
3 重写 geterateDefaultLayoutParams()方法
4 重写 checkLayoutParams 方法

2 事件分发

1 为什么会有事件分发机制
因为andorid 上的view是树形结构的,view可能重叠在一起,当点击的地方有多个view可以响应的时候,这个点击事件该归谁呢,为了解决这个问题,就有了事件分发机制.

2 事件分发的主要对象
有三个,Activity,viewGroup view

用户执行activity 的setContentView方法,内部是phoneWindow的setContentView方法,在phoneWindow中完成DecorView的创建,PhoneWindow是window的实现类
DecorView将屏幕分为2个部位,titleView 和 contentView ,我们时常加载的布局就是contentView

3分发的主要方法
有三个 dispatchTouchEvent,onTouchEvent ,onIntercepteTouchEvent

dispatchTouchEvent 主要用于事件分发,该view决定是由当前view自己处理, 还是分发给子view,让子view递归调用其自身的dispatchTouchEvent来处理.
onInterceptTouchEvent 用来拦截事件的,当父控件下发事件给子控件进行拦截的时候,如果子控件需要对事件进行处理,就要在onInterceptTouchEvent方法中进行拦截,然后到子控件的onTouchEvent方法中进行事件的监听以及逻辑判断,

4onTouchEvent 用于处理传递view的手势事件
四个触摸事件 ,down move up cancel
事件分发流程, activity的 dispatch 到 phonewindow 到 decorView 到 viewgroup 到 view 的 dispatch 到达view的dispathch后开始处理,
并返回 ,首先是 view的ontoucEvent 判断是否处理事件,如果不处理就往上返,viewgroup的ontouchevent 判断是否处理,如果不处理就一直往回返,路经decorview - phoneWindow 最后到 activity的 ontouchevent ,如果都不处理就抛弃该事件 ,这一套流程 用了 ,责任链设计模式
正常情况下 一个 down事件只能被一个view 消耗,当down 事件被消耗后,当一个down事件被view拦截后,该事件序列的后续所有事件都包括一系列move 和 up 都会交给该view进行处理. ontouchEvent 返回true为消耗事件
ViewGroup默认不拦截任何事件,源码中 viewgroup的onInterceptTouchEvent默认返回的是false
view的onTouctEvent 默认是会消耗事件,返回true .除非他是不可点击的(clickable 和 longClickable 同时为false) view的longClickable属性默认是false,clickable 的默认属性要分情况,button的clickable属性默认是true,textview的clickable属性默认为false
通过requestDisallowInterTouchEvent 方法可以在ziview中干预父元素的事分发,但ACTION_DOWN 事件除外
6 注意问题
1 onTouchListener 的优先级高于onTouchEvent 如果onTouchListener返回true,onTouchEvent就不执行,反之则会调用
2 如果事件没有被消费,最后会传给Activity ,如果activity也不需要就被抛弃了
3 onTouchListener > ontouchEvent > onClick 的优先级
4 onClick() 的调用是在 onTouchEvent的ACTION_UP中调用的
5 在 viewgroup中oninterceptTouchEvent 方法对事件进行拦截 返回true 表示拦截,吧事件交给自己处理,执行自己的onTouchEvent方法,返回false标识不拦截事件继续向下传递, 可以在子view中requestDisallInterceptTouchEvent进行修改
6 重写了dispatchTouchEvent方法,dispatchTouchEvent无论返回true 还是false 事件都不在进行分发,只有返回super.dispatchTouchEvent(ev)才表明具有想法层分发的意愿
事件冲突的处理方法,可以分为外部拦截法和内部拦截法

外部拦截法
就是在viewgroup 的oninterceptTouchEvent 进行拦截
即点击事件先进过父容器的拦截处理,如果父容器需要此事件就拦截,不需要就不拦截,需要重写父容器的onInterceptTouchEvent方法,在onInterceptTouchEvent方法中,首先ACTION_DOWN这个事件必须返回false,即不拦截ACTION_DOWN事件,因为一旦父容器拦截了ACTION_DOWN,那么后续的MOVE 和 UP事件都会直接交给父容器处理,其次是ACTION_MOVE事件,根据需求决定是否拦截,最后ACTION_UP事件这里必须返回false,表示不拦截

内部拦截法
是在 view的dispathTouchEvent 进行拦截
所有事件都传递给子元素,如果子元素需要就消耗掉,不需要就交给父元素处理,需要子元素配合requestDisallowInterpectTouchEvent 方法才能正常工作,父元素需要默认拦截除ACTION_DOWN外的事件,这样子元素调用parent.requestDisallowInterpectTouchEvent (false)方法事,父元素才能继续拦截需要的事件,

相关面试问题
1 View 是最后一个处理事件分发的节点了为什么了还会有dispatchTouchEvent呢 是因为,在该方法中,要对view的长按事件,触摸事件,点击事件进行调度处理
2 View 为什么没有 onInterceptTouchEvent呢 因为作为最后处理的一个节点,没有必要拦截了 ,要不就返回ture 表示处理 ,要不就返回false 不处理.
3 简述事件传递的流程
事件都是重activity的dispatchTouchEvent开始传递的
一个事件发生后,首先传递给activity,然后一层一层往下传,从上往下调用dispatchTouchEvent,activity - phonewindow - decorview-viewgroup-view
如果事件传递到最底层还没有被拦截,就会按照反方向回传给activity,从下往上调用onTouchEvent方法,最后会到activity的ontouchEvent方法,如果activity也不消费处理事件,这个事件就会被抛弃.
dispatchTouchEvent方法用于事件的分发,andorid中所有事件都必须进过这个方法的分发,然后决定自身消费当前事件,还是继续往下分发给子控件处理,返回true表示不继续分发了,事件没有被消费,返回false 继续往下分发,如果viewgroup则分发给onInterceptTouchEvent进行判断是否拦截该事件,
onTouchEvent方法用于事件的处理,返回true 表示消费处理当前事件,返回false 则不处理,交给子控件进行继续分发
onInterceptTouchEvent是viewgroup中才有的方法,view中没有,他的作用是负责事件的拦截,返回true的时候表示拦截当前事件,不继续往下分发,交给自身的onTouchEvent进行处理,返回false 则不拦截,继续往下分发,这是viewgroup特有的方法,因为viewgroup中可以能还有子view,而在andorid中view中是不能有子view的
上层view既可以直接拦截事件,自己处理,也可以先询问子view,如果子view需要就交给子view处理,如果子view不需要还能继续交给上层view处理,既保证了事件的有序性,又非常灵活,
事件由父view传递给子view.viewgroup可以通过onInterceptTouchEvent方法对事件进行拦截,停止其向子view传递,
如果view没有对ACTION_DOWN进行消费,之后的其他事件也不会传递过过来,也就是说ACTION_DOWN必须返回true,之后的事件才会传递过来

viewgroup的dispatchTouchEvent 返回 true 事件终结 返回false
调用父view的onTouchEvent 所有 viewgroup想调用自己的onTouchEvent方法,则需要dispatchTouchEvent返回super.dispatchTouchEvent()这时,在super中会调用viewgroup的onInterceptTouchEvent方法,这是可以通过返回true 进行拦截,接下来就会调用viewgroup的onTouchEvent方法了
viewgroup中onInterceptTouchEvent 方法中 super.onInterceptTouchEvent 默认 返回false

View 中的dispatchTouchEvent方法,返回super.dispatchTouchEvent时 才会调用view的onTouchEvent方法, 所有一般不用重写dispatchTouchEvent方法

Viewgroup 第一步判断这个事件是否分发给子view 如果是则调用onInterceptTouchEvent方法返回true,进行拦截,如果这个事件是down事件,那么久需要找一个消费此事件的子view,如果找到则为它创建一个touchTarget,之后的move事件和up事件,都会交给这个子view处理.

当事件被拦截被viewgroup 拦截后,或出现界面跳转等其他情况,当事件流程中断时,viewGroup会发送一个ACTION_CANCEL事件给view.

多指触控通过之前创建的touchTarget 中的id 进行数据保存,