View

作者: Dream_Book | 来源:发表于2020-03-25 18:16 被阅读0次

    文章仅做个人笔记使用:

    是所有控件的基类,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.依据水平和垂直方向的速度差来做判断

    如果在水平滑动停止之前用户迅速垂直滑动怎么办?

    这个时候就会导致界面水平方向无法滑动到终点。避免这种状态的发生时,当水平方向正在滑动时,下个序列的点击事件还交给父容器处理。

    相关文章

      网友评论

          本文标题:View

          本文链接:https://www.haomeiwen.com/subject/bzftuhtx.html