文章仅做个人笔记使用:
是所有控件的基类,ViewGroup同样也继承了View。通过四个参数确定他在父容器中的位置,左上角(left,top)右下角(right,bottom)。
MotionEvent:触摸事件。down,move,up
getX/getY:获取相对于View的坐标
getRawX/getRawY:获取相对于手机屏幕的坐标
TouchSlop:系统能识别出的最小滑动距,不同的设备值不同。
View的滑动
第一种:通过View 本身提供的scrollTo/scrollBy方法实现滑动。
第二种:通过动画给View增加平移效果达到滑动。
第三种:通过改变View的LayoutParams使得View重新布局从而实现滑动。
scrollTo和scrollBy的区别:
- scrollBy内部调用了scrollTo。scrollBy是基于当前位置的相对滑动。scrollTo是绝对滑动,因此如果使用相同输入参数多次调用scrollTo,由于View的初始位置是不变的,所以只会出现一次View滚动的效果。
- 两者只能对view中的内容滑动而非View本身滑动。
区别:
第一种只能滑动view内容不能滑动View本身,例如一个TextView通过scrollTo移动,只会将其中的文字内容移动,TextView并不会移动。第二种如果可以使用属性动画就可以避免交互问题,而且复杂的移动效果动画有明显优势。第三种操作有些复杂。
View的事件分发机制:
主要方法:
dispatchTouchEvent():用来进行事件分发,如果事件能够传递给该View此方法一定会被调用,返回结果受当前View的onTouchEvent()和下级View的dispatchTouchEvent()决定。表示是否消耗该事件。
onInterceptTouchEvent():截听:在dispatchTouchEvent()内部调用,判断是否拦截该事件,如果当前View进行拦截,那么在同一个事件序列当中该方法不会再被调用。
onTouchEvent():在dispatchTouchEvent()中调用,用来处理事件,返回结果表示是否消耗该事件,如果不消耗,该View在同一个时间序列中不会再接收到事件。
三者的关系:事件的传递过程都是由外而内的,当一个事件产生后总是会先传递给Activity,Activity会传递给Window(是抽象的,唯一实现是phoneWindow),Window会传给DecorView,进而传给顶级View。当一个根View接收到一个事件后,他的dispatchTouchEvent()会被调用,如果他的onInterceptTouchEvent()方法返回true则表示他要拦截该事件,接着他的onTouchEvent就会被调用。如果onInterceptTouchEvent()返回false表示不拦截该事件,那么该View的子View的dispatchTouchEvent就会被调用。如果没有子View那么他的父View的onTouchEvent()就会被调用,如果父View的onToucheEvent也返回false,那么事件就会继续往上传递最终会被Activity的onTouchEvent()处理。
如果一个View设置OnTouchListener那么是否消耗事件还需要看OnTouchListener中onTouch方法的返回值,如果返回false则onTouchEvent会被调用,如果返回true则onTouchEvent不会被调用。
ViewGroup默认不拦截任何事件,源码中ViewGroup的onInterceptTouchEvent()默认返回false.
View是没有onInterceptTouchEvent()方法的,一旦有事件传入就会调用onTouchEvent(),onTouchEvent默认都会消耗掉事件,除非设置不可点击。View的longClickable默认都是false,clickable需要区分,button默认为true,textVIew默认为false.
- 事件序列:从手指触摸屏幕到离开屏幕这个过程中产生的一些列事件。
- DecorView继承自FrameLayout他的子view 一般情况下是一个LinerLayout分上下两部分上部分标题栏下面是内容栏。通过setContentView()设置的View是他(内容栏)的一个子元素,也就是顶级元素,顶级元素一般都是ViewGroup。
- 通过requestDisallowInterceptTouchEvent()可以在子元素中敢于父元素的的事件分发过程。ACTION_DOWN事件除外。
- 是否能够接收事件:
1.子元素是否在播放动画。
2.事件是否落在子元素的区域内。
View的绘制流程
三大流程通过ViewRoot(对应ViewRootImpl,是DecorView和WindowManger的纽带)完成的,是从ViewRoot的performTraversals(是否需要重新计算measure、是否重新安置是否位置、是否重新绘制)开始的,依次执行
- performMeasure(measure----onMeasure)
- performLayout(layout----onLayout)
- performDraw(draw----onDraw)。
measure:
用来测量view的宽高。
获取宽:getMeasureWidth
获取高:getMeasureHeight
在大多数情况下这是View的最终宽高。
MeasureSpec:
32位的int值,前两位SpecMode(测量模型),后30位SpecSize(测量模型下的规格大小)。
父容器影响View的MeasureSpec创建,在测量过程中,系统会将View的LayoutParams根据父容器施加的规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽高。
SpecMode类型:
1.UNSPECIFIED:父容器不对子View做任何限制,要多大给多大。
2.EXACTLY:父容器已经检测出View所需要的精确大小,这种模式下,SpecSize就是View的最终大小。对应的就是LayoutParams中的match_partent和具体值。
3.AT_MOST:父容器指定了一个固定大小(SpecSize),子View的宽高不能超过指定的大小,具体情况看不同View的实现。对应的是LayoutParams中的wrap_content
- 普通View的MeasureSpec由父容器的MeasureSpce和自身的LayoutParams决定的,还有自身的padding和margin。
- DecorView的MeasureSpec由窗口的尺寸和自身的layoutParams决定。
MeasureSpec确定以后onMeasure()就可以测量View的宽高。
view的measure过程:
View的测量过程通过measure方法完成,measure会调用OnMeasure来完成,通过setMeasuredDimension()设置View宽高的测量值。
viewGroup的measure过程:
除了完成自己的measure过程以外,还会遍历调用所有子元素measure方法,各个子元素去递归执行这个过程。和View不同的是ViewGroup是一个抽象类
在Activity的onCreater、onStart、onResume中均无法获得View的宽高?
是因为View的measure过程和Activity生命周期不是同步执行的。
解决方案:
1.View初始化完成后会调用onWindowFocusChanged(),但是该方法会被调用很多次,当Activity得到或者失去焦点都会被调用。
2.view.Post(new Runnable()),将一个runnable投递到消息队列尾部,等looper调用的时候View也完成了初始化。
3.ViewTreeObserver()当view树放生改变也是获取的好时候,但是同样会有多次,因为只要View 树改变就会调用。
layout:
用来确定View在父容器中的放置位置及四个定点的位置以及实际View的宽高。
获取定点:getTop、getBottom、getLeft、getRight。
获取宽:getWidth
获取高:getHight
在这里获取的宽高是View的最终宽高。
ViewGroup如何摆放自己的子元素?
layout方法通过setFrame会确定View的四个定点位置,完成View的摆放,接着会调用OnLayout方法会确定所有子元素的位置,View和ViewGroup都没有真正实现onLayout。遍历所有子元素调用setChildFrame,而setChildFrame只是调用了子元素的layout方法,进而调用setFrame确定位置,经过层层遍历完成View树的摆放。
为什么说measure中测量的值不是View的最终大小?
在View的默认实现中,测量宽高和最终宽高是一致的。但在某些特定环境下:1.重写layout方法,对定点做更改,虽然没有什么实际意义但是确实导致测量值和最终值不一样。2.在某些measure需要重复测量的情况下,在前几次的过程中和最终值会有误差存在。
draw:
负责将View绘制在屏幕上,只有Draw方法完成后才能呈现在屏幕上。
绘制流程:
1.绘制背景background.draw(canvas)
2.绘制自己onDraw
3.绘制子元素(dispatchDraw)
4.绘制装饰
View的刷新机制:
-
requestLayout:如果一个view的大小、形状和位置发生变化,就需要对这个view重新进行测量和布局,但不会进行绘制。
子View调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用PerformTraversals开始三大流程的工作,从measure开始,对于每一个含有标记位的view及其子View都会进行测量、布局、绘制。 -
invalidate:该方法的调用会引起View树的重绘,不会引起View重新测量和布局,并且只会重绘调用者本身。常用于内部调用(比如 setVisiblity())或者需要刷新界面的时候,需要在主线程(即UI线程)中调用该方法。
-
postInvalidate:和invalidate区别在于,在非UI线程调用,如果要在子线程中调用invalidate,需要配合Handler。
View的加载流程:
view随着Activity的创建而加载,startActivity()启动一个Activity时,在Activity的handleLaunchActivity()中调用Activity的OnCreate(),在onCreate()中调用setContentView()来创建DecorView,并将layout加载道根布局中,当进入handleResumeActivity()时,Activity的onResume()方法会被调用,然后WindowManager会将根布局设置给ViewRootImpl,这样根布局就被加载到Window中了,此时界面还要通过View的measure,layout,draw才能完成View的工作流程。
View的绘制是有ViewRoot来负责的,每一个DecorView都有一个与之关联的ViewRoot,这种关联由WindowManger来维护,将根布局和ViewRoot关联之后,ViewRootImpl的requedtLayout会被调用完成初步布局,通过sucheduleTraversals()向主线程发送遍历请求,最终调用ViewRootImpl的PerformTraversals()完成view的measure、layout、draw。
自定义View的分类?
- 继承View:需要自己支持wap_content和padding;
- 继承ViewGroup:需要自行处理ViewGroup的测量和布局,并同时处理自元素的测量和布局。
- 继承特定的View:一般用来对一些原生的view进行扩展,比如不希望ViewPager左右滑动,就可以继承ViewPager然后重写ScrollTo()禁止他滑动。
- 继承特定的ViewGroup
为什么要在继承View后支持warp_content?
- 如果直接继承View或者ViewGroup需要自在OnMeasur()中设置warp_content时自身的大小,否则在使用过程中设置warp_content是不起作用的,效果和设置match_parent是一样的。因为在View中使用warp_content他的specMode是AT_MOST,这种模式下View的大小就是specSize,也就是父容器中现在剩余的大小,在这种模式下match_parent也是如此。
自定义View需要注意些什么?
- 尽量不要在View中使用Handler,因为View内部本身提供了Post系列方法。
- 在合适的时候关闭线程和动画,否则会造成内存泄漏。比如OnDetachedFromWindow()方法,该方法会在包含该View的Acticity退出或者该View被Remove()的时候会调用。对应的方法是OnAttachedToWindow();
- View带有滑动嵌套情景时,需要处理好滑动冲突。
- 不要再onDraw()中创建对象,onDraw()会被频繁调用,导致Gc频频回收,导致内存抖动。在view的构造函数中进行创建对象。
- 降低View的刷新频率,尽可能减少不必要的invalidate()调用,尽量调用带参的invalidate(),无参是会刷新整个View,带参则是制定性局部刷新。
- 在Activity尽可能保存自己的状态和属性。
View的滑动冲突如何产生?
界面中内外两层同时可以滑动,就会产生滑动冲突。
- 外部和内部滑动方向不一致
- 外部和内部滑动方向一致
- 多层多个方向滑动
如何解决View滑动冲突?
- 通过判定滑动方向为水平方向还是垂直方向来决定由谁来拦截事件。
如何判定滑动方向?
1.依据滑动路径和水平方向的夹角
2.依据水平方向和垂直方向上的距离差来判断
3.依据水平和垂直方向的速度差来做判断
如果在水平滑动停止之前用户迅速垂直滑动怎么办?
这个时候就会导致界面水平方向无法滑动到终点。避免这种状态的发生时,当水平方向正在滑动时,下个序列的点击事件还交给父容器处理。
网友评论