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

 

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

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的装饰(例如滚动条)         */                 }

 

完成...

更多相关文章

  1. android面试(7)-handler机制
  2. iOS开发 OC与java相对应的3DES加解密
  3. Android编程学习笔记 之 基于回调的事件传播
  4. Android(安卓)学习之路一:Activity之间传递数据的四种方式
  5. 《第一行代码--Android》读书笔记之数据存储
  6. android 自动化测试的傻瓜实践之旅(UI篇) -----小试身手
  7. 转载:Android获取其他包的Context和在任意位置获取应用程序Contex
  8. Android(安卓)measure过程分析
  9. Android(安卓)MVP 模式 项目初体验(一)

随机推荐

  1. android logger
  2. Android(安卓)手机震动效果
  3. Android(安卓)去除Gallery的边缘颜色
  4. android中设置按钮不同状态时按钮的效果
  5. android更新SDK时出现的问题
  6. Android(安卓)如何删除短信
  7. android 导航布局
  8. Android优秀开源项目
  9. Android(安卓)Dev
  10. android 禁用home键