Android View 笔记

一、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又是什么意思呢?其实如果你仔细去阅读上面的源码应该可以自己分析出答案,这里我先将结论说一下吧,感兴趣的朋友可以再阅读一下源码,校验我的结论是否正确。

  1. 如果root为null,attachToRoot将失去作用,设置任何值都没有意义。

  2. 如果root不为null,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,即root。

  3. 如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效。

  4. 在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。

过程:


Android View 笔记_第1张图片
image.png

第一个参数为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处理。

  1. 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

你可能感兴趣的:(Android View 笔记)