`

Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)

 
阅读更多

本文原创, 转载请注明出处http://blog.csdn.net/qinjuning

 

 

 

在之前一篇博文中<<Android中View绘制流程以及invalidate()等相关方法分析>>,简单的阐述 了Android View

绘制流程的三个步骤,即:

 

1、 measure过程 --- 测量过程

2、 layout 过程 --- 布局过程
3、 draw 过程 --- 绘制过程


要想对Android 中View这块深入理解,对这三个步骤地学习是必不可少的 。

今天,我着重讲解下如下三个内容:

1、measure过程

2、WRAP_CONTENT、MATCH_PARENT/FILL_PARENT属性的原理说明

3、xml布局文件解析成View树的流程分析。


希望对大家能有帮助。- -分析版本基于Android 2.3



1、WRAP_CONTENT、MATCH_PARENT/FILL_PARENT


初入Android殿堂的同学们,对这三个属性一定又爱又恨。爱的是使用起来挺爽地---照葫芦画瓢即可,恨的

却是时常混淆这几个属性地意义,需要三思而后行。在带着大家重温下这几个属性的用法吧(希望我没有啰嗦)。


这三个属性都用来适应视图的水平或垂直大小,一个以视图的内容或尺寸为基础的布局比精确地指定视图范围

更加方便。

fill_parent

设置一个视图的布局为fill_parent将强制性地使视图扩展至父元素大小。

 

match_parent

Android 中match_parent和fill_parent意思一样,但match_parent更贴切,于是从2.2开始两个词都可以

用,但2.3版本后建议使用match_parent。

wrap_content

自适应大小,强制性地使视图扩展以便显示其全部内容。以TextView和ImageView控件为例,设置为

wrap_content将完整显示其内部的文本和图像。布局元素将根据内容更改大小。

可不要重复造轮子,以上摘自<<Androidfill_parent、wrap_content和match_parent的区别>>


当然,我们可以设置View的确切宽高,而不是由以上属性指定。

 

        android:layout_weight="wrap_content"   //自适应大小
        android:layout_weight="match_parent"   //与父视图等高
        android:layout_weight="fill_parent"	   //与父视图等高
        android:layout_weight="100dip"         //精确设置高度值为 100dip

 

 

接下来,我们需要转换下视角,看看ViewGroup.LayoutParams类及其派生类。


2、ViewGroup.LayoutParams类及其派生类


2.1、 ViewGroup.LayoutParams类说明

Android API中如下介绍:

LayoutParams are used by views to tell their parents how they want to be laid out.


意思大概是说:View通过LayoutParams类告诉其父视图它想要地大小(即,长度和宽度)。


因此,每个View都包含一个ViewGroup.LayoutParams类或者其派生类,View类依赖于ViewGroup.LayoutParams。

路径:frameworks\base\core\java\android\view\View.java

 

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
  ...
  /**
   * The layout parameters associated with this view and used by the parent
   * {@link android.view.ViewGroup} to determine how this view should be
   * laid out.
   * {@hide}
   */
  //该View拥有的 LayoutParams属性,父试图添加该View时,会为其赋值,特别注意,其类型为ViewGroup.LayoutParams。
  protected ViewGroup.LayoutParams mLayoutParams;  
  ...
}

 

2.2、 ViewGroup.LayoutParams源码分析

路径位于:frameworks\base\core\java\android\view\ViewGroup.java

 

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
	...
	 public static class LayoutParams {
        /**
         * Special value for the height or width requested by a View.
         * FILL_PARENT means that the view wants to be as big as its parent,
         * minus the parent's padding, if any. This value is deprecated
         * starting in API Level 8 and replaced by {@link #MATCH_PARENT}.
         */
        @Deprecated
        public static final int FILL_PARENT = -1;  // 注意值为-1,Android2.2版本不建议使用
        /**
         * Special value for the height or width requested by a View.
         * MATCH_PARENT means that the view wants to be as big as its parent,
         * minus the parent's padding, if any. Introduced in API Level 8.
         */
        public static final int MATCH_PARENT = -1; // 注意值为-1
        /**
         * Special value for the height or width requested by a View.
         * WRAP_CONTENT means that the view wants to be just large enough to fit
         * its own internal content, taking its own padding into account.
         */
        public static final int WRAP_CONTENT = -2; // 注意值为-2
        /**
         * Information about how wide the view wants to be. Can be one of the
         * constants FILL_PARENT (replaced by MATCH_PARENT ,
         * in API Level 8) or WRAP_CONTENT. or an exact size.
         */
        public int width;  //该View的宽度,可以为WRAP_CONTENT/MATCH_PARENT 或者一个具体值
        /**
         * Information about how tall the view wants to be. Can be one of the
         * constants FILL_PARENT (replaced by MATCH_PARENT ,
         * in API Level 8) or WRAP_CONTENT. or an exact size.
         */
        public int height; //该View的高度,可以为WRAP_CONTENT/MATCH_PARENT 或者一个具体值
        /**
         * Used to animate layouts.
         */
        public LayoutAnimationController.AnimationParameters layoutAnimationParameters;
        /**
         * Creates a new set of layout parameters. The values are extracted from
         * the supplied attributes set and context. The XML attributes mapped
         * to this set of layout parameters are:、
         */
        public LayoutParams(Context c, AttributeSet attrs) {
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_Layout_layout_width,
                    R.styleable.ViewGroup_Layout_layout_height);
            a.recycle();
        }

        /**
         * Creates a new set of layout parameters with the specified width
         * and height.
         */
        public LayoutParams(int width, int height) {
            this.width = width;
            this.height = height;
        }
        /**
         * Copy constructor. Clones the width and height values of the source.
         *
         * @param source The layout params to copy from.
         */
        public LayoutParams(LayoutParams source) {
            this.width = source.width;
            this.height = source.height;
        }
        /**
         * Used internally by MarginLayoutParams.
         * @hide
         */
        LayoutParams() {
        }
        /**
         * Extracts the layout parameters from the supplied attributes.
         *
         * @param a the style attributes to extract the parameters from
         * @param widthAttr the identifier of the width attribute
         * @param heightAttr the identifier of the height attribute
         */
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            width = a.getLayoutDimension(widthAttr, "layout_width");
            height = a.getLayoutDimension(heightAttr, "layout_height");
        }
}

 

我们发现FILL_PARENT/MATCH_PARENT值为 -1 ,WRAP_CONETENT值为-2,是不是有点诧异? 将值

设置为负值的目的是为了区别View的具体值(an exact size) 总是大于0的。


ViewGroup子类可以实现自定义LayoutParams,自定义LayoutParams提供了更好地扩展性,例如LinearLayout

有LinearLayout.LayoutParams自定义类(见下文)。整个LayoutParams类家族还是挺复杂的。

ViewGroup.LayoutParams及其常用派生类的类图(部分类图)如下:


该类图是在太庞大了,大家有兴趣的去看看Android API吧。


前面我们说过,每个View都包含一个ViewGroup.LayoutParams类或者其派生类,下面我们的疑问是Android框架

中时如何为View设置其LayoutParams属性的。


有两种方法会设置View的LayoutParams属性:

1、 直接添加子View时,常见于如下几种方法:ViewGroup.java

 

//Adds a child view.	
void addView(View child, int index)
//Adds a child view with this ViewGroup's default layout parameters 
//and the specified width and height.
void addView(View child, int width, int height)
//Adds a child view with the specified layout parameters.		
void addView(View child, ViewGroup.LayoutParams params)


三个重载方法的区别只是添加View时构造LayoutParams对象的方式不同而已,稍后我们探寻一下它们的源码。

2、 通过xml布局文件指定某个View的属性为:android:layout_heigth=””以及android:layout_weight=”” 时。

总的来说,这两种方式都会设定View的LayoutParams属性值----指定的或者Default值。

方式1流程分析

直接添加子View时,比较容易理解,我们先来看看这种方式设置LayoutParams的过程:

路径:\frameworks\base\core\java\android\view\ViewGroup.java

 

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
	...
    /**
     * Adds a child view. If no layout parameters are already set on the child, the
     * default parameters for this ViewGroup are set on the child.
     *
     * @param child the child view to add
     *
     * @see #generateDefaultLayoutParams()
     */
    public void addView(View child) {
        addView(child, -1);
    }
    /**
     * Adds a child view. If no layout parameters are already set on the child, the
     * default parameters for this ViewGroup are set on the child.
     *
     * @param child the child view to add
     * @param index the position at which to add the child
     *
     * @see #generateDefaultLayoutParams()
     */
    public void addView(View child, int index) {
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams(); //返回默认地LayoutParams类,作为该View的属性值
            if (params == null) {//如果不能获取到LayoutParams对象,则抛出异常。
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }
    /**
     * Adds a child view with this ViewGroup's default layout parameters and the
     * specified width and height.
     *
     * @param child the child view to add
     */
    public void addView(View child, int width, int height) {
    	//返回默认地LayoutParams类,作为该View的属性值
    	final LayoutParams params = generateDefaultLayoutParams(); 
        params.width = width;   //重新设置width值
        params.height = height; //重新设置height值
        addView(child, -1, params); //这儿,我们有指定width、height的大小了。
    }
    /**
     * Adds a child view with the specified layout parameters.
     *
     * @param child the child view to add
     * @param params the layout parameters to set on the child
     */
    public void addView(View child, LayoutParams params) {
        addView(child, -1, params);
    }
    /**
     * Adds a child view with the specified layout parameters.
     *
     * @param child the child view to add
     * @param index the position at which to add the child
     * @param params the layout parameters to set on the child
     */
    public void addView(View child, int index, LayoutParams params) {
        ...
        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate();
        addViewInner(child, index, params, false);
    }
    /**
     * Returns a set of default layout parameters. These parameters are requested
     * when the View passed to {@link #addView(View)} has no layout parameters
     * already set. If null is returned, an exception is thrown from addView.
     *
     * @return a set of default layout parameters or null
     */
    protected LayoutParams generateDefaultLayoutParams() {
        //width 为 WRAP_CONTENT大小 , height 为WRAP_CONTENT 
    	//ViewGroup的子类可以重写该方法,达到其特定要求。稍后会以LinearLayout类为例说明。
    	return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }
    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (!checkLayoutParams(params)) { //params对象是否为null
            params = generateLayoutParams(params); //如果params对象是为null,重新构造个LayoutParams对象
        }
        //preventRequestLayout值为false
        if (preventRequestLayout) {  
            child.mLayoutParams = params; //为View的mLayoutParams属性赋值
        } else {
            child.setLayoutParams(params);//为View的mLayoutParams属性赋值,但会调用requestLayout()请求重新布局
        }
        //if else 语句会设置View为mLayoutParams属性赋值
        ...
    }
	...
}

 

 

主要功能就是在添加子View时为其构建了一个LayoutParams对象。但更重要的是,ViewGroup的子类可以重载

上面的几个方法,返回特定的LayoutParams对象,例如:对于LinearLayout而言,则是LinearLayout.LayoutParams

对象。这么做地目的是,能在其他需要它的地方,可以将其强制转换成LinearLayout.LayoutParams对象。


LinearLayout重写函数地实现为:

public class LinearLayout extends ViewGroup {
	...
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LinearLayout.LayoutParams(getContext(), attrs);
    }
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        //该LinearLayout是水平方向还是垂直方向
    	if (mOrientation == HORIZONTAL) { 
            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        } else if (mOrientation == VERTICAL) {
            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        }
        return null;
    }
    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }
	/**
     * Per-child layout information associated with ViewLinearLayout.
     * 
     * @attr ref android.R.styleable#LinearLayout_Layout_layout_weight
     * @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity
     */ //自定义的LayoutParams类
    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        /**
         * Indicates how much of the extra space in the LinearLayout will be
         * allocated to the view associated with these LayoutParams. Specify
         * 0 if the view should not be stretched. Otherwise the extra pixels
         * will be pro-rated among all views whose weight is greater than 0.
         */
        @ViewDebug.ExportedProperty(category = "layout")
        public float weight;      //  见于属性,android:layout_weight=""  ;
        /**
         * Gravity for the view associated with these LayoutParams.
         *
         * @see android.view.Gravity
         */
        public int gravity = -1;  // 见于属性, android:layout_gravity=""  ; 
        /**
         * {@inheritDoc}
         */
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a =c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
            weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
            gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);

            a.recycle();
        }
        /**
         * {@inheritDoc}
         */
        public LayoutParams(int width, int height) {
            super(width, height);
            weight = 0;
        }
        /**
         * Creates a new set of layout parameters with the specified width, height
         * and weight.
         *
         * @param width the width, either {@link #MATCH_PARENT},
         *        {@link #WRAP_CONTENT} or a fixed size in pixels
         * @param height the height, either {@link #MATCH_PARENT},
         *        {@link #WRAP_CONTENT} or a fixed size in pixels
         * @param weight the weight
         */
        public LayoutParams(int width, int height, float weight) {
            super(width, height);
            this.weight = weight;
        }
        public LayoutParams(ViewGroup.LayoutParams p) {
            super(p);
        }
        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }
    }
	...
}

 

 

LinearLayout.LayoutParams类继承至ViewGroup.MarginLayoutParams类,添加了对android:layout_weight以及

android:layout_gravity这两个属性的获取和保存。而且它的重写函数返回的都是LinearLayout.LayoutParams

类型。样,我们可以再对子View进行其他操作时,可以将将其强制转换成LinearLayout.LayoutParams对象进行

使用。

例如,LinearLayout进行measure过程,使用了LinearLayout.LayoutParam对象,有如下代码:

 

public class LinearLayout extends ViewGroup {
	...
	@Override  //onMeasure方法。
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	    //判断是垂直方向还是水平方向,这儿我们假设是VERTICAL垂直方向,
		if (mOrientation == VERTICAL) {
	        measureVertical(widthMeasureSpec, heightMeasureSpec);
	    } else {
	        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
	    }
	}
	 /**
     * Measures the children when the orientation of this LinearLayout is set
     * to {@link #VERTICAL}.
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
     *
     * @see #getOrientation()
     * @see #setOrientation(int)
     * @see #onMeasure(int, int)
     */
	  void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
	        mTotalLength = 0;
	        ...
	        // See how tall everyone is. Also remember max width.
	        for (int i = 0; i < count; ++i) {
	            final View child = getVirtualChildAt(i); //获得索引处为i的子VIew   
                ...
                //注意,我们将类型为 ViewGroup.LayoutParams的实例对象强制转换为了LinearLayout.LayoutParams,
                //即父对象转换为了子对象,能这样做的原因就是LinearLayout的所有子View的LayoutParams类型都为
                //LinearLayout.LayoutParams
	            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
                ...
	    }
	...
}


超类ViewGroup.LayoutParams强制转换为了子类LinearLayout.LayoutParams,因为LinearLayout的每个

”直接“子ViewLayoutParams属性都是LinearLayout.LayoutParams类型,因此可以安全转换。


PS : Android 2.3源码Launcher2中也实现了自定义的LayoutParams类,在IDLE界面的每个View至少包含如下

信息:所在X方向的单元格索引和高度、所在Y方向的单元格索引和高度等。

路径:packages\apps\Launcher2\src\com\android\launcher2\CellLayout.java

 

public class CellLayout extends ViewGroup {
	... 
	public static class LayoutParams extends ViewGroup.MarginLayoutParams {
	        /**
	         * Horizontal location of the item in the grid.
	         */
	        public int cellX;   //X方向的单元格索引
	        /**
	         * Vertical location of the item in the grid.
	         */
	        public int cellY;   //Y方向的单元格索引
	        /**
	         * Number of cells spanned horizontally by the item.
	         */
	        public int cellHSpan;  //水平方向所占高度
	        /**
	         * Number of cells spanned vertically by the item.
	         */
	        public int cellVSpan;  //垂直方向所占高度
            ...
	        public LayoutParams(Context c, AttributeSet attrs) {
	            super(c, attrs);
	            cellHSpan = 1;  //默认为高度 1
	            cellVSpan = 1;
	        }

	        public LayoutParams(ViewGroup.LayoutParams source) {
	            super(source); //默认为高度 1
	            cellHSpan = 1;
	            cellVSpan = 1;
	        }
	        
	        public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
	            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
	            this.cellX = cellX;
	            this.cellY = cellY;
	            this.cellHSpan = cellHSpan;
	            this.cellVSpan = cellVSpan;
	        }
	        ...
	    }
	...
}

 

 

对该自定义CellLayout.LayoutParams类的使用可以参考LinearLayout.LayoutParams类,我也不再赘述了。


方法2流程分析

使用属性android:layout_heigth=””以及android:layout_weight=”” 时,为某个View设置LayoutParams值。

 

其实这种赋值方法其实也如同前面那种,只不过它需要一个前期孵化过程---需要利用XML解析将布局文件

解析成一个完整的View树,可别小看它了,所有Xxx.xml的布局文件都需要解析成一个完整的View树。下面,

我们就来仔细走这个过程,重点关注如下两个方面

①、xml布局是如何解析成View树的 ;

②、android:layout_heigth=””和android:layout_weight=””的解析。


PS: 一直以来,我都想当然android:layout_heigth以及android:layout_weight这两个属性的解析过程是在

View.java内部完成的,但当我真正去找寻时,却一直没有在View.java类或者ViewGroup.java类找到。直到一位

网友的一次提问,才发现它们的藏身之地。


3、布局文件解析流程分析


解析布局文件时,使用的类为LayoutInflater。 关于该类的使用请参考如下博客:

<android中LayoutInflater的使用>>

主要有如下API方法:

publicViewinflate(XmlPullParserparser,ViewGrouproot, boolean attachToRoot)

publicViewinflate(int resource,ViewGrouproot)

publicViewinflate(int resource,ViewGrouproot, boolean attachToRoot)

这三个类主要迷惑之处在于地三个参数attachToRoot,即是否将该View树添加到root中去。具体可看这篇博客:

<<关于inflate的第3个参数>>

当然还有LayoutInflater的inflate()的其他重载方法,大家可以自行了解下。

我利用下面的例子给大家走走这个流程 :

 

public class MainActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //1、该方法最终也会调用到 LayoutInflater的inflate()方法中去解析。
        setContentView(R.layout.main);
        
        //2、使用常见的API方法去解析xml布局文件,
        LayoutInflater layoutInflater = (LayoutInflater)getSystemService();
        View root = layoutInflater.inflate(R.layout.main, null);
    }
}

 


Step 1、获得LayoutInflater的引用。

路径:\frameworks\base\core\java\android\app\ContextImpl.java

 

/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 */
class ContextImpl extends Context {
	if (WINDOW_SERVICE.equals(name)) {
        return WindowManagerImpl.getDefault();
    } else if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        synchronized (mSync) {
            LayoutInflater inflater = mLayoutInflater;
            //是否已经赋值,如果是,直接返回引用
            if (inflater != null) {
                return inflater;
            }
            //返回一个LayoutInflater对象,getOuterContext()指的是我们的Activity、Service或者Application引用
            mLayoutInflater = inflater = PolicyManager.makeNewLayoutInflater(getOuterContext());
            return inflater;
        }
    } else if (ACTIVITY_SERVICE.equals(name)) {
        return getActivityManager();
    }...
}

 

 

继续去PolicyManager查询对应函数,看看内部实现。

径:frameworks\base\core\java\com\android\internal\policy\PolicyManager.java

 

public final class PolicyManager {
	private static final String POLICY_IMPL_CLASS_NAME = "com.android.internal.policy.impl.Policy";
	private static final IPolicy sPolicy;   // 这可不是Binder机制额,这只是是一个接口,别想多啦
	static {
        // Pull in the actual implementation of the policy at run-time
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        }
        ...
	}
	...
	public static LayoutInflater makeNewLayoutInflater(Context context) {
        return sPolicy.makeNewLayoutInflater(context); //继续去实现类中去查找
    }
}

 

IPolicy接口的实现对为Policy类。路径:/frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java

 

//Simple implementation of the policy interface that spawns the right
//set of objects
public class Policy implements IPolicy{
	...
    public PhoneLayoutInflater makeNewLayoutInflater(Context context) {
        //实际上返回的是PhoneLayoutInflater类。
		return new PhoneLayoutInflater(context);
    }
}
//PhoneLayoutInflater继承至LayoutInflater类
public class PhoneLayoutInflater extends LayoutInflater {
	...
	/**
     * Instead of instantiating directly, you should retrieve an instance
     * through {@link Context#getSystemService}
     * 
     * @param context The Context in which in which to find resources and other
     *                application-specific things.
     * 
     * @see Context#getSystemService
     */
    public PhoneLayoutInflater(Context context) {
        super(context);
    }
	...
}

 

LayoutInflater是个抽象类,实际上我们返回的是PhoneLayoutInflater类,但解析过程的操作基本上是在

LayoutInflater中完成地。



Step 2、调用inflate()方法去解析布局文件。

public abstract class LayoutInflater {
    ...
    public View inflate(int resource, ViewGroup root) {
    	//继续看下个函数,注意root为null
        return inflate(resource, root, root != null); 
    }
    
    public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
    	//获取一个XmlResourceParser来解析XML文件---布局文件。
    	//XmlResourceParser类以及xml是如何解析的,大家自己有兴趣找找。
        XmlResourceParser parser = getContext().getResources().getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
}
/**
 * The XML parsing interface returned for an XML resource.  This is a standard
 * XmlPullParser interface, as well as an extended AttributeSet interface and
 * an additional close() method on this interface for the client to indicate
 * when it is done reading the resource.
 */
public interface XmlResourceParser extends XmlPullParser, AttributeSet {
    /**
     * Close this interface to the resource.  Calls on the interface are no
     * longer value after this call.
     */
    public void close();
}


我们获得了一个当前应用程序环境的XmlResourceParser对象,该对象的主要作用就是来解析xml布局文件的。

XmlResourceParser类是个接口类,更多关于XML解析的,大家可以参考下面博客:

<<android之XmlResourceParser类使用实例>>

<<android解析xml文件的方式(其一)>>

<<android解析xml文件的方式(其二)>>

<<android解析xml文件的方式(其三)>>

 

 

Step 3 、真正地开始解析工作 。

public abstract class LayoutInflater {
    ...
    /**
     * Inflate a new view hierarchy from the specified XML node. Throws
     * {@link InflateException} if there is an error.
     */
    //我们传递过来的参数如下: root 为null , attachToRoot为false 。
    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context)mConstructorArgs[0];
            mConstructorArgs[0] = mContext;  //该mConstructorArgs属性最后会作为参数传递给View的构造函数
            View result = root;  //根View

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                ...
                final String name = parser.getName();  //节点名,即API中的控件或者自定义View完整限定名。
                if (TAG_MERGE.equals(name)) { // 处理<merge />标签
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //将<merge />标签的View树添加至root中,该函数稍后讲到。
                    rInflate(parser, root, attrs);
                } else {
                    // Temp is the root view that was found in the xml
                	//创建该xml布局文件所对应的根View。
                    View temp = createViewFromTag(name, attrs); 

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        // Create layout params that match root, if supplied
                    	//根据AttributeSet属性获得一个LayoutParams实例,记住调用者为root。
                        params = root.generateLayoutParams(attrs); 
                        if (!attachToRoot) { //重新设置temp的LayoutParams
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
                    // Inflate all children under temp
                    //添加所有其子节点,即添加所有字View
                    rInflate(parser, temp, attrs);
                    
                    // 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;
                    }
                }
            } 
            ...
            return result;
        }
    }
    
    /*
     * default visibility so the BridgeInflater can override it.
     */
    View createViewFromTag(String name, AttributeSet attrs) {
    	//节点是否为View,如果是将其重新赋值,形如 <View class="com.qin.xxxView"></View>
        if (name.equals("view")) {  
            name = attrs.getAttributeValue(null, "class");
        }
        try {
            View view = (mFactory == null) ? null : mFactory.onCreateView(name,
                    mContext, attrs);  //没有设置工厂方法

            if (view == null) {
                //通过这个判断是Android API的View,还是自定义View
            	if (-1 == name.indexOf('.')) {
                    view = onCreateView(name, attrs); //创建Android API的View实例
                } else {
                    view = createView(name, null, attrs);//创建一个自定义View实例
                }
            }
            return view;
        } 
        ...
    }
    //获得具体视图的实例对象
    public final View createView(String name, String prefix, AttributeSet attrs) {
		Constructor constructor = sConstructorMap.get(name);
		Class clazz = null;
		//以下功能主要是获取如下三个类对象:
		//1、类加载器  ClassLoader
		//2、Class对象
		//3、类的构造方法句柄 Constructor
		try {
		    if (constructor == null) {
		    // Class not found in the cache, see if it's real, and try to add it
		    clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name);
		    ...
		    constructor = clazz.getConstructor(mConstructorSignature);
		    sConstructorMap.put(name, constructor);
		} else {
		    // If we have a filter, apply it to cached constructor
		    if (mFilter != null) {
		        ...   
		    }
		}
		    //传递参数获得该View实例对象
		    Object[] args = mConstructorArgs;
		    args[1] = attrs;
		    return (View) constructor.newInstance(args);
		} 
		...
	}

}

 

这段代码的作用是获取xml布局文件的root View,做了如下两件事情

1、获取xml布局的View实例,通过createViewFromTag()方法获取,该方法会判断节点名是API 控件

还是自定义控件,继而调用合适的方法去实例化View。

2、判断root以及attachToRoot参数,重新设置root View值以及temp变量的LayoutParams值。

 

如果仔细看着段代码,不知大家心里有没有疑惑:当root为null时,我们的temp变量的LayoutParams值是为

null的,即它不会被赋值?有个View的LayoutParams值为空,那么,在系统中不会报异常吗?见下面部分

代码:

    //我们传递过来的参数如下: root 为null , attachToRoot为false 。
    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...
            try {
                
                ...
                if (TAG_MERGE.equals(name)) { // 处理<merge />标签
                    ...
                } else {
                    // Temp is the root view that was found in the xml
                	//创建该xml布局文件所对应的根View。
                    View temp = createViewFromTag(name, attrs); 
                    ViewGroup.LayoutParams params = null;

                    //注意!!! root为null时,temp变量的LayoutParams属性不会被赋值的。
                    if (root != null) {
                        // Create layout params that match root, if supplied
                    	//根据AttributeSet属性获得一个LayoutParams实例,记住调用者为root。
                        params = root.generateLayoutParams(attrs); 
                        if (!attachToRoot) { //重新设置temp的LayoutParams
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
                    ...
                }
            } 
            ...
        }
    }

 

 

关于这个问题的详细答案,我会在后面讲到。这儿我简单说下,任何View树的顶层View被添加至窗口时,

一般调用WindowManager.addView()添加至窗口时,在这个方法中去做进一步处理。即使LayoutParams

空,UI框架每次measure()时都忽略该View的LayoutParams值,而是直接传递MeasureSpec值至View树。

 

接下来,我们关注另外一个函数,rInflate(),该方法会递归调用每个View下的子节点,以当前View作为根View

形成一个View树。

 

    /**
     * Recursive method used to descend down the xml hierarchy and instantiate
     * views, instantiate their children, and then call onFinishInflate().
     */
    //递归调用每个字节点
    private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)
            throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            final String name = parser.getName();
            
            if (TAG_REQUEST_FOCUS.equals(name)) { //处理<requestFocus />标签
                parseRequestFocus(parser, parent);
            } else if (TAG_INCLUDE.equals(name)) { //处理<include />标签
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, parent, attrs);//解析<include />节点
            } else if (TAG_MERGE.equals(name)) { //处理<merge />标签
                throw new InflateException("<merge /> must be the root element");
            } else {
            	//根据节点名构建一个View实例对象
                final View view = createViewFromTag(name, attrs); 
                final ViewGroup viewGroup = (ViewGroup) parent;
                //调用generateLayoutParams()方法返回一个LayoutParams实例对象,
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflate(parser, view, attrs); //继续递归调用
                viewGroup.addView(view, params); //OK,将该View以特定LayoutParams值添加至父View中
            }
        }
        parent.onFinishInflate();  //完成了解析过程,通知....
    }

 

 

值得注意的是,每次addView前都调用了viewGroup.generateLayoutParams(attrs)去构建一个LayoutParams

实例,然后在addView()方法中为其赋值。参见如下代码:ViewGroup.java

 

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
	...
	
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }
	public static class LayoutParams {
        ... //会调用这个构造函数
        public LayoutParams(Context c, AttributeSet attrs) {
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_Layout_layout_width,
                    R.styleable.ViewGroup_Layout_layout_height);
            a.recycle();
        }
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            width = a.getLayoutDimension(widthAttr, "layout_width");
            height = a.getLayoutDimension(heightAttr, "layout_height");
        }
	
}

 

 

好吧 ~~ 我们还是探寻根底,去TypeArray类的getLayoutDimension()看看。

路径:/frameworks/base/core/java/android/content/res/TypedArray.java

public class TypedArray {
	...
	/**
     * Special version of {@link #getDimensionPixelSize} for retrieving
     * {@link android.view.ViewGroup}'s layout_width and layout_height
     * attributes.  This is only here for performance reasons; applications
     * should use {@link #getDimensionPixelSize}.
     * 
     * @param index Index of the attribute to retrieve.
     * @param name Textual name of attribute for error reporting.
     * 
     * @return Attribute dimension value multiplied by the appropriate 
     * metric and truncated to integer pixels.
     */
    public int getLayoutDimension(int index, String name) {
        index *= AssetManager.STYLE_NUM_ENTRIES;
        final int[] data = mData;
        //获得属性对应的标识符 , Identifies,目前还没有仔细研究相关类。
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type >= TypedValue.TYPE_FIRST_INT
                && type <= TypedValue.TYPE_LAST_INT) {
            return data[index+AssetManager.STYLE_DATA];
        } else if (type == TypedValue.TYPE_DIMENSION) { //类型为dimension类型
            return TypedValue.complexToDimensionPixelSize(
                data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
        }
        //没有提供layout_weight和layout_height会来到此处 ,这儿会报异常!
        //因此布局文件中的View包括自定义View必须加上属性layout_weight和layout_height。
        throw new RuntimeException(getPositionDescription()
                + ": You must supply a " + name + " attribute.");
    }
	...
}


从上面得知, 我们将View的AttributeSet属性传递给generateLayoutParams()方法,让其构建合适地

 

LayoutParams对象,并且初始化属性值weight和height。同时我们也得知布局文件中的View包括自定义View

必须加上属性layout_weight和layout_height,否则会报异常。


Step 3 主要做了如下事情:
首先,获得了了布局文件地root View,即布局文件中最顶层的View。

其次,通过递归调用,我们形成了整个View树以及设置了每个View的LayoutParams对象。


 

总结:通过对布局文件的解析流程的学习,也就是转换为View树的过程,我们明白了解析过程的个中奥妙,以及

设置ViewLayoutParams对象的过程。但是,我们这儿只是简单的浮光掠影,更深层次的内容希望大家能深入学习。



本来是准备接下去往下写的,但无奈贴出来的代码太多,文章有点长而且自己也有点凌乱了,因此决定做两篇

博客发表吧。下篇内容包括如下方面:

1、MeasureSpec类说明 ;

2、measure过程中如何正确设置每个View的长宽 ;

3、UI框架正确设置顶层View的LayoutParams对象,对Activity而言,顶层View则是DecorView,

其他的皆是普通View了。










分享到:
评论

相关推荐

    S-measure(matlab)_S-measure_libraryzsp_

    标题中的"S-measure(matlab)_S-measure_libraryzsp_"提到了S-measure,这是一个在图像分析和图形检测领域中使用的评价指标。S-measure,全称Structural Similarity Measure,是针对图像分割结果的质量评估工具,它在...

    BMI.rar_BMI_android 游戏_health measure_健康_身高测量

    随着科技的发展,Android平台上的健康测量软件层出不穷,它们以不同的方式帮助用户轻松计算BMI,并提供了许多便于用户了解自身健康状况的附加功能。 “BMI”这款应用,以游戏化的方式为用户带来全新的健康管理体验...

    measurepos.rar_measure_pos_measurepos_opencv卡尺_一维测量_卡尺测量

    6. 结果显示:将测量结果可视化,可以是原始图像上标注的测量线,也可以是单独的测量数值。 在“measurepos”项目中,开发者已经实现了这些步骤,并提供了交流学习的平台。用户可以根据自己的需求进行定制和优化,...

    apm_test.rar_android_android 回音_apm_far.pcm_flatness measure_voi

    这个名为"apm_test.rar_android_android 回音_apm_far.pcm_flatness measure_voi"的压缩包文件似乎专注于Android系统中的回音消除算法,这对于实现高质量的VoIP(Voice over IP)服务至关重要。以下是对这个主题的...

    PR-F_Measure.zip_f measure _f-measure_ground truth_ground truth

    标题中的“PR-F_Measure.zip_f measure _f-measure_ground truth_ground truth”指的是一个压缩文件,其中包含了一个名为“PR-F_Measure”的应用程序,该应用主要用于评估图像处理中的边缘检测方法,如Canny和Sobel...

    voltage_measure_C语言_lcd1602_51单片机_ADC0831_

    标题中的“voltage_measure_C语言_lcd1602_51单片机_ADC0831_”项目涉及几个关键的硬件和软件组件,这些组件共同工作以实现一个实用的电压测量系统。在这个系统中,51系列单片机作为核心处理器,负责协调整个系统的...

    ads.rar_Measure For Measure_OCT

    "ads.rar_Measure For Measure_OCT"这一压缩包文件,其名称暗示了OCT技术在特定测量任务中的重要性,尤其可能是针对角膜或眼内角度的测量。 OCT是一种基于干涉原理的光学成像技术,它通过发射近红外光并分析反射...

    Android自定义Layout布局

    在项目中使用自定义布局时,可以像使用系统布局一样在XML布局文件中声明它,并添加子视图。在Android Studio中,可以创建一个res/layout目录下的XML文件,声明自定义布局类,并在`&lt;layout&gt;`标签中设置属性。 ```xml...

    Schematic-AC-Meter-AVR.rar_Measure For Measure_bascom

    根据【压缩包子文件的文件名称列表】中的"1.DSN",我们可以推测这是一个电路设计文件,可能是Proteus的DSN格式文件,其中包含了电路的详细布局和元器件信息。 在实际应用中,这个项目可能包含以下知识点: 1. **...

    measure_alpha_gamma_psca_

    "measure_alpha_gamma_pscx" 是一个与电力系统分析相关的PSCAD模型,主要涉及的是阿尔法(Alpha)和伽马(Gamma)角的测量。在电力系统中,这两个角度是交流电路参数的重要组成部分,通常与功率因数和电能质量有关。...

    Android 布局文件Layout XML属性

    在Android开发中,布局文件(Layout XML)是构建用户界面的核心元素。布局文件定义了UI组件的排列方式、大小和相互关系。这篇文章将深入探讨几种常见的Android Layout XML属性,帮助开发者更好地理解和使用它们。 ...

    Android屏幕自适应

    在XML布局文件中,我们应尽量使用dp作为尺寸单位,而不是px。例如: ```xml android:layout_width="wrap_content" android:layout_height="48dp" android:text="点击我" /&gt; ``` 2. **使用sp(scale-...

    Evaluation_evaluation_f-measure_椭圆_椭圆检测_

    "Evaluation_evaluation_f-measure_椭圆_椭圆检测_"这个标题暗示了我们正在讨论一个用于评估椭圆检测算法性能的系统,该系统可能包含了计算Recall、Precision和F-measure指标的Python代码。这些指标是衡量检测算法...

    android 动态添加布局

    在Android中,我们可以使用LayoutInflater类来动态地从XML布局文件中加载视图,并将其添加到父视图中。这里涉及的关键步骤包括: 1. 初始化LayoutInflater:通常在Activity或Fragment中,我们可以通过`...

    Android实现View排列自动换行

    最后,别忘了在XML布局文件中使用自定义的`WordWrapLayout`,并添加子View: ```xml xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height...

    Android 标签流式布局

    在Android开发中,"标签流式布局"是一种常见的界面设计方式,它允许我们在屏幕上水平排列多个标签,并在空间不足时自动换行。这种布局模式在许多应用中都有所应用,如新闻阅读应用的分类标签、设置菜单的选项标签等...

    Geometric_Measure_Theory_Beginner_Guide

    几何测度理论(Geometric Measure Theory, GMT)是一门研究空间中各种形状及其性质的数学分支,特别是那些涉及最小化问题的情况。该理论在理解自然界中的复杂现象方面扮演着关键角色,如肥皂泡、黑洞、材料缺陷、...

    Measure_symboliuv_keiluvision3_

    标题“Measure_symboliuv_keiluvision3_”暗示了一个与测量相关的项目,可能是一个示例或教程,其中涉及了“symboliuv”和“keiluvision3”这两个关键元素。"symboliuv"可能是指一个特定的符号或变量在代码中的用法...

Global site tag (gtag.js) - Google Analytics