开门见山的说,一般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()发起这三个过程,先盗用百度图片:

若单说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.* <p/>* 注释FrameLayout的代码*/
public class FrameLayout extends android.widget.FrameLayout {//默认的gravityprivate 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,默认为falseif (a.getBoolean(com.android.internal.R.styleable.FrameLayout_measureAllChildren, false)) {setMeasureAllChildren(true);}a.recycle();}/*** 设置是否考虑所有的子view,即连同View.GONE的子view都进行测量,这里只是修改了一个标志位*/@android.view.RemotableViewMethodpublic 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的大小* <p/>* 在该方法调用之后,才可以通过getMeasuredWidth()和getMeasuredHeight()方法获取FrameLayout真实的大小* {@inheritDoc}*/@Overrideprotected 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_PARENTmMatchParentChildren.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的实现类必须重写该方法* <p/>* 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方法确定了自身的位置* <p/>* 注:layout过程是一个确定位置的过程,即父布局有所有权来决定子布局应当所处的位置,这与measure过程是不同的* measure过程需要父布局给的限定大小,以及Spec的mode,再加上子view的mode来综合确定。*/@Overrideprotected 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,则不用考虑,直接当做没有这个子Viewif (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默认为-1gravity = 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* <p/>* 所有ViewGroup子类中的LayoutParams都继承自ViewGroup类的MarginLayoutParams,因此动态添加布局时,可以使用MarginLayoutParams对象,这样就不用在不同的ViewGroup中设置不同的LayoutParams对象了。* <p/>* 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* <p/>* 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属性值时,该方法返回值也是-1gravity = 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详解

Android开发简单的ViewGroup——FrameLayout相关推荐

  1. Android开发——简单计算器实现

    计算器项目,要求实现加.减.乘.除.求倒数.求平方根等简单运算. 真机调试结果如下图: 布局文件:main_activity.xml <?xml version="1.0" ...

  2. Android开发—简单的图片浏览器

    开发工具:ecplise 图片浏览器:实现简单的图片浏览功能,点击当前图片会自动切换到下一张图片 1.先在布局文件中定义一个简单的线性布局容器 <?xml version="1.0&q ...

  3. Android 开发简单记事本程序(附源码)

    简单介绍一下功能:简单记事本只能添加文字内容,首页用ListView显示所有保存的事项和保存的时间,添加页面添加内容,点击首页的ListView可以查看内容,删除内容. 先看一下运行效果: 先建立数据 ...

  4. Android开发简单人脸签到系统开发

    需求分析 第一步我对该实验做了一个完整性的需求分析: 我们需要做到以下几点: (1)学生信息的管理,对学生的信息的添加,删除,编译修改: (2)基于每个学生的人脸上传,并识别该图片是否是一个人脸图片: ...

  5. Android开发--简单实现Android应用的启动页

    Android启动页效果展示 平时打开手机的应用时,会跳出来3秒钟的广告后,再进入应用.今天我们就来简单实现一下引导页的功能. 1.首先,新建一个activity页面,命名:SplashActivit ...

  6. 做简单的android 软件推荐,Android_适用于Android开发的简单聊天软件,适用于android 开发。是一个简 - phpStudy...

    适用于Android开发的简单聊天软件 适用于android 开发.是一个简单的聊天软件,包括知识点,各个控件的运用(ExpandableListView,ViewPager,Spinner,Line ...

  7. Android开发实践:自定义ViewGroup的onLayout()分析

    Android开发中,对于自定义View,分为两种,一种是自定义控件(继承View类),另一种是自定义布局容器(继承ViewGroup).如果是自定义控件,则一般需要重载两个方法,一个是onMeasu ...

  8. android开发分页查询,Android开发中实现分页效果的简单步骤

    分页加载在程序开发中是必备的,但是我们实现这个功能并不仅仅为了美观,用户体验也是很重要的,爱站技术频道下面就带大家了解Android开发中实现分页效果的简单步骤,感兴趣的小伙伴们参考看看吧! 具体内容 ...

  9. android简单实现表格布局,Android开发中TableLayout表格布局

    Android开发中TableLayout表格布局 一.引言 在移动端应用程序开发中,常常会使用到表格布局,iOS和Android开发框架中都提供了独立的表格视图控件供开发者使用,例如iOS中的UIT ...

最新文章

  1. 参考WebStorm设置VSCode“转到编辑器中的符号”快捷键为Shift双击(这是一个频繁使用的快捷键)
  2. mysql单引号和双引号
  3. python的特点和优点-Python的优点和缺点有哪些?Python语言的特点
  4. 如何在Spring Boot App中集成H2数据库
  5. 弹性盒模型--新版与旧版比较(2)
  6. 电商数据库设计及架构优化实战(一) - 制定数据库开发规范
  7. SQL SERVER 2005 数据挖掘与商业智能完全解决方案---学习笔记(一)
  8. 75道程序员面试逻辑思维题
  9. raw格式转换jpg软件 V5.2
  10. 135编辑器代码是html吗,不会代码,你也能做背景样式!!!
  11. TalkingData三大产品创新,引领2022数字营销技术新格局
  12. 使用Riverbed SteelCentral NetProfiler,大海捞针不再难
  13. 车牌识别停车场智能管理系统
  14. MAC库乐队、APP残留清理
  15. 基于ThreeJS修改模型材质
  16. 什么是线程安全 什么是线程不安全
  17. 2014ACM亚洲区域北京邀请赛总结
  18. 前端开发 SSR 是什么技术?
  19. 《第1阶段》——正交试验法
  20. 常见的一些反爬虫策略(下篇)-Java网络爬虫系统性学习与实战系列(10)

热门文章

  1. 苹果手机iphone研发、设计、生产、供应链体系,附加值占比,以及中国为什么没有产出这样的跨国企业?
  2. 疼你的心一直守在你周围
  3. 维盟智能路由_维盟路由器
  4. imfinfo matlab,MATLAB函数imfinfo函数
  5. (转)每个架构师都应该研究下康威定律
  6. POST和GET区别 -- 面试重点之一
  7. 前程无忧发布2023年中国大学生喜爱雇主榜单,140家企业上榜 | 美通社头条
  8. 鸿蒙os基于安卓架构,对标Apple Watch!魅族推首款智能手表,官宣接入华为鸿蒙系统...
  9. 0XID定位多网卡主机 使用记录
  10. Mac 下使用ll命令