Android View的绘制流程

在项目的开发的过程中,对于一般的需求我们使用Android提供的原生的空间就可以满足开发需求,但是当我们遇到一些特殊的需求需要我们自定义View的时候,需要开发人员实现测量,布局和绘制等操作,这些都依赖于我们对View绘制流程的理解和掌握

 

先看下Android的UI管理系统的层级关系: 

Android View的绘制流程_第1张图片

PhoneWindow是Android系统中最基本的窗口系统,每个Activity会创建一个.PhoneWindow是Activity和View系统交互的接口.DecorView本质上是Framelayout,是Activity中所有View的祖先
 

目录

1.绘制的整体流程

2.MeasureSpec

3.Measure 

4.Layout 

5.Draw


1.绘制的整体流程

当一个app启动的时候,会启动一个主Activity,Android系统会根据Activity的布局对他进行绘制.绘制从根视图ViewRoot的performTraversals()方法开始,从上到下遍历整个视图树,每个View空间负责绘制自己,而Viewgroup还要负责告诉自己的子View开始进行绘制,视图树绘制可以分为三个步骤:分别是测量Measure ,布局 Layout ,绘制 Draw.

关于performTraversals()方法,可以去源码查看,在ViewRootImp.java文件中 

核心的三个点: 

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();

2.MeasureSpec

   MeasureSpec表示的是一个32位的整型值,他的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize .

  MeadureSpec 是View类的一个静态内部类,用来表示改如果测量这个View 

核心代码 :

   public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
 

        /**
         *  不指定测量模式
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         *  精确测量模式
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * 最大值测量模式
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        /**
         *  根据指定的大小和模式创建一个MeasureSpec
         */
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        
        /**
         *微调SpecMeasure的大小
         */
        
        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }

        
    }

 

三种测量模式 :

  • UNSPECIFIED :不指定测量模式,父视图没有限制子视图的大小,子视图可以想要任何尺寸.开发中很少见

  • EXACTLY    : 精准的测量模式,当该视图的width和height指定为具体的数值或者是match_parent时候生效,表示父视图已经决定子视图的大小,改模式下View的测量值就是SpecSize的值

  • AT_MOST    :最大值模式,宽和高为wrap_content的时候,子视图可以是不超过父视图允许的最大尺寸内的任何尺寸

对DecorView来说,他的MeasureSpec是由窗口尺寸和其自身的LayoutParams共同决定,对于普通的View,他的MeasureSpec由父视图和MeasureSpec和自身的layoutParams来决定 

 

3.Measure 

Measure方法是用来计算View的实际大小的,通过前面的分析,我们知道页面的绘制流程是从performMeasure开始的 


    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
//核心代码
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

 

具体的测量操作是分发给ViewGroup的,由ViewGroup在他的measureChild方法中传递给子类.ViewGroup通过遍历自身所有的子View,并调用View的measure方法来完成测量 ,查看ViewGroup中源码,找到2个关键的方法:

/**
* 遍历测量ViewGroup中的所有子View
*/ 
  protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
/**
* 测量指定View 
*/  

  protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

 

下面看下View和ViewGroup中的measure方法,最终的测量时通过回调onMeasure方法实现的.这个通常都是View的子类自己实现的,我们可以通过这个方法来实现自定义View 

   public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
 ........
/*
* 核心代码
*/
                onMeasure(widthMeasureSpec, heightMeasureSpec);
         .............  
    }

 

如果自定义测量过程,这个方法可以重写 

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

如果没有重写这个方法,默认的会直接调用getDefaultSize来获取View的尺寸 

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

4.Layout 

确定View在父容器中的位置,它是由父容器获取子view的位置参数后,调用子view的layout方法,将获取的参数传入来实现确定位置的.

viewRootImp中的performLayout代码 :

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
      ............
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
 ...............
    }

 

View中的layout方法代码 :

    public void layout(int l, int t, int r, int b) {
//.......         
onLayout(changed, l, t, r, b);
 
           ....              
 
 
    }

 

onlayout方法 : 

 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

 

这是一个空方法,View的子类如果是ViewGroup类型,则重写这个方法,实现ViewGroup中所有View的布局 ,我们看下ViewGroup中这个方法 

 @Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);

可以看到,它里面实现了View的这个onLayout方法,并且设置为抽象方法,这样它的子类必须实现这个方法来确定包含的子View的绘制 ,我们找个ViewGroup的子类看看代码: 最常用的线性布局中的代码=>

/**
*  实现了ViewGroup中的onLayout方法,分水平和垂直分别给出了绘制子View的流程
*/ 
@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }

 

 

5.Draw

 Draw操作是用来将控件绘制在界面上,绘制的流程是从performDraw方法开始的,看下代码 :

 private void performDraw() {
        if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
            return;
        } else if (mView == null) {
            return;
        }

        final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
        mFullRedrawNeeded = false;

        mIsDrawing = true;
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");

        boolean usingAsyncReport = false;
        if (mReportNextDraw && mAttachInfo.mThreadedRenderer != null
                && mAttachInfo.mThreadedRenderer.isEnabled()) {
            usingAsyncReport = true;
            mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> {
                // TODO: Use the frame number
                pendingDrawFinished();
            });
        }

        try {
            boolean canUseAsync = draw(fullRedrawNeeded);
            if (usingAsyncReport && !canUseAsync) {
                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
                usingAsyncReport = false;
            }
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        // For whatever reason we didn't create a HardwareRenderer, end any
        // hardware animations that are now dangling
        if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
            final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
            for (int i = 0; i < count; i++) {
                mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
            }
            mAttachInfo.mPendingAnimatingRenderNodes.clear();
        }

        if (mReportNextDraw) {
            mReportNextDraw = false;

            // if we're using multi-thread renderer, wait for the window frame draws
            if (mWindowDrawCountDown != null) {
                try {
                    mWindowDrawCountDown.await();
                } catch (InterruptedException e) {
                    Log.e(mTag, "Window redraw count down interrupted!");
                }
                mWindowDrawCountDown = null;
            }

            if (mAttachInfo.mThreadedRenderer != null) {
                mAttachInfo.mThreadedRenderer.setStopped(mStopped);
            }

            if (LOCAL_LOGV) {
                Log.v(mTag, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
            }

            if (mSurfaceHolder != null && mSurface.isValid()) {
                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished);
                SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();

                sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
            } else if (!usingAsyncReport) {
                if (mAttachInfo.mThreadedRenderer != null) {
                    mAttachInfo.mThreadedRenderer.fence();
                }
                pendingDrawFinished();
            }
        }
    }

核心的代码:

 

  boolean canUseAsync = draw(fullRedrawNeeded);

看下draw方法 :

    private boolean draw(boolean fullRedrawNeeded) {
      ...

        
    

                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
       
...
    }
drawSoftware方法 :
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
   ....
  mView.draw(canvas);
  ......   
 }

根据源码追踪,看到了ViewRootImp中最终会调用view的draw方法具体绘制每个具体的View,绘制分为六个步骤: 看下View中的代码,截取出主要的代码:

 

 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)
                 1.绘制背景 
                 2.如果需要,保存canvas图层,为fading做准备 
                 3.绘制view内容 
                 4.绘制view的子view 
                 5.如果需要,绘制view的fading边缘并回复图层
                 6.绘制view的装饰(例如滚动条)
         */
 
        
        }

 

完成...

你可能感兴趣的:(Android基础,Android高级进阶)