zoukankan      html  css  js  c++  java
  • Android view的一些认识

    转载:9102年末,我对Android view的13条认识;

    顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找
    https://github.com/xiangjiana/Android-MS

     

    1.LinearLayout布局,会不会走onDraw()方法?
    明framelayout是viewgroup的子类,而viewgroup是view的子类,在自定义view的时候重写ondraw方法可以画出各式各样的view为什么在framelayou里ondraw方法就不执行呢?
    经过一番查阅资料,发现原因简单来说是这样的,google工程师们是这么分类的,viewgroup是属于容器,view属于内容,于是容器里面的ondraw方法默认设置为不执行。
    解决方法如下:
    1,在构造函数里面,给其设置一个颜色,如#00000000。
    自定义了一个集成LinearLayout的控件,但是如果xml不设置background属性,onDraw不会被调用~
    2,在构造函数里面,调用setWillNotDraw(false)。

    ViewGroup默认情况下,出于性能考虑,会被设置成WILL_NOT_DROW,这样,ondraw就不会被执行了。
    如果我们想重写一个viewgroup的ondraw方法,有两种方法:
    1,构造函数中,给viewgroup设置一个颜色。
    2,构造函数中,调用setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。
    在viewgroup初始化的时候,它调用了一个私有方法:initViewGroup,它里面会有一句setFlags(WILLL_NOT_DRAW,DRAW_MASK);相当于调用了setWillNotDraw(true),所以说,对于ViewGroup,他就认为是透明的了,如果我们想要重写onDraw,就要调用setWillNotDraw(false)。

     
    一丶 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, 进入ViewGroupdispatchTouchEvent()

    执行 onInterceptTouchEvent 方法判断是否拦截,再不拦截的情况下,此时会遍历 ViewGroup 的子元素,进入子 View 的dispatchToucnEvent 方法。

    如果子 view 设置了 onTouchListener, 就执行 onTouch方法,并根据 onTouch 的返回值为 true 还是 false 来决定是否执行 onTouchEvent方法,

    如果是 false 则继续执行 onTouchEvent。 在onTouchEvent 的 Action Up 事件中判断,如果设置了 onClickListener , 就执行 onClick 方法。

    View 事件传递分发机制?

    • View 事件分发本质就是对 MotionEvent 事件分发的过程。即当一个 MotionEvent 发生后,系统将这个点击事件传递到一个具体的 View 上
    • 点击事件的传递顺序: Activity( Window)→ViewGroup→ View
    • 事件分发过程由三个方法共同完成
      • dispatchTouchEvent:用来进行事件的分发。如果事件能够传递给当前 View,那么此方法一定会被调用,返回结果受当前 View 的 onTouchEvent 和下级View 的 dispatchTouchEvent 方法的影响,表示是否消耗当前事件
      • onInterceptTouchEvent:在上述方法内部调用,对事件进行拦截。该方法只在 ViewGroup 中有,View(不包含 ViewGroup)是没有的。一旦拦截,则执行 ViewGrouponTouchEvent,在 ViewGroup 中处理事件,而不接着分发给 View。且只调用一次,返回结果表示是否拦截当前事件
      • onTouchEvent: 在 dispatchTouchEvent 方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件
    三丶 View的加载流程

       1.1 View 随着 Activity 的创建而加载,startActivity 启动一个 Activity 时,在
       ActivityThreadhandleLaunchActivity()会执行 Activity 的 onCreate 方法,这个时候会调用 setContentView 加载布局,

          创建出 DecorView  并将我们的 layout加载到 DecorView 中。

    1.2 当执行到 handleResumeActivity 时,Activity 的 onResume方法被调用, 然后 WindowManager 会将 DecorView 设置给 ViewRootImpl,

    这样,DecorView就被加载到Window中了,此时界面还没有显示出来, 还需要经过 View的 measure,layout 和 draw 方法,才能完成 View 的工作流程。

     View的绘制 是  由ViewRoot来负责的,每一个DecorView都有一个与之关联的ViewRoot, 这种关联关系是由WindowManager 维护的,

    1.3 将DecorViewViewRoot 关联之后,

    ViewRootImpl的 requestLayout会被调用 以完成初步布局,通过scheduleTraversals方法向主线程发送消息请求遍历,

    最终调用ViewRootImplperformTraversals方法, 这个方法会执行 View 的 measure layout 和 draw 流程。

    四丶 自定义view 需要注意的几点

    1. 让 view 支持 wrap_content 属性,在 onMeasure 方法中针对 AT_MOST 模式做专门处理,否则 wrap_content 会和 match_parent 效果一样(继承 ViewGroup 也同样要在 onMeasure 中做这个判断处理)

    if (widthMeasureSpec == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {
       setMeasuredDimension( 200 , 200 ); 
       // wrap_content
    

    情况下要设置一个默认值,200 只是举个例子,最终的值需要计算得到刚好包裹内容的宽高值

       } else if (widthMeasureSpec == MeasureSpec.AT_MOST) {
            setMeasuredDimension( 200 ,heightMeasureSpec );
       } else if (heightMeasureSpec == MeasureSpec.AT_MOST) {
             setMeasuredDimension(heightMeasureSpec , 200 );
       }
    

    2.让 view 支持 padding( onDraw 的时候, 宽高减去 padding 值, margin 由父布局控制,不需要 view 考虑),

         自定义 ViewGroup 需要考虑自身的 padding 和子view的 margin 造成的影响。
    3.在 view 中尽量不要使用 handler,使用 view 本身的 post 方法
    4.在 onDetachedFromWindow 中及时停止线程或动画
    5.view 带有滑动嵌套情形时,处理好滑动冲突

    五丶View 的  measure(),  layout()   和 draw()?

    在上边的分析中我们知道,View 绘制流程的入口在 ViewRootImplperformTraversals 方法,在方法中首先调用 performMeasure 方法,

    传入一个childWidthMeasureSpecchildHeightMeasureSpec 参数,这两个参数代表的是DecorViewMeasureSpec 值,

    这个 MeasureSpec 值由窗口的尺寸和 DecorViewLayoutParams 决定,最终调用 View 的 measure 方法进入测量流程

    measure :
    View 的 measure 过程由 ViewGroup 传递而来,在调用 View.measure 方法之前, 会首先根据 View 自身的 LayoutParams 和 父布局的MeasureSpec,

    确定子 view 的MeasureSpec,然后将 view 宽高对应的 measureSpec 传递到 measure 方法中,那么子 view 的 MeasureSpec 获取规则是怎样的?

    分几种情况进行说明

    1.父布局是 EXACTLY 模式:

    a. 子 view 宽或高是个确定值,那么子 view 的 size 就是这个确定值,mode是 EXACTLY(是不是说子 view 宽高可以超过父 view?见下一个)
    b. 子 view 宽或高设置为 match_parent, 那么子 view 的 size 就是占满父容器剩余空间,模式就是 EXACTLY
    c. 子 view 宽或高设置为 wrap_content,  那么子 view 的 size 就是占满父容器剩余空间,不能超过父容器大小,模式就是 AT_MOST

    2.父布局是 AT_MOST 模式:

    a. 子 view 宽或高是个确定值,那么子 view 的 size 就是这个确定值,mode 是EXACTLY
    b. 子 view 宽或高设置为 match_parent,那么子 view 的 size 就是占满父容器剩余空间,不能超过父容器大小,模式就是 AT_MOST
    c. 子 view 宽或高设置为 wrap_content, 那么子 view 的 size 就是占满父容器剩余空间,不能超过父容器大小,模式就是 AT_MOST

    3.父布局是 UNSPECIFIED 模式:

    a. 子 view 宽或高是个确定值,那么子 view 的 size 就是这个确定值,mode 是EXACTLY
    b. 子 view 宽或高设置为 match_parent,那么子 view 的 size 就是 0,模式就是UNSPECIFIED
    c. 子 view 宽或高设置为 wrap_content,那么子 view 的 size 就是 0,模式就是UNSPECIFIED

     获取到宽高的 MeasureSpec 后,传入 view 的 measure 方法中来确定 view 的宽高,这个时候还要分情况

    1.当 MeasureSpec 的 mode 是 UNSPECIFIED,  此时 view 的宽或者高要看 view 有没有设置背景,

     如果没有设置背景,就返回设置的 minWidthminHeight, 这两个值如果没有,设置默认就是 0,

     如果 view 设置了背景,就取 minWidthminHeight 和 背景 这个 drawable 固有宽或者高 ,中的最大值返回;
    2.当 MeasureSpec 的 mode 是 AT_MOST 和 EXACTLY,此时 view 的宽高都返回从MeasureSpec 中获取到的 size 值,这个值的确定见上边的分析。

    因此如果要通过继承 view 实现自定义 view,一定要重写 onMeasure 方法对 wrap_conten 属性做处理,

    否则,他的 match_parentwrap_content 属性效果就是一样的

    layout:
    layout()是用来确定 view 本身的位置,onLayout()确定所有子元素的位置。

    ViewGroup 的位置确定之后, 它在 onLayout 中会遍历所有的子元素并调用其 layout 方法,在子元素的 layout 方法中 onLayout 方法又会被调用。

    layout 方法的流程是,首先通过 setFrame 方法确定 view 四个顶点的位置,然后view 在父容器中的位置也就确定了,

    接着会调用onLayout 方法,确定子元素的位置,onLayout 是个空方法,需要继承者去实现。

    getMeasuredHeightgetHeight方法有什么区别?
    getMeasuredHeight (测量高度)形成于 view 的 measure 过程,getHeight(最终高度)形成于 layout 过程,

    在有些情况下,view 需要 measure 多次才能确定测量宽高,在前几次的测量过程中,得出的测量宽高有可能和最终宽高不一致,

    但是最终来说,还是会相同。  有一种情况会导致两者值不一样,如下,此代码会导致 view 的最终宽高比测量宽高大100PX。

      public void layout(int l,int t,int r, int b) {
         super.layout(l,t,r+100,b+100); {
     }
    

    View 的绘制过程遵循如下几步:

    a.绘制背景 background.draw(canvas)
    b.绘制自己(onDraw)
    c.绘制 children(dispatchDraw)
    d.绘制装饰(onDrawScrollBars)

    View  绘制过程的传递是通过 dispatchDraw 来实现的,它会遍历所有的子元素的draw 方法,如此 draw 事件就一层一层的传递下去了

    ps: view 有一个特殊的方法 setWillNotDraw,如果一个 view 不需要绘制内容,即不需要重写 onDraw 方法绘制,可以开启这个标记,系统会进行相应的优化。

    默认情况下,View 没有开启这个标记,默认认为需要实现 onDraw 方法绘制。

    当我们继承 ViewGroup 实现自定义控件,并且明确知道不需要具备绘制功能时,可以开启这个标记,如果我们重写了 onDraw, 那么要显示的关闭这个标记。

    子 view 宽高可以超过父 view ?能

    1. android:clipChildren = "false" 这个属性要设置在父 view 上。代表其中的子View 可以超出屏幕。
    2. 子 view 要有具体的大小,一定要比父 view 大 才能超出。比如 父 view 高度100px 子 view 设置高度 150px。子 view 比父 view 大,这样超出的属性才有意义。(高度可以在代码中动态赋值,但不能用 wrap_content / match_partent)。
    3. 对父布局还有要求,要求使用 linearLayout(反正我用 RelativeLayout 是不行)。你如果必须用其他布局,可以在需要超出的 view 上面套一个linearLayout 外面再套其他的布局。
    4. 最外面的布局如果设置的 padding 不能超出

    六丶MotionEvent 是什么? 包含几种事件? 什么条件下会产生?

    MotionEvent 是手指接触屏幕后所产生的一系列事件。典型的事件类型有如下:

    ACTION_DOWN:手指刚接触屏幕
    ACTION_MOVE:手指在屏幕上移动
    ACTION_UP:手指从屏幕上松开的一瞬间
    ACTION_CANCELL:手指保持按下操作,并从当前控件转移到外层控件时触发

    正常情况下,一次手指触摸屏幕的行为会触发一系列点击
    事件,考虑如下几种情况:

    点击屏幕后松开,事件序列:DOWN→UP
    点击屏幕滑动一会再松开,事件序列为DOWN→MOVE→.....→MOVE→UP

    七丶如何解决View 的事件冲突

    参考回答:
    常见开发中,事件冲突的有 ScrollViewRecyclerView 滑动冲突、 RecyclerView 内嵌同时滑动同一方向

    滑动冲突的处理规则:

    • 对于由于外部滑动和内部滑动,方向不一致导致的滑动冲突,可以根据滑动的方向 判断谁来拦截事件。
    • 对于由于外部滑动方向和内部滑动,方向一致导致的滑动冲突,可以根据业务需求,规定何时让外部View 拦截事件,何时由内部 View 拦截事件。
    • 对于上面两种情况的嵌套,相对复杂,可同样根据需求在业务上找到突破点。

    滑动冲突的实现方法:

    • 外部拦截法: 指点击事件都先经过父容器的拦截处理,如果父容器需要此事件,就拦截,否则就不拦截。
      具体方法: 需要重写父容器的onInterceptTouchEvent 方法,在内部做出相应的拦截。
    • 内部拦截法: 指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件就直接消耗,否则就交由父容器进行处理。
      具体方法:需要配合 requestDisallowInterceptTouchEvent 方法
    八丶Scroller 是怎么实现View的弹性滑动

    参考回答:

    • MotionEvent.ACTION_UP 事件触发时调用startScroll()方法,该方法并没有进行实际的滑动操作,而是记录滑动相关量(滑动距离、滑动时间)
    • 接着调用 invalidate/postInvalidate()方法,请求 View重绘,导致 View.draw 方法被执行
    • 当 View 重绘后会在 draw 方法中调用 computeScroll 方法,而 computeScroll 又会去向 Scroller 获取当前的scrollXscrollY
    • 然后通过 scrollTo 方法实现滑动;接着又调用 postInvalidate 方法来进行第二次重绘,和之前流程一样,如此反复导致 View 不断进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,直到整个滑动过成结束
       
       
    九丶 invalidate()和 postInvalidate() 的区别 ?

    invalidate()postInvalidate()都用于刷新 View,

    主要区别是 invalidate()在主线程中调用,若在子线程中使用需要配合 handler; 而 postInvalidate()可在子线程中直接调用。

    十丶 SurfaceView 和 View 的区别?
    • View 需要在 UI 线程对画面进行刷新,而 SurfaceView 可在子线程进行页面的刷新
    • View 适用于主动更新的情况, 而 SurfaceView 适用于被动更新,如频繁刷新,这是因为如果使用 View 频繁刷新会阻塞主线程,导致界面卡顿。
    • SurfaceView 在底层已实现--双缓冲机制,而 View 没有,因此 SurfaceView 更适用于需要频繁刷新、刷新时数据处理量很大的页面(如视频播放界面)
    十一丶 scrollTo()和 scollBy() 的区别?
    • scollBy 内部调用了 scrollTo,它是基于当前位置的相对滑动; 而 scrollTo 是绝对滑动;
    •      因此如果使用相同输入参数多次调用 scrollTo 方法,由于 View 的初始位置是不变的,所以只会出现一次 View 滚动的效果
    • 两者都只能对 View内容的滑动,而非使View本身滑动。   可以使用 Scroller 有过度滑动的效果
    十三丶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 方法实现滑动的效果 。

    十三丶自定义View 如何考虑机型适配?
    • 合理使用 warp_content,match_parent
    • 尽可能的是使用 RelativeLayout
    • 针对不同的机型,使用不同的布局文件,放在对应的目录下,android 会自动匹配。
    • 尽量使用点 9 图片。
    • 使用与密度无关的像素单位:  dp,sp
    • 引入 android 的百分比布局。
    • 切图的时候切大分辨率的图,应用到布局当中。 在小分辨率的手机上也会有很好的显示效果。
  • 相关阅读:
    BooStrap4文档摘录: 1. Layout
    PistgreSQL9.6手册(基础摘录)
    HTML 5与CSS 3权威指南(第3版·上册)
    Excel PPT 2013办公应用从入门到精通
    EclipseWTPWeb应用开发
    Photoshop移动UI设计实用教程(第2版)
    高手速成:EDIUS专业级视频与音频制作从入门到精通
    黑客攻防从入门到精通
    利用Python进行数据分析(原书第2版)
    Linux二进制分析
  • 原文地址:https://www.cnblogs.com/awkflf11/p/12085334.html
Copyright © 2011-2022 走看看