一、View 创建过程
1.xml 布局
setContentView 加载布局创建出 DecorView 并将我们的 layout加载到 DecorView 中,当执行到 handleResumeActivity 时,Activity 的 onResume方法被调用,然后 WindowManager 会将 DecorView 设置给 ViewRootImpl,这样,DecorView就被加载到Window中了,此时界面还没有显示出来,还需要经过 View的 measure,layout 和 draw 方法,才能完成 View 的工作流程。我们需要知道 View的绘制是由ViewRoot来负责的,每一个DecorView都有一个与之关联的ViewRoot,这种关联关系是由WindowManager 维护的,将DecorView和 ViewRoot 关联之后,ViewRootImpl的requestLayout会被调用以完成初步布局,通过scheduleTraversals方法向主线程发送消息请求遍历,最终调用ViewRootImpl 的 performTraversals方法,这个方法会执行 View 的 measure layout 和 draw 流程
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
2.代码创建view
方式一:
LayoutInflater inflater = (LayoutInflater) Context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view= =inflater.inflate(R.layout.custom_dialog,null);
方式一:
View view=LayoutInflater.from(this).inflate(R.layout.ID, null);
方式三:
View view = View.inflate(this, R.layout.dialog_layout, null);
inflate方法一般接收俩个参数,第一个参数就是要加载的布局,第二个参数是指给该布局的外部再嵌套一层父布局,如果不需要就直接传null。这样就成功创建了一个布局的实例,之后再将它添加到指定位置就可以显示出来了。
inflate()方法还有个接收三个参数的方法重载,结构如下:
inflate(intresource, ViewGroup root,booleanattachToRoot)
那么这第三个参数attachToRoot又是什么意思呢?其实如果你仔细去阅读上面的源码应该可以自己分析出答案,这里我先将结论说一下吧,感兴趣的朋友可以再阅读一下源码,校验我的结论是否正确。
如果root为null,attachToRoot将失去作用,设置任何值都没有意义。
如果root不为null,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,即root。
如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效。
在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。
过程:
第一个参数为int 的方法执行
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);
...
return inflate(parser, root, attachToRoot);
这四种参数的方法最终都会调用
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {}
并且在这个方法里
最终调用 root ViewGroup 的addview()方法添加当前view。
二、View 绘制流程
参考答案
View 的工作流程主要是指 measure、layout、draw 这三大流程,即测量、布局和绘制,其中 measure 确定 View 的测量宽/ / 高,layout 确定 View 的最终宽/ / 高和 四个顶点的位置,而 draw 则将 View绘制到屏幕上
View 的绘制过程遵循如下几步:
绘制背景 background.draw(canvas)
绘制自己(onDraw)
绘制 children(dispatchDraw)
绘制装饰(onDrawScollBars)
三、View事件分发
点击事件产生后,首先传递给 Activity 的 dispatchTouchEvent 方法,通过PhoneWindow 传递给 DecorView,然后再传递给根 ViewGroup,进入 ViewGroup 的dispatchTouchEvent 方法,执行 onInterceptTouchEvent 方法判断是否拦截,再不拦截的情况下,此时会遍历 ViewGroup 的子元素,进入子 View 的dispatchToucnEvent 方法,如果子 view 设置了 onTouchListener,就执行 onTouch方法,并根据 onTouch 的返回值为 true 还是 false 来决定是否执行 onTouchEvent方法,如果是 false 则继续执行 onTouchEvent。在onTouchEvent 的 Action Up 事件中判断,如果设置了 onClickListener ,就执行 onClick 方法。
点击事件的传递顺序: Activity( Window)→ViewGroup→ View
事件分发过程由三个方法共同完成
dispatchTouchEvent:用来进行事件的分发。如果事件能够传递给当前 View,那么此方法一定会被调用,返回结果受当前 View 的 onTouchEvent 和下级View 的 dispatchTouchEvent 方法的影响,表示是否消耗当前事件
onInterceptTouchEvent:在上述方法内部调用,对事件进行拦截。该方法只在 ViewGroup 中有,View(不包含 ViewGroup)是没有的。一旦拦截,则执行 ViewGroup 的 onTouchEvent,在 ViewGroup 中处理事件,而不接着分发给 View。且只调用一次,返回结果表示是否拦截当前事件
onTouchEvent: 在 dispatchTouchEvent 方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件
四、获取View 的宽高
在onCreate方法里面view调用getWidth、getMeasuredWidth获得长宽值的,始终为0。因为在onCreate中。我们的控件事实上还并没有画好,换句话说,等onCreate方法运行完了,我们定义的控件才会被度量(measure),所以我们在onCreate方法里面通过view.getHeight()获取控件的高度或者宽度肯定是0。
方法一、
int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
textView.measure(w, h);
int height = textView.getMeasuredHeight();
int width = textView.getMeasuredWidth();
方法二、
这种方法。我们须要注冊一个ViewTreeObserver的监听回调,这个监听回调,就是专门监听画图的,既然是监听画图,那么我们自然能够获取測量值了,同一时候。我们在每次监听前remove前一次的监听。避免反复监听。
final ViewTreeObserver vto = textView.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
textView.getViewTreeObserver().removeOnPreDrawListener(this);
int height = textView.getMeasuredHeight();
int width = textView.getMeasuredWidth();
return true;
}
});
方法三、
这种方法于第2个方法基本同样,但他是全局的布局改变监听器,所以是最推荐使用的。
ViewTreeObserver vto1 = textView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
textView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int height = textView.getMeasuredHeight();
int width = textView.getMeasuredWidth();
}
});
五、View 的滑动冲突
1.滑动冲突的处理规则:
对于由于外部滑动和内部滑动方向不一致导致的滑动冲突,可以根据滑动的方向判断谁来拦截事件。
对于由于外部滑动方向和内部滑动方向一致导致的滑动冲突,可以根据业务需求,规定何时让外部View 拦截事件,何时由内部 View 拦截事件。
滑动冲突的实现方法:
外部拦截法: 指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则就不拦截。
具体方法:需要重写父容器的onInterceptTouchEvent 方法,在内部做出相应的拦截。
内部拦截法: 指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件就直接消耗,否则就交由父容器进行处理。
具体方法:需要配合requestDisallowInterceptTouchEvent 方法
说明
MotionEvent 是手指接触屏幕后所产生的一系列事件。典型的事件类型有如下:
ACTION_DOWN:手指刚接触屏幕
ACTION_MOVE:手指在屏幕上移动
ACTION_UP:手指从屏幕上松开的一瞬间
ACTION_CANCELL:手指保持按下操作,并从当前控件转移到外层控件时触发
1.onInterceptTouchEvent返回值为true
当调用ViewGroup的onInterceptTouchEvent后返回值为true,则表示当前ViewGroup拦截了此TouchEvent事件,此ViewGroup的onTouchEvent会收到回调;
2.onInterceptTouchEvent返回值为false
如果返回值为false,则调用dispatchTransformedTouchEvent,去寻找此Point上hit到的子View,如果寻找到子View,则调用子View的dispatchTouchEvent事件,否则就调用super.dispatchTouchEvent,即调用View的dispatchTouchEvent实现,在此会调用到onTouchEvent函数去处理此TouchEvent事件。
3.onInterceptTouchEvent总结
onInterceptTouchEvent流程为父ViewGroup->子ViewGroup->孙ViewGruop,如果其中一个ViewGroup拦截了事件,则此ViewGroup,则此ViewGroup直接处理OnTouchEvent事件,且TouchEvent不在往下dispatch,而是开始return。
4.onTouchEvent返回值为true
如果返回值为true,则此TouchEvent被处理完毕
5.onTouchEvent返回值为false
如果为false,则return给父ViewGroup,父ViewGroup会继续交给此ViewGroup的兄弟View处理。
- requestDisallowInterceptTouchEvent
子View在onInterceptTouchEvent的ACTION_DOWN之后调用requestDisallowInterceptTouchEvent(true),则此子View的所有父ViewGroup会跳过onInterceptTouchEvent回调
六、View 的滑动方式
a. layout(left,top,right,bottom):通过修改 View 四个方向的属性值来修改 View 的坐标,从而滑动 View
b. offsetLeftAndRight() offsetTopAndBottom():指定偏移量滑动 view
c. LayoutParams,改变布局参数:layoutParams 中保存了 view 的布局参数,可以通过修改布局参数的方式滑动 view
d. 通过动画来移动 view:注意安卓的平移动画不能改变 view 的位置参数,属性动画可以
e. scrollTo/scrollBy:注意移动的是 view 的内容,scrollBy(50,50)你会看到屏幕上的内容向屏幕的左上角移动了,这是参考对象不同导致的,你可以看作是它移动的是手机屏幕,手机屏幕向右下角移动,那么屏幕上的内容就像左上角移动了
f. scroller:scroller 需要配置 computeScroll 方法实现 view 的滑动,scroller 本身并不会滑动 view,它的作用可以看作一个插值器,它会计算当前时间点 view 应该滑动到的距离,然后 view 不断的重绘,不断的调用 computeScroll 方法,这个方法是个空方法,所以我们重写这个方法,在这个方法中不断的从 scroller 中获取当前 view 的位置,调用 scrollTo 方法实现滑动的效果
参考:https://www.jianshu.com/p/c00a22fc1646
最后更新 2020年3月10日 21:58:53