美文网首页
android绘图知识

android绘图知识

作者: JeremyDai | 来源:发表于2016-06-21 17:22 被阅读72次

LayoutInflater

LayoutInflater 我们经常用到,尤其是在fragment当中,那么获取layoutInflater的实例2种方式:

  • 第一种
    LayoutInflater layoutInflater = LayoutInflater.from(context);
  • 第二种
LayoutInflater layoutInflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);```

拿到layoutinflater后就可以调用它的```layoutInflater.inflate(resourceId,?root);```接收2个参数,第一个是资源的id,第2个表示是否给该布局的外部再嵌套一层父布局,如果不需要的话直接传null就可以了。

public class MainActivity extends Activity{

private LinearLayout mainLayout;

@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainLayout=(LinearLayout)?findViewById(R.id.main_layout);
//使用例子
LayoutInflater layoutInflater=LayoutInflater.from(this);
View buttonLayout= layoutInflater.inflate(R.layout.button_layout,null);
mainLayout.addView(buttonLayout);
}

}
或者在fragment中的例子
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
logger.debug("onCreateView");
setHasOptionsMenu(false);
View rootView = inflater.inflate(R.layout.fragment_album, container, false);
initRecyclerView();
return rootView;
}

这个时候我们就想看一下 inflate 这个方法里面到底是实现了什么东西了,

/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: "" + res.getResourceName(resource) + "" ("
+ Integer.toHexString(resource) + ")");
}

    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}
里面实例化了一个xml的解析器去解析我们设置过去的resourceId,具体的实现在 inflate(parser, root, attachToRoot)里面,再进到这个方法里面看看,

/**
* Inflate a new view hierarchy from the specified XML node. Throws
* {@link InflateException} if there is an error.
* <p>
* <em><strong>Important</strong></em> For performance
* reasons, view inflation relies heavily on pre-processing of XML files
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
*
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
            // Look for the root node.
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }

            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }

            final String name = parser.getName();
            
            if (DEBUG) {
                System.out.println("**************************");
                System.out.println("Creating root view: "
                        + name);
                System.out.println("**************************");
            }

            if (TAG_MERGE.equals(name)) {
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }

                rInflate(parser, root, inflaterContext, attrs, false);//rInflate()方法来循环遍历这个根布局下的子元素
            } else {
                // Temp is the root view that was found in the xml
             
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);
                    }
                }

                if (DEBUG) {
                    System.out.println("-----> start inflating children");
                }

                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);

                if (DEBUG) {
                    System.out.println("-----> done inflating children");
                }

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {
                    root.addView(temp, params); 
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            InflateException ex = new InflateException(e.getMessage());
            ex.initCause(e);
            throw ex;
        } catch (Exception e) {
            InflateException ex = new InflateException(
                    parser.getPositionDescription()
                            + ": " + e.getMessage());
            ex.initCause(e);
            throw ex;
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        return result;
    }
}


###  view 绘制主要的过程
- measure
- layout
- draw 
一个view的绘制主要流程是这三步,那一定有一个开始的地方,就像一个类是从main函数开始执行一样。整个view树的绘图流程是在ViewRootImpl类的preformTraversals()方法中开始的。该函数的执行过程中主要是根据之前设置的状态判断是否重新计算视图大小(Measure)、是否重新放置是视图的位置(layout)、以及是否重绘(draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个,看看方法的大致代码:

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);
......
}```

//* Figures out the measure spec for the root view in a window based on it's
  * layout params.
  *
  * @param windowSize
  *            The available width or height of the window
  *
  * @param rootDimension
  *            The layout params for one dimension (width or height) of the
  *            window.
  *
  * @return The measure spec to use to measure the root view.
  */
 private static int getRootMeasureSpec(int windowSize, int rootDimension) {
     int measureSpec;
     switch (rootDimension) {

     case ViewGroup.LayoutParams.MATCH_PARENT:
         // Window can't resize. Force root view to be windowSize.
         measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
         break;
     ......
     }
     return measureSpec;
 }
从上面来看绘制的得到的rootview的大小走的是match_parent的分支,specsize是等于window size,也就是我们的根视图是全屏的,其中的mView 也就是我么的view 了,它的大致流程图是

下面就看看view 的这3个流程主要是做了些什么
Meaure
Measure的流程图是这样的:



viewgrop的view的一个集合,就是它里面是装了若干个子view,我们平常使用的linearlayout 就是一个view group,viewgroup也是一个view。 看看View的measure方法,注意代码中的英文注释

     * <p>
     * This is called to find out how big a view should be. The parent
     * supplies constraint information in the width and height parameters.
     * </p>
     *
     * <p>
     * The actual measurement work of a view is performed in
     * {@link #onMeasure(int, int)}, called by this method. Therefore, only
     * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
     * </p>
     *
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the
     *        parent
     * @param heightMeasureSpec Vertical space requirements as imposed by the
     *        parent
     *
     * @see #onMeasure(int, int)
     */
     //final方法,子类不可重写
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ......
        //回调onMeasure()方法
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ......
    }

看见注释信息没有,他告诉你了很多重要信息。为整个View树计算实际的大小,然后设置实际的高和宽,每个View控件的实际宽高都是由父视图和自身决定的。实际的测量是在onMeasure方法进行,所以在View的子类需要重写onMeasure方法,这是因为measure方法是final的,不允许重载,所以View子类只能通过重载onMeasure来实现自己的测量逻辑。看见注释信息没有,他告诉你了很多重要信息。为整个View树计算实际的大小,然后设置实际的高和宽,每个View控件的实际宽高都是由父视图和自身决定的。实际的测量是在onMeasure方法进行,所以在View的子类需要重写onMeasure方法,这是因为measure方法是final的,不允许重载,所以View子类只能通过重载onMeasure来实现自己的测量逻辑。 measure 的2个参数都是父View传递过来的,也就是代表了父view的规格。它有2部分组成,高2为表示mode,定义在measurespec类(view的内部类)当中,有3种诶下,MeasureSpec.EXACTLY 表示确定大小, Measure.AT_MOST表示最大大小,MeasureSpec.UNSPECIFIED不确定,低30位表示size,也就是父view的大小。对于系统window类的DecorView的mode一般都是MeasureSpec.EXACTLY,而size分别对应的屏幕的宽高。对于子view来说大小是由父view和自子view共同决定的。 在这里可以看出最终方法是回调了view的onmeasure方法,再看看onMeasure的源码实现

/**
     * <p>
     * Measure the view and its content to determine the measured width and the
     * measured height. This method is invoked by {@link #measure(int, int)} and
     * should be overriden by subclasses to provide accurate and efficient
     * measurement of their contents.
     * </p>
     *
     * <p>
     * <strong>CONTRACT:</strong> When overriding this method, you
     * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
     * measured width and height of this view. Failure to do so will trigger an
     * <code>IllegalStateException</code>, thrown by
     * {@link #measure(int, int)}. Calling the superclass'
     * {@link #onMeasure(int, int)} is a valid use.
     * </p>
     *
     * <p>
     * The base class implementation of measure defaults to the background size,
     * unless a larger size is allowed by the MeasureSpec. Subclasses should
     * override {@link #onMeasure(int, int)} to provide better measurements of
     * their content.
     * </p>
     *
     * <p>
     * If this method is overridden, it is the subclass's responsibility to make
     * sure the measured height and width are at least the view's minimum height
     * and width ({@link #getSuggestedMinimumHeight()} and
     * {@link #getSuggestedMinimumWidth()}).
     * </p>
     *
     * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     * @param heightMeasureSpec vertical space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     *
     * @see #getMeasuredWidth()
     * @see #getMeasuredHeight()
     * @see #setMeasuredDimension(int, int)
     * @see #getSuggestedMinimumHeight()
     * @see #getSuggestedMinimumWidth()
     * @see android.view.View.MeasureSpec#getMode(int)
     * @see android.view.View.MeasureSpec#getSize(int)
     */
     //View的onMeasure默认实现方法
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }


我们看到这里调用了setMeasuredDimension这个方法,这个方法U对成员变量mMeasuredWidth和mMeasuredHeight变量赋值,Measure的主要目的就是对每个view的mMeasuredWidth和mMeasuredHeight变量赋值,所以一旦赋值完成Measure完成,那么它的工作就结束了,但是这个如果是viewgroup的话海需要对下面的子view进行Measure操作。先看看getDefaultSize里面做了什么吧。

public static int getDefaultSize(int size, int measureSpec) {
       int result = size;
       //通过MeasureSpec解析获取mode与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;
   }

如果specmode等于AT_MOST或EXACTLY就返回specSize,这就是系统默认的规格。getDefaultSize参数的widthMeasureSpec和heightMeasureSpec都是由父View传递进来的。getSuggestedMinimumWidth与getSuggestedMinimumHeight都是View的方法,具体如下:

protected int getSuggestedMinimumWidth() {
        return (mBackground == null)   mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

    protected int getSuggestedMinimumHeight() {
        return (mBackground == null)   mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

    }
protected int getSuggestedMinimumHeight() {

 return (mBackground == null)   mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

 }


返回的建议的最小宽度和高度都是由View的Background尺寸与通过设置View的miniXXX属性共同决定的,到此一次最基础的元素View的measure过程就完成了。以上说的是一个单个的view,我们一般的view都是嵌套的也就是view group,所以在ViewGroup中定义了measureChildren, measureChild, measureChildWithMargins方法来对子视图进行测量,measureChildren内部实质只是循环调用measureChild,measureChild和measureChildWithMargins的区别就是是否把margin和padding也作为子视图的大小。如下我们以ViewGroup中稍微复杂的measureChildWithMargins方法来分析:

/**

    * Ask one of the children of this view to measure itself, taking into
    * account both the MeasureSpec requirements for this view and its padding
    * and margins. The child must have MarginLayoutParams The heavy lifting is
    * done in getChildMeasureSpec.
    *
    * @param child The child to measure
    * @param parentWidthMeasureSpec The width requirements for this view
    * @param widthUsed Extra space that has been used up by the parent
    *        horizontally (possibly by other children of the parent)
    * @param parentHeightMeasureSpec The height requirements for this view
    * @param heightUsed Extra space that has been used up by the parent
    *        vertically (possibly by other children of the parent)
    */
   protected void measureChildWithMargins(View child,
           int parentWidthMeasureSpec, int widthUsed,
           int parentHeightMeasureSpec, int heightUsed) {
       //获取子视图的LayoutParams
       final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
       //调整MeasureSpec
       //通过这两个参数以及子视图本身的LayoutParams来共同决定子视图的测量规格
       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);
       //调运子View的measure方法,子View的measure中会回调子View的onMeasure方法
       child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
   }


public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //获取当前Parent View的Mode和Size
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        //获取Parent size与padding差值(也就是Parent剩余大小),若差值小于0直接返回0
        int size = Math.max(0, specSize - padding);
        //定义返回值存储变量
        int resultSize = 0;
        int resultMode = 0;
        //依据当前Parent的Mode进行switch分支逻辑
        switch (specMode) {
        // Parent has imposed an exact size on us
        //默认Root View的Mode就是EXACTLY
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                //如果child的layout_wOrh属性在xml或者java中给予具体大于等于0的数值
                //设置child的size为真实layout_wOrh属性值,mode为EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //如果child的layout_wOrh属性在xml或者java中给予MATCH_PARENT
                // Child wants to be our size. So be it.
                //设置child的size为size,mode为EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //如果child的layout_wOrh属性在xml或者java中给予WRAP_CONTENT
                //设置child的size为size,mode为AT_MOST
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        ......
        //其他Mode分支类似
        }
        //将mode与size通过MeasureSpec方法整合为32位整数返回
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

getChildMeasureSpec的逻辑是通过其父View提供的MeasureSpec参数得到specMode和specSize,然后根据计算出来的specMode以及子View的childDimension(layout_width或layout_height)来计算自身的measureSpec,如果其本身包含子视图,则计算出来的measureSpec将作为调用其子视图measure函数的参数,同时也作为自身调用setMeasuredDimension的参数,如果其不包含子视图则默认情况下最终会调用onMeasure的默认实现,并最终调用到setMeasuredDimension。 可以看见当通过setMeasuredDimension方法最终设置完成View的measure之后View的mMeasuredWidth和mMeasuredHeight成员才会有具体的数值,所以如果我们自定义的View或者使用现成的View想通过getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。
Measure 总结
Measure的过程就是从顶层父view向子view递归调用view.measure方法,Measure又回回调onMeasure方法的过程, Measurespec 中的specMode又3种值分别代表:
MeasureSpec.EXACTLY //确定模式,父View希望子View的大小是确定的,由specSize决定;
MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;
MeasureSpec.UNSPECIFIED //未指定模式,父View完全依据子View的设计值来决定;

Layout 流程
ViewRootImpl的performTraversals中measure执行完成以后会接着执行mView.layout,具体是 mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

可以看出layout 接收4个参数,这4个参数分别代表对parent的左、上、右、下坐标。可以看出上面左和上都是0,右和下分别是刚才测量的width和height的值。 至此又回归到view的layout的方法具体实现逻辑了,整个view的layout递归流程如下:
view也是递归结构,那我们先看看ViewGroup 中layout这个方法里面的实现

public final void layout(int l, int t, int r, int b) {

......

super.layout(l, t, r, b);

......

}

调用了父类(View)的layout方法。

public void layout(int l, int t, int r, int b) ```
 //实质都是调用setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量

 //判断View的位置是否发生过变化,以确定有没有必要对当前的View进行重新layout

boolean changed = isLayoutModeOptical(mParent)

setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

//需要重新layout

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {

//回调onLayout

onLayout(changed, l, t, r, b);

......

}

......

}

上面的代码很清楚的显示了,与onmeasure类似,回调到onLayout的方法中,对比View的layout与ViewGroup的方法可以发现,view的layout方法是可以在子类重写的,而ViewGroup的Layout是不能在子类中重写的,说白了就是ViewGroup中只能通过重写onLayout方法,那么看看ViewGroup的onLayout方法,是个抽象方法, “`?@Override protected abstract void onLayout(boolean changed,?int l, int t, int r, int b);
也就是说viewgroup的所有子类都必须重写这个方法,所以在自定义ViewGroup控件中,onLayout配合onMeasure方法一起使用可以实现自定义View的复杂布局。自定义View首先调用onMeasure进行测量,然后调用onLayout方法动态获取子View和子View的测量大小,然后进行layout布局。重载onLayout的目的就是安排其children在父View的具体位置,重载onLayout通常做法就是写一个for循环调用每一个子视图的layout(l, t, r, b)函数,传入不同的参数l, t, r, b来确定每个子视图在父视图中的显示位置。

再看下View的onLayout方法,

```protected void onLayout(boolean changed, int left, int top, int right, int bottom) {?}```
是个空的,也就意味着具体的放置位置是继承它的类自己去放置。

看一个LinearLayout的例子,

void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;

    int childTop;
    int childLeft;

    // Where right end of child should go
    //计算父窗口推荐的子View宽度
    final int width = right - left;
    //计算父窗口推荐的子View右侧位置
    int childRight = width - mPaddingRight;

    // Space available for child
    //child可使用空间大小
    int childSpace = width - paddingLeft - mPaddingRight;
    //通过ViewGroup的getChildCount方法获取ViewGroup的子View个数
    final int count = getVirtualChildCount();
    //获取Gravity属性设置
    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
    //依据majorGravity计算childTop的位置值
    switch (majorGravity) {
       case Gravity.BOTTOM:
           // mTotalLength contains the padding already
           childTop = mPaddingTop + bottom - top - mTotalLength;
           break;

           // mTotalLength contains the padding already
       case Gravity.CENTER_VERTICAL:
           childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
           break;

       case Gravity.TOP:
       default:
           childTop = mPaddingTop;
           break;
    }
    //重点!!!开始遍历
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            //LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
            //获取子View的LayoutParams
            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();

            int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }
            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            //依据不同的absoluteGravity计算childLeft位置
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                            + lp.leftMargin - lp.rightMargin;
                    break;

                case Gravity.RIGHT:
                    childLeft = childRight - childWidth - lp.rightMargin;
                    break;

                case Gravity.LEFT:
                default:
                    childLeft = paddingLeft + lp.leftMargin;
                    break;
            }

            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }

            childTop += lp.topMargin;
            //通过垂直排列计算调运child的layout设置child的位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        }
    }
}

setChildFrame 方法中就会调用child.layout()方法。

#### layout 的总结:

- layout 是从顶层父view向子view递归调用view的layout方法的过程,父view根据上一步measure子view得到的布局大小和布局参数,将子view放在合适的位置上

- view.layout方法可以被重载,viewgroup的layout方法不可以被重载,viewgroup的onlayout方法是个abstract的子类必须实现。

- measure操作完成后得到的是对每个View经测量过的measuredWidth和measuredHeight,layout操作完成之后得到的是对每个View进行位置分配后的mLeft、mTop、mRight、mBottom,这些值都是相对于父View来说的。

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

### Draw

在上面介绍过在Measure和layput,之后就开始执行draw的操作,

draw的顺序是在measure()和layout()之后,这里的mView对于activity来说就是PhoneWindow.DecorView,ViewRootImpl中的代码回创建一个canvas对象,然后调用View的draw方法来执行具体的绘制。这个先看一个View的层级图

![](https://img.haomeiwen.com/i2043394/ac526907544c0dbd?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
然后就看看view树的递归draw的流程图:

![](https://img.haomeiwen.com/i2043394/ad454fa0faaca146?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
看到 viewgroup当中没有复写draw方法,所有直接从view的draw方法开始看,方法中的注释已经大致告诉了我们几个步骤,

/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
/
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);
    ......
}

参考文档:
感谢他们的分享
http://blog.csdn.net/guolin_blog/article/details/12921889
http://blog.csdn.net/yanbober/article/details/46128379

相关文章

网友评论

      本文标题:android绘图知识

      本文链接:https://www.haomeiwen.com/subject/godgdttx.html