专业的编程技术博客社区

网站首页 > 博客文章 正文

Android View(Android viewpager显示三个view)

baijin 2025-01-31 11:49:26 博客文章 12 ℃ 0 评论

Android UI界面架构

每个Activity包含一个PhoneWindow对象,PhoneWindow设置DecorView为应用窗口的根视图,在里面就是TitleView和ContentView, 平时使用的setContentView()就是设置的ContentView。

View控件

Android中控件基本分为两类View和ViewGroup,ViewGroup作为容器管理View。Android视图,是类似于Dom树的架构,如下图:

如何绘制View

当startActivity启动一个Activity时,会绘制当前Activity的布局。绘制从根视图开始,从上至下遍历整棵视图树,每一个ViewGroup负责让自己的子View被绘制,每一个View负责绘制自己,通过draw()方法,绘制过程分三步走:

  • Measure: 测量
  • Layout:定位
  • Draw:绘制及显示

整个绘制流程是在ViewRoot中的performTraversals()方法展开的。部分源代码如下:

private void performTraversals() {
    ......
    //最外层的根视图的widthMeasureSpec和heightMeasureSpec由来
    //lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ......
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    ......
    mView.draw(canvas);
    ......
}

View绘制的流程图如下:

Measure

Measure过程会为一个 View 及所有子节点的 mMeasuredWidth 和 mMeasuredHeight 变量赋值,该值可以通过 getMeasuredWidth()getMeasuredHeight()方法获得。

onMeasure()方法核心源码如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
           setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                   getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
       }

       //设置View宽高的测量值
       protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
       
       //measureSpec指的是View测量后的大小
       public static int getDefaultSize(int size, int measureSpec) {
           int result = size;
           int specMode = MeasureSpec.getMode(measureSpec);
           int specSize =  MeasureSpec.getSize(measureSpec);
   
           switch (specMode) {
           //MeasureSpec.UNSPECIFIED一般用来系统的内部测量流程
           case MeasureSpec.UNSPECIFIED:
               result = size;
               break;
           //我们主要关注着两种情况,它们返回的是View测量后的大小
           case MeasureSpec.AT_MOST:
           case MeasureSpec.EXACTLY:
               result = specSize;
               break;
           }
           return result;
       }
       
       //如果View没有设置背景,那么返回android:minWidth这个属性的值,这个值可以为0
       //如果View设置了背景,那么返回android:minWidth和背景最小宽度两者中的最大值。
       protected int getSuggestedMinimumHeight() {
           int suggestedMinHeight = mMinHeight;
   
           if (mBGDrawable != null) {
               final int bgMinHeight = mBGDrawable.getMinimumHeight();
               if (suggestedMinHeight < bgMinHeight) {
                   suggestedMinHeight = bgMinHeight;
               }
           }
   
           return suggestedMinHeight;
       }

通过源码可以看出,最终的高宽是调用setMeasuredDimension()设定的,如果不重写,默认是直接调用getDefaultSize获取尺寸的。

MeasureSpec是一个32位int值,高2位为测量的模式,低30位为测量的大小。测量的模式可以分为以下三种。

  • EXACTLY
    精确值模式,当layout_width或layout_height指定为具体数值,或者为match_parent时,系统使用EXACTLY。
  • AT_MOST
    最大值模式,指定为wrap_content时,控件的尺寸不能超过父控件允许的最大尺寸。
  • UNSPECIFIED
    不指定测量模式,View想多大就多大。

使用View的getMeasuredWidth()getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值

Layout

通过onMeasure()方法我们已经知道了View的大小,那么接下来就是确定该View到底在哪个位置显示,这个时候就要调用layout()方法。

public void layout(int l, int t, int r, int b) {
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    boolean changed = setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
        .....
        onLayout(changed, l, t, r, b);
        .....
}

layout获取四个参数,左,上,右,下坐标,相对于父视图而言。

通过上面performTraversals()方法中的源码:

mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

这里可以看到,就是使用了onMeasure()测量的宽和高,作为后面两个参数。

当layout结束以后getWidth()getHeight()才会返回正确的值。

getWidth/Height()getMeasuredWidth/Height()有什么区别?

下图用于比较形象的展示getHeight()和getMeasuredHeight():

  • getHeight(): View在设定好布局后View的高度。
  • getMeasuredHeight(): 对View上的內容进行测量后得到的View內容占据的高度。

getWidth()和getMeasuredWidth()与高度一致。

Draw

绘制流程如下:

总结一下步骤其实就是

  1. view绘制自身(包含背景,自身内容)
  2. 绘制装饰(滚动指示器,滚动条等)

如下是draw()方法的核心代码

public void draw(Canvas canvas) {
        ......
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        ......
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        ......

        // Step 2, save the canvas' layers
        ......
            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }
        ......

        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers
        ......
        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, right, top + length, p);
        }
        ......

        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);
        ......
    }

Android View常见面试题

  • 首次 View 的绘制流程是在什么时候触发的?
  • ViewRootImpl 创建的时机?
  • ViewRootImpl 和 DecorView 的关系是什么?
  • DecorView 的布局是什么样的?
  • DecorView 的创建时机?
  • setContentView 的流程
  • LayoutInflate 的流程
  • Activity、PhoneWindow、DecorView、ViewRootImpl 的关系?
  • PhoneWindow 的创建时机?
  • 如何触发重新绘制?
  • requestLayout 和 invalidate 的流程?
  • requestLayout 和 invalidate 的区别?

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表