一View的工作原理

目录

一、View的工作原理:

● View的事件体系:

1. MotionEvent:

2.TouchSlop:

● View的滑动

◎ View滑动的三种方式:

二、View的绘制流程:

● View常见的回调方法:

● View的滑动,解决滑动冲突。

● 自定义View的几种固定类型:

1.ViewRoot和DecorView:

2.MeasureSpec:

3.DecorView和View的测量区别:

4.measure :

5.四种方法获取View的宽高:

6.layout:

7.Draw:

● 自定义View的分类:


本文章作为学习总结

尊重原创老师  参考书集《《Android开发艺术探索》(任玉刚)(3)》及部分前辈网络总结,如涉及版权问题,请联系。

一、View的工作原理:

● View的事件体系:

x,y为View左上角坐标,translationX,translationY为View左上角相对于父容器的偏移量。

有关画布偏移量translate:y = top + translationY

● MotionEvent和TouchSlop:

1. MotionEvent:

ACTION_DOWN:手指刚接触屏幕

ACTION——MOVE:手指在屏幕上滑动

ACTION——UP:手指从屏幕上松开的一瞬间

getX/getY返回的是相对于当前View左上角的x和y坐标,而getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标。

2.TouchSlop:

是系统所能识别出的被认为是滑动的最小距离。通过如下方式即可获取这个常量:ViewConfiguration.get(getScaledTouchSlop)。

● VelocityTracker、GestureDetector和Scroller

VelocityTracker:速度追踪

● View的滑动

◎ View滑动的三种方式:

1.view本身提供的scrollTo/scrollBy方法;2.通过动画给view施加平移效果来实现滑动3.通过改变view的LayoutParams使得view重新布局从而实现滑动。

源码:Scroller scroller = new Scroller(mContext);

//缓慢滑动到指定位置

private void smoothScrollerTo (int destX,int destY) {

int scrollX = getScrollerX( );

int delta = destX - scrollX;

//1000ms内滑向destX,效果就是慢慢滑动

mScroller.startScroller (scrollX , 0 , delta , 0 , 1000);

invalidate( );

}

@Override

public void computeScroll( ) {

if(mScroller.computeScrolloffset( ) ) {

scrollTo(mScroller.getCurrX( ), mScroller.getCurrY( ) );

postInvalidate( );

}

}

//示例text1--SlideControl2--实现了滑动位移变色小方块自定义控件

if (isMove) {

    //滑过中控线
    if (circle_x >= centerX) {
        //关闭(执行开启)
        //源码:1000ms内滑向destX,效果就是慢慢滑动---scrollX起点横坐标,delta终点横坐标,1000ms时间
        //mScroller.startScroll(scrollX,0,delta,0,1000)
        mScroller.startScroll((int) circle_x, 0, (int) (circleEndX - circle_x), 0);
        isChecked = true;
    } else {
        //开启(执行关闭)
        mScroller.startScroll((int) circle_x, 0, (int) (circleStartX - circle_x), 0);
        isChecked = false;
    }
} else {

//实现弹性滑动--固定代码
@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        circle_x = mScroller.getCurrX();
        invalidate();
    }
}

二、View的绘制流程:

p174--4.1

● View常见的回调方法:

比如构造方法,onAttach、onVisibilityChanged、onDetach等。

● View的滑动,解决滑动冲突。

● 自定义View的几种固定类型:

直接继承自View和ViewGroup,继承现有的系统控件。

1.ViewRoot和DecorView:

ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程都是通过ViewRoot来完成的。

整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为

根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘

(draw),其框架过程如下:

View 绘制中主要流程分为measure,layout, draw 三个阶段。

measure :根据父 view 传递的 MeasureSpec 进行计算大小。

layout :根据 measure 子 View 所得到的布局大小和布局参数,将子View放在合适的位置上。

draw :把 View 对象绘制到屏幕上。

2.MeasureSpec:

封装了从父View 传递给到子View的布局需求。每个MeasureSpec代表宽度或高度的要求。每个MeasureSpec都包含了size(大小)和mode(模式)。

MeasureSpec 一个32位二进制的整数型int,前面2位代表的是mode,后面30位代表的是size。mode 主要分为3类,分别是

EXACTLY:父容器已经测量出子View的大小。对应是 View 的LayoutParams的match_parent 或者精确数值

AT_MOST:父容器已经限制子view的大小,View 最终大小不可超过这个值。对应是 View 的LayoutParams的wrap_content。

UNSPECIFIED:父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。(这种不怎么常用,下面分析也会直接忽略这种情况)。

示例:

 protected void measureChildWithMargins(View child,

        int parentWidthMeasureSpec, int widthUsed,

        int parentHeightMeasureSpec, int heightUsed) {

    //获取子View的LayoutParam

    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    //通过父View的MeasureSpec和子View的margin,父View的padding计算,算出子View的MeasureSpec

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin

                    + widthUsed, lp.width);

    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin

                    + heightUsed, lp.height);

    //通过计算出来的MeasureSpec,让子View自己测量。

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {

    int specMode = MeasureSpec.getMode(spec);

    int specSize = MeasureSpec.getSize(spec);

    //计算子View的大小

    int size = Math.max(0, specSize - padding);

    int resultSize = 0;

    int resultMode = 0;

    switch (specMode) {

    // 父View是EXACTLY的

    case MeasureSpec.EXACTLY:

        //子View的width或height是个精确值,则size为精确值,mode为 EXACTLY

        if (childDimension >= 0) {

            resultSize = childDimension;

            resultMode = MeasureSpec.EXACTLY;

        //子View的width或height是MATCH_PARENT,则size为父视图大小,mode为 EXACTLY

        } else if (childDimension == LayoutParams.MATCH_PARENT) {

            // Child wants to be our size. So be it.

            resultSize = size;

            resultMode = MeasureSpec.EXACTLY;

        //子View的width或height是WRAP_CONTENT,则size为父视图大小,mode为 AT_MOST

        } else if (childDimension == LayoutParams.WRAP_CONTENT) {

            // Child wants to determine its own size. It can't be

            // bigger than us.

            resultSize = size;

            resultMode = MeasureSpec.AT_MOST;

        }

        break;

    // 2、父View是AT_MOST的

    case MeasureSpec.AT_MOST:

        //子View的width或height是个精确值,则size为精确值,mode为 EXACTLY

        if (childDimension >= 0) {

            // Child wants a specific size... so be it

            resultSize = childDimension;

            resultMode = MeasureSpec.EXACTLY;

        //子View的width或height是MATCH_PARENT,则size为父视图大小,mode为 AT_MOST

        } else if (childDimension == LayoutParams.MATCH_PARENT) {

            // Child wants to be our size, but our size is not fixed.

            // Constrain child to not be bigger than us.

            resultSize = size;

            resultMode = MeasureSpec.AT_MOST;

        //子View的width或height是WRAP_CONTENT,则size为父视图大小,mode为 AT_MOST

        } else if (childDimension == LayoutParams.WRAP_CONTENT) {

            // Child wants to determine its own size. It can't be

            // bigger than us.

            resultSize = size;

            resultMode = MeasureSpec.AT_MOST;

        }

        break;

    // 3、父View是UNSPECIFIED的

    case MeasureSpec.UNSPECIFIED:

        if (childDimension >= 0) {

            // Child wants a specific size... let him have it

            resultSize = childDimension;

            resultMode = MeasureSpec.EXACTLY;

        } else if (childDimension == LayoutParams.MATCH_PARENT) {

            // Child wants to be our size... find out how big it should

            // be

            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

            resultMode = MeasureSpec.UNSPECIFIED;

        } else if (childDimension == LayoutParams.WRAP_CONTENT) {

            // Child wants to determine its own size.... find out how

            // big it should be

            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

            resultMode = MeasureSpec.UNSPECIFIED;

        }

        break;

    }

    //noinspection ResourceType

    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);

}

3.DecorView和View的测量区别:

对于顶级View(即DecorView)和普通View来说,MeasureSpec的转换过程略有不同。

DecorView:其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定;

普通View:其MeasureSpec由父容器的MeasureSpec和其自身的LayoutParams来共同决定;

●View 的 MeasureSpec 并不是父 View 独自决定,它是根据父 view 的MeasureSpec加上子 View 的自己的 LayoutParams,通过相应的规则转化。

需要注意一点就是,此时的MeasureSpec并不是View真正的大小,只有setMeasuredDimension之后才能真正确定View的大小。一种良好的习惯是,在onLayout方法中去获取VIew的测量宽高或者最终宽高。

●View 测量流程是父 View 先测量子 View,等子 View 测量完了,再来测量自己。在ViewGroup 测量子 View 的入口就是 measureChildWithMargins

●在调用子元素的measure方法之前会先通过getChildMeasureSpec方法来得到子元素的MeasureSpec;

●子元素的MeasureSpec的创建与 父容器的MeasureSpec子元素的LayoutParams有关,此外还和View的margin及padding有关。

4.measure :

  1. performMeasure中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure过程。
  2. measure 主要功能就是测量设置 View 的大小。该方法是 final 类型,子类不能覆盖,在方法里面会调用 onMeasure(),我们可以复写 onMeasure() 方法去测量设置 View 的大小。
  3. measure过程决定了View的宽高。Measure完成以后,可以通过getMeasureWidth和getMeasureHeight方法来获取到View测量后的宽和高(几乎所有情况下都等于View最终的宽高,特殊情况除外)。
  4. Layout过程决定了View的四个顶点的坐标和实际的View的宽高。完成以后,可以通过(setFrame方来设定)getTop、getBottom、getLeft、getRight来拿到View的四个顶点位置,并可以通过getWidth和getHeight方法来拿到View的最终宽高。
  5. Draw过程决定了View的显示。
  6. ViewGroup:除了完成自己的测量过程,还会遍历去调用所有子元素的measure方法,各个子元素在递归去执行这个流程。

5.四种方法获取View的宽高:

1.Activity/View#onWindowFocusChanged(回调多次)

onWindowFocusChanged:含义是View已经初始化完毕了,宽高已经准备好了。

2.view.post(runnable)

通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了。

3.ViewTreeObserver(回调多次)

使用ViewTreeObserver的众多回调可以完成这个功能,比如OnGlobalLayoutListener这个接口,

当View树的状态发生改变或者View树内部的View的可见性发生改变时,onGlobalLayout方法将被回调。因此这是获View宽高的一个好时机。

4.view.measure(int widthMeasureSpec,intheightMeasureSpec)

通过手动对View进行measure 来得到View的宽高(复杂,分情况处理,根据View的LayoutParams来分)。

6.layout:

●Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,他在onLayout中会遍历所有的子元素并调用其layout,在layout方法中onlayout方法有会被调用。

●layout方法确定View本身的位置,onLayout方法确定所有子元素的位置。、

●Layout过程决定了View的四个顶点的坐标和实际的View的宽高。完成以后,可以通过(setFrame方来设定)getTop、getBottom、getLeft、getRight来拿到View的四个顶点位置,并可以通过getWidth和getHeight方法来拿到View的最终宽高。

7.Draw:

●View的绘制过程:

  1. 绘制背景background.draw(canvas)
  2. 绘制自己(onDraw)
  3. 绘制children(dispatchDraw)
  4. 绘制装饰(onDrawScrollBars)

●View的绘制过程的传递是通过dispatchDraw来实现的,便利所有子元素的draw方法。

●View有一个特殊的方法:setWillNotDraw:当我们的自定义控件继承于ViewGroup并且本身不具备绘制功能时,开启这个标志位,便于系统进行后续优化。

当明确知道一个ViewGroup需要通过onDraw来绘制内容时,需要显示地关闭WILL——NOT——DRAW这个标记位。

View默认没有启用标记位false

ViewGroup默认启用标记位true

● 自定义View的分类:

1.继承View重写onDraw方法:需要自己支持wrap_content和padding。

2.继承ViewGrop派生特殊的Layout:需要合适的处理ViewGroup的测量、布局,并同时处理自元素的测量和布局过程。

3.继承特定的View(比如TextView):用于扩展一有的View功能,不需要自己支持wrap_content和padding。

4.继承特定的ViewGroup(比如LinearLayout):与方法2的区别在于方法2更接近View的底层。

● 自定义View须知:

  1. 让View支持wrap_content:直接继承view或者viewgroup控件,要在onMeasure中对wrap_content做特殊处理。
  2. 如果有必要,让你的View支持padding:因为直接继承view的控件,如果不在draw中处理padding,padding属性是无法 起作用的。另外,直接继承自ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素margin对其造成的 影响,不然将导致padding和子元素的margin失效。

3. 尽量不要再View中使用Handler,没必要

4. View中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow:当其不可见是,需要停止线程和动画,防 止内存泄露。

5. View带有滑动嵌套情形时,需要处理好滑动冲突:

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值