开门见山的说,一般android开发中,FrameLayout更多的是作为图层功能,或者碎片占位符;如时下的身份证扫描界面,可以利用FrameLayout实现两级图层;再有就是一些自定义的控件,往往是FrameLayout的子类;
其实对于我来说,这个从出生起一直生存到现在的空间,直接使用的次数并不算多,因为它的功能实在是太过简单,虽然方便定制视图,但造轮子的事情早已有无数的先辈们替我们完成了,因此。。。
FrameLayout存在的意义仿佛只是提供一个容器,凭借自己的想象,可以放置任何控件,甚至大小什么都无所谓,反正超过屏幕的部分会消失掉,不过也正是如此,才体现出了ViewGroup的含义——可以放View的容器;当然,嵌套ViewGroup也是可以的,毕竟ViewGroup也是View的实现类,虽然两者“长相”相差有点大。
简单一提,View和ViewGroup之间的关系错综复杂,但他们有一个很直观的相同的特点——都能被看到;
View表示的是视图,能被看到,才能更好的表达信息,才能够与用户交互,否则就又要回归单片机的世界了。
这里就牵扯出重要的部分,view的呈现机制,见得很多地方将“能够理解View机制”作为一个中级菜鸟的合格证,不可否认,纯粹做一个码农,利用现有的控件或者大神写好的类,不理解这些东西也活的下去,确实如此,即便懂了这些,更深层次的底层实现我们还是一无所知,所以,兴趣才是前提。
依靠计算机来理解人的思维,并显示到屏幕上,是个有些无从下手的问题,还好,最难的部分已经有人做完了,我们所做的只是拿着一堆的七巧板,来拼凑出让人耳目一新的效果(或许一个良好的UI设计师才适合做移动开发)。好在程序员只需要做一个简单的工作:用丑陋的界面实现交互的功能,剩下的部分就让其他人去完成吧。
提及View,不得不推一下那些总结出结论的前辈,没有他们的努力,在不知道结论的情况下我是不怎么喜欢去瞅源码的(时间好紧张。。。)。可以先看一下一系列的文章:深入了解View(一)
反正就四篇,即便看不懂,先记住结论也是可以的,有一点需要说明,因为上述博客太过经典,所以有些滞后,而sdk版本更迭是很快的,因此有些源码可能会与自己手头的文档有些差异,不过没关系,反正记住了结论,就可以参照源码自己分析了。
言归正传,一个View想要显示在屏幕上,需要的过程是复杂的,不过这已经被人为的抽象成了三个阶段(当前分析参照API23):
又是一个老生常谈的部分,没办法,黔驴技穷了,要是不说这个,还真就无从开始了,不过还好,只要看完上述一系列的四篇博客,至少可以去翻源码了,主要总结一下也不复杂:measure、layout和draw都是由一个类调用的,恩,一称为ViewRoot,在该类中由performTraversals()发起这三个过程,先盗用百度图片:
若单说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完成这三个流程。
再次说明,这里参照的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等等;其实参照源码可以更好的理解,一些关键的英文注释会指明方法的功能等。
在大多时候,需要查看多个文档才能多篇博客,才能真正理解,即便是百年难得一用的FrameLayout,在配合上自定义的事件动画时,也可以有好的效果。类似这种Android View框架的measure机制以前感觉很神奇的问题,总有人知道为什么。总要多看看,当然,前提是喜欢 Android View刷新机制 。还有,发现不管学多少,都只是起点ViewGroup详解