Android开发&简单的ViewGroup——FrameLayout

    开门见山的说,一般android开发中,FrameLayout更多的是作为图层功能,或者碎片占位符;如时下的身份证扫描界面,可以利用FrameLayout实现两级图层;再有就是一些自定义的控件,往往是FrameLayout的子类;
    其实对于我来说,这个从出生起一直生存到现在的空间,直接使用的次数并不算多,因为它的功能实在是太过简单,虽然方便定制视图,但造轮子的事情早已有无数的先辈们替我们完成了,因此。。。

一、 简单的FrameLayout

FrameLayout存在的意义仿佛只是提供一个容器,凭借自己的想象,可以放置任何控件,甚至大小什么都无所谓,反正超过屏幕的部分会消失掉,不过也正是如此,才体现出了ViewGroup的含义——可以放View的容器;当然,嵌套ViewGroup也是可以的,毕竟ViewGroup也是View的实现类,虽然两者“长相”相差有点大。

二、 ViewGroup 与View

简单一提,View和ViewGroup之间的关系错综复杂,但他们有一个很直观的相同的特点——都能被看到;
View表示的是视图,能被看到,才能更好的表达信息,才能够与用户交互,否则就又要回归单片机的世界了。
这里就牵扯出重要的部分,view的呈现机制,见得很多地方将“能够理解View机制”作为一个中级菜鸟的合格证,不可否认,纯粹做一个码农,利用现有的控件或者大神写好的类,不理解这些东西也活的下去,确实如此,即便懂了这些,更深层次的底层实现我们还是一无所知,所以,兴趣才是前提。

三、 View的呈现机制

依靠计算机来理解人的思维,并显示到屏幕上,是个有些无从下手的问题,还好,最难的部分已经有人做完了,我们所做的只是拿着一堆的七巧板,来拼凑出让人耳目一新的效果(或许一个良好的UI设计师才适合做移动开发)。好在程序员只需要做一个简单的工作:用丑陋的界面实现交互的功能,剩下的部分就让其他人去完成吧。
提及View,不得不推一下那些总结出结论的前辈,没有他们的努力,在不知道结论的情况下我是不怎么喜欢去瞅源码的(时间好紧张。。。)。可以先看一下一系列的文章:深入了解View(一)
反正就四篇,即便看不懂,先记住结论也是可以的,有一点需要说明,因为上述博客太过经典,所以有些滞后,而sdk版本更迭是很快的,因此有些源码可能会与自己手头的文档有些差异,不过没关系,反正记住了结论,就可以参照源码自己分析了。
言归正传,一个View想要显示在屏幕上,需要的过程是复杂的,不过这已经被人为的抽象成了三个阶段(当前分析参照API23)

  1. measure 测量:确定view的大小
  2. layout 定位:确定view所在的坐标位置
  3. draw 绘制:在指定的坐标位置绘制出view

又是一个老生常谈的部分,没办法,黔驴技穷了,要是不说这个,还真就无从开始了,不过还好,只要看完上述一系列的四篇博客,至少可以去翻源码了,主要总结一下也不复杂:measure、layout和draw都是由一个类调用的,恩,一称为ViewRoot,在该类中由performTraversals()发起这三个过程,先盗用百度图片:

Android开发&简单的ViewGroup——FrameLayout_第1张图片

若单说view的话,只从DecorView开始就行了;DecorView可看做一个纵向排列的LinearLayout,包含两个子布局TitleView以及ContentView,TitleView我们一般都让它消失,因此只看ContentView就行,该View不仅是一个布局,并且还是正好要讨论的FrameLayout,再好不过。
先简单的叙述一下measure,layout,draw好了,反正看完博客自己也可以理解,就不过多××了。

【measure过程】
1、View系统的绘制流程会从ViewRoot的performTraversals()开始,测量根view(这里从FrameLayout开始考虑比较简单)
2、对于根View,会调用View的measure(),final类型,无法重写,measure()中会调用onMeasure()
3、ViewGroup的子类中一般会重写onMeasure(),完成两个操作:测量自身,测量子布局(调用子View的measure(),返回第2步递归调用)。而对于View的子类(不是ViewGroup的子类)而言,只完成一个操作:测量自身。
4、View的子类(非ViewGroup子类)中onMeasure()会调用setMeasureDimension()为成员变量mMeasuredWidth和mMeasuredHeight赋值,完成测量自身的工作;

【layout过程】
1、ViewRoot的performTraversals()会在measure结束后继续执行,并调用View的layout()来执行定位过程。
2、View类的layout()会调用onLayout(),View类的onLayout()为空,不进行任何操作;onLayout()主要确定视图在布局中的位置,因此一般由具体的ViewGroup的子类来重写(且在ViewGroup类中,onLayout为抽象方法)。(View类的layout()只会完成定位自身的工作,ViewGroup的onLayout()来让子View调用layout())
3、也就是说:若调用layout方法的为View的子类,那整个layout过程就已经结束,如果调用layout方法的为ViewGroup子类,则会在ViewGroup子类中调用onLayout方法,该方法会调用所有子view的layout方法,即返回第2步,递归调用

【draw过程】
ViewRoot的performTraversals()会在layout结束后继续执行;会先调用最上层视图的draw方法(View类中重载了两个draw方法,一个为public权限,一个为默认权限,首先系统会调用default的draw(),在其中会调用computeScroll()。
接下来,一般情况来看(忽略滚动条的绘制或保存canvas等比较麻烦的部分):
若对象为View的子类,不是ViewGroup的子类:default的draw()会调用public的draw()。
若对象为ViewGroup的子类,且ViewGroup自身没有可视部分,则default的draw()会直接调用dispatchDraw(),即一些ViewGroup子类中并不会调用public的draw()和onDraw()。
一般来说,public的draw()不会被重写,该方法会依次进行如下操作:
* 1、绘制background
* 2、If necessary, save the canvas’ layers to prepare for fading(暂不考虑)
* 3、Draw view’s content,此时即调用onDraw(),View类的该方法为空,一般View的子类需要重写该方法,进行内容的绘制。
* 4、draw the children,调用dispatchDraw(),该方法在View类中为空方法,在ViewGroup子类中则有具体的实现。dispatchDraw()中会调用ViewGroup子类中的drawChild(),drawChild()中会让子View调用View类非public的draw(),即返回draw流程开始部分。
* 5、If necessary, draw the fading edges and restore layers
* 6、Draw decorations (scrollbars for instance),即前景onDrawForeground 等(忽略此步骤即可)

当然肯定还有一些东西没有考虑到,比如padding,margin,滚动等等,不过熟悉基本调用过程就可以自定义view了。然后参照一下FrameLayout的源码,看一下一个ViewGroup如何对自身和子view完成这三个流程。

四、FrameLayout代码解析

再次说明,这里参照的SDK版本为23,每一个版本代码都会有变化,因此最好找个尽可能“古老”的版本,至少逻辑会清楚一些。接下来代码会将FrameLayout中重要部分全部注释出来,有很多变量源码中也找不到引用,因此这里只是将一些有View呈现有关的方法列出,一些成员变量即便没有引用的出处,也给出了大概表示的含义。

package com.android.senior.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by Administrator on 17-1-7.
 * 

* 注释FrameLayout的代码 */ public class FrameLayout extends android.widget.FrameLayout { //默认的gravity private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START; /** * 构造函数 */ public FrameLayout( Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); //获取属性集合 final TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.FrameLayout, defStyleAttr, defStyleRes); //获取xml中FrameLayout的measureAllChildren属性,属性为true则会在测量等过程考虑所有的即使是View.Gone的子View,默认为false if (a.getBoolean(com.android.internal.R.styleable.FrameLayout_measureAllChildren, false)) { setMeasureAllChildren(true); } a.recycle(); } /** * 设置是否考虑所有的子view,即连同View.GONE的子view都进行测量,这里只是修改了一个标志位 */ @android.view.RemotableViewMethod public void setMeasureAllChildren(boolean measureAll) { mMeasureAllChildren = measureAll; } /** * 这里及接下来的三个方法可以先简单的看做时获取FrameLayout的padding值的(其实该值需要参照xml中FrameLayout的padding属性值和background(背景图片或背景资源xml文件——R.drawable.*)中的padding值) */ int getPaddingLeftWithForeground() { return isForegroundInsidePadding() ? Math.max(mPaddingLeft, mForegroundPaddingLeft) : mPaddingLeft + mForegroundPaddingLeft; } int getPaddingRightWithForeground() { return isForegroundInsidePadding() ? Math.max(mPaddingRight, mForegroundPaddingRight) : mPaddingRight + mForegroundPaddingRight; } private int getPaddingTopWithForeground() { return isForegroundInsidePadding() ? Math.max(mPaddingTop, mForegroundPaddingTop) : mPaddingTop + mForegroundPaddingTop; } private int getPaddingBottomWithForeground() { return isForegroundInsidePadding() ? Math.max(mPaddingBottom, mForegroundPaddingBottom) : mPaddingBottom + mForegroundPaddingBottom; } /** * 系统会在View的measure方法中调用onMeasure方法,该方法完成两个操作: * 测量FrameLayout自身的大小 * 测量每一个子View的大小 *

* 在该方法调用之后,才可以通过getMeasuredWidth()和getMeasuredHeight()方法获取FrameLayout真实的大小 * {@inheritDoc} */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //获取所有子View的个数,该方法返回ViewGroup的成员变量mChildrenCount,该成员变量在ViewGroup的addInArray方法中更改值的大小。 int count = getChildCount(); //当传入的宽高对象中mode不都是EXACTLY时,该变量measureMatchParentChildren为true;即表示该FrameLayout的宽高还是有待测量的,不等于父布局的宽高(先不考虑margin和padding) final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; //mMatchParentChildren表示View的集合,该集合中每个宽高属性中至少有一个为MATCH_PARENT mMatchParentChildren.clear(); //maxHeight表示该FrameLayout最大的高度,maxWidth同理;childState属性暂时不考虑 int maxHeight = 0; int maxWidth = 0; int childState = 0; //对所有的子View进行遍历 for (int i = 0; i < count; i++) { final View child = getChildAt(i); //mMeasureAllChildren默认为false,在构造函数中进行了赋值操作,因此不为Gone则进入if中 if (mMeasureAllChildren || child.getVisibility() != GONE) { //先对子View进行一次测量,此次测量出的子View宽高是没有除去子view的margin以及FrameLayout的padding的,因此肯定不准确;在该measureChildWithMargins方法中会让子View去调用自身的measure方法;这里可以成为估测 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); //获取子View的LayoutParams对象,该对象中包括了layout_width等属性的值 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //取(maxWidth,子View的width加上子view的左右margin值)中较大的值,maxHeight同理 maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { //如果此FrameLayout的宽高并不能直接确定,且子View的宽高有一个是MATCH_PARENT,则将该子View放到mMatchParentChildren集合中,该集合表示match自身布局的子View集合 mMatchParentChildren.add(child); } } } } // Account for padding too;负责padding的处理,将上面计算出的maxWidth和maxHeight再加上自身定义的padding值(paddingBottom+paddingTop+maxHeight,paddingLeft+paddingRight+maxWidth) maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width;结合minHeight等属性来选择出最大的一个maxHeight和maxWidth值 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width;结合background等属性来选择出最大的一个maxHeight和maxWidth值 final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } //提供一个maxWidth值和widthMeasureSpec对象,Spec对象中可以提取Mode属性,根据该属性来决定FrameLayout自身应当取的width值;测量height同理;然后调用setMeasuredDimension方法,该方法调用之后,通过getMeasuredWidth()和getMeasuredHeight()方法才能获取到FrameLayout正确的宽高值。 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); //如果FrameLayout的宽高并不能直接确定(如果可以确定的话mMatchParentChildren的size肯定为0),且有超过一个子View在集合中 count = mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); //获取MarginLayoutParams对象,该对象在ViewGroup中定义,所有自定义的ViewGroup对象中的LayoutParams对象基本都是该对象的子类 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec; //如果子View宽为MATCH_PARENT,则进行如下操作(注:mMatchParentChildren集合中所有的子View宽高至少有一个属性为MATCH_PARENT) if (lp.width == LayoutParams.MATCH_PARENT) { //这里getMeasuredWidth()就 可以获取到该FrameLayout正确的宽度;然后减去FrameLayout可能有的paddingLeft和paddingRight值,在减去子view中可能定义的layout_marginLeft,layout_marginRight值,剩下的部分就是子View的宽度;如果FrameLayout测量正确且宽度为MATCH_PARENT,则此处就完成了子view宽度的测量。 final int width = Math.max(0, getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin); //宽度值MATCH_PARENT属于EXACTLY模式,并且求出了子view的width,因此可以生成宽度的Spec对象。 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY); } else { //如果宽度不是MATCH_PARENT,那么高度肯定是 MATCH_PARENT;此时要计算子View的宽度,就只能依赖view的measure过程中最最核心的方法——getChildMeasureSpec();该方法根据父布局的widthMeasureSpec,多余的FrameLayout的padding、子View的margin,以及子布局估测的宽度来综合得出一个合理的子view的width值 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } //childHeightMeasureSpec计算方法与子view的width的计算方法类似 final int childHeightMeasureSpec; if (lp.height == LayoutParams.MATCH_PARENT) { final int height = Math.max(0, getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( height, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } //然后让子view去调用一次measure方法 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } } /** * ViewGroup类中onLayout为抽象方法,因此所有的ViewGroup的实现类必须重写该方法 *

* View类中onLayout的方法说明如下: * Derived classes with children should override * this method and call layout on each of * their children. * 即:所有的ViewGroup实现类,都必须重写onLayout方法,并在该方法中调用所有子View的layout方法; * 更多的是,在View的layout方法中,已经通过setFrame方法确定了自身的位置 *

* 注:layout过程是一个确定位置的过程,即父布局有所有权来决定子布局应当所处的位置,这与measure过程是不同的 * measure过程需要父布局给的限定大小,以及Spec的mode,再加上子view的mode来综合确定。 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //根据该方法的说明,在调用onLayout方法前,就已经在layout方法中调用了setFrame为ViewGroup(这里是FrameLayout)自身进行了layout过程,即left,top,right,bottom已经被赋值了。 layoutChildren(left, top, right, bottom, false /* no force left gravity */); } /** * 尤其重要的是,FrameLayout的left,top,right,bottom值是相对于FrameLayout的父布局而言的;对子view调用layout方法时,传入的坐标系则是以FrameLayout而言的,这点需要进行转换 * * @param left FrameLayout的左侧位置 * @param top FrameLayout的顶部位置 * @param right FrameLayout的右侧位置 * @param bottom FrameLayout的底部位置 * @param forceLeftGravity 是否强行设置子view的排列方式Gravity为left */ void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { //子view的个数 final int count = getChildCount(); //这里进行坐标系的变换:FrameLayout的左上角现在变为了(0,0),因此FrameLayout的paddingLeft值会限定子View所处的位置,即子view的left值一般情况下是小于FrameLayout的paddingLeft值的(有一种情况除外,即子view的marginLeft属性可能设置为负数),因此这里parentLeft表示:相对于子view来说,最左侧的坐标值。 paddingRight属性值 final int parentLeft = getPaddingLeftWithForeground(); //同理,进行坐标系转换,parentRight表示子View右侧坐标最大的值,计算方法是:FrameLayout的右侧坐标减去左侧坐标减去paddingRight值,即width减去paddingRight(正常情况下) final int parentRight = right - left - getPaddingRightWithForeground(); //这里parentTop和parentBottom同上 final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); //开始layout子view的过程 for (int i = 0; i < count; i++) { final View child = getChildAt(i); //如果子view的visibility为View.GONE,则不用考虑,直接当做没有这个子View if (child.getVisibility() != GONE) { //子view获取LayoutParams,这个对象可以当做在xml文件中添加元素时设置的,padding,margin,layout_width等值的集合。 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //因为这个方法属于layout过程,measure过程会先执行,因此这里可以直接获取子view的宽高 final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); //子view的左侧,上侧位置;这里只需要这两个属性就行,右下坐标可以结合width,height属性得到 int childLeft; int childTop; //gravity:子view的放置方式,这里与layout_gravity要区分开;layout_gravity表示FrameLayout在父布局中如何放置;gravity表示子view在FrameLayout中如何放置; int gravity = lp.gravity; if (gravity == -1) { //这里很明显,如果gravity为-1,则设置gravity为默认值;这里的-1从何而来暂不考虑,姑且当做如果FrameLayout没有设置gravity属性,则lp.gravity默认为-1 gravity = DEFAULT_CHILD_GRAVITY; } //layoutDirection表示习惯的阅读方式,一般习惯时从左向右读,但有些情况下(如古代诗文的阅读方式,或台湾文字阅读方式),阅读时从右向左的;该属性值可以通过在manifest文件中application节点下设置android:supportsRtl 来改变 final int layoutDirection = getLayoutDirection(); //结合gravity和layoutDirection得出真实的布局方式absoluteGravity(水平方向) final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); //获取竖直方向上的布局方式 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: //如果是水平方向上时Gravity.CENTER_HORIZONTAL布局方式,则子view左侧坐标就是——parentLeft(子view初始处于的最左侧的坐标)【加上】(parentRight - parentLeft - width)/2(这个表示ziview可以处于的最右侧坐标减去最左侧坐标再减去子view的宽度,得到横向上除内容部分空白区域的宽度,然后除以2得到左侧空白的宽度)【加上】lp.leftMargin(子view的marginLeft的值,该值可能为负数,为负数则表示向左偏移的坐标数)【减去】lp.rightMargin(子view的marginRight的值);其实从这里可以看到,leftMargin和rightMargin在这种情况下不是相对左右侧的距离,而是偏移量 childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (!forceLeftGravity) { //如果是Gravity.RIGHT布局方式,并且没有强行限制forceLeftGravity,则子view左侧坐标为parentRight减去子view的宽度,再留出marginLeft的空白 childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: //其他情况就相对简单,parentLeft加上子view的marginLeft的值;这里需要说明:安卓中子view的宽度和前端中div宽度是不同的,这里border只是背景的一种效果,padding只是限制内容区域或者子view应当偏离的值,这两者不会影响自身的width(会影响子view的大小),而margin只是设置自身在父布局中偏离的值,同样不会修改自身width,事实上在measure过程中,一个view的大小就已经确定了 childLeft = parentLeft + lp.leftMargin; } //计算childTop和计算childLeft类似; switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } //然后通过childLeft和childTop就可以确定子view所处的坐标了 child.layout(childLeft, childTop, childLeft + width, childTop + height); } } } /** * 在FrameLayout中没有draw过程,因此draw过程会调用父类ViewGroup中默认的dispatchDraw方法 * * 一般而言,带有进度条ScrollBar的view绘制流程会很复杂,view的绘制流程参照View类的public的draw方法可以看到很清楚。 * * 到此为止,FrameLayout中measure,layout,draw三个view流程全部完成。 */ /** * 还有一个重要的部分,也是每个ViewGroup子类都会有的部分——定义自己的LayoutParams *

* 所有ViewGroup子类中的LayoutParams都继承自ViewGroup类的MarginLayoutParams,因此动态添加布局时,可以使用MarginLayoutParams对象,这样就不用在不同的ViewGroup中设置不同的LayoutParams对象了。 *

* Per-child layout information for layouts that support margins. * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes} * for a list of all child view attributes that this class supports. * * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity */ public static class LayoutParams extends MarginLayoutParams { /** * 在这里可以看到,默认的gravity属性值确实是-1 *

* The gravity to apply with the View to which these layout parameters * are associated. * * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity * @see android.view.Gravity */ public int gravity = -1; /** * {@inheritDoc} */ public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout); //在未设置gravity属性值时,该方法返回值也是-1 gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1); a.recycle(); } /** * {@inheritDoc} */ public LayoutParams(int width, int height) { super(width, height); } /** * 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 gravity the gravity * @see android.view.Gravity */ public LayoutParams(int width, int height, int gravity) { super(width, height); this.gravity = gravity; } /** * {@inheritDoc} */ public LayoutParams(ViewGroup.LayoutParams source) { super(source); } /** * {@inheritDoc} */ public LayoutParams(ViewGroup.MarginLayoutParams source) { super(source); } /** * Copy constructor. Clones the width, height, margin values, and * gravity of the source. * * @param source The layout params to copy from. */ public LayoutParams(LayoutParams source) { super(source); this.gravity = source.gravity; } } }

由于代码过多,因此可能有些东西会出错,不过对于一个真实的ViewGroup的实现类而言,除了draw过程有些“水”,measure和layout过程都考虑到了很多方面,包括margin,padding,gravity,rtl等等;其实参照源码可以更好的理解,一些关键的英文注释会指明方法的功能等。

五、Broad vision

在大多时候,需要查看多个文档才能多篇博客,才能真正理解,即便是百年难得一用的FrameLayout,在配合上自定义的事件动画时,也可以有好的效果。类似这种Android View框架的measure机制以前感觉很神奇的问题,总有人知道为什么。总要多看看,当然,前提是喜欢 Android View刷新机制 。还有,发现不管学多少,都只是起点ViewGroup详解

你可能感兴趣的:(安卓开发)