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):
- measure 测量:确定view的大小
- layout 定位:确定view所在的坐标位置
- 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相关推荐
- Android开发——简单计算器实现
计算器项目,要求实现加.减.乘.除.求倒数.求平方根等简单运算. 真机调试结果如下图: 布局文件:main_activity.xml <?xml version="1.0" ...
- Android开发—简单的图片浏览器
开发工具:ecplise 图片浏览器:实现简单的图片浏览功能,点击当前图片会自动切换到下一张图片 1.先在布局文件中定义一个简单的线性布局容器 <?xml version="1.0&q ...
- Android 开发简单记事本程序(附源码)
简单介绍一下功能:简单记事本只能添加文字内容,首页用ListView显示所有保存的事项和保存的时间,添加页面添加内容,点击首页的ListView可以查看内容,删除内容. 先看一下运行效果: 先建立数据 ...
- Android开发简单人脸签到系统开发
需求分析 第一步我对该实验做了一个完整性的需求分析: 我们需要做到以下几点: (1)学生信息的管理,对学生的信息的添加,删除,编译修改: (2)基于每个学生的人脸上传,并识别该图片是否是一个人脸图片: ...
- Android开发--简单实现Android应用的启动页
Android启动页效果展示 平时打开手机的应用时,会跳出来3秒钟的广告后,再进入应用.今天我们就来简单实现一下引导页的功能. 1.首先,新建一个activity页面,命名:SplashActivit ...
- 做简单的android 软件推荐,Android_适用于Android开发的简单聊天软件,适用于android 开发。是一个简 - phpStudy...
适用于Android开发的简单聊天软件 适用于android 开发.是一个简单的聊天软件,包括知识点,各个控件的运用(ExpandableListView,ViewPager,Spinner,Line ...
- Android开发实践:自定义ViewGroup的onLayout()分析
Android开发中,对于自定义View,分为两种,一种是自定义控件(继承View类),另一种是自定义布局容器(继承ViewGroup).如果是自定义控件,则一般需要重载两个方法,一个是onMeasu ...
- android开发分页查询,Android开发中实现分页效果的简单步骤
分页加载在程序开发中是必备的,但是我们实现这个功能并不仅仅为了美观,用户体验也是很重要的,爱站技术频道下面就带大家了解Android开发中实现分页效果的简单步骤,感兴趣的小伙伴们参考看看吧! 具体内容 ...
- android简单实现表格布局,Android开发中TableLayout表格布局
Android开发中TableLayout表格布局 一.引言 在移动端应用程序开发中,常常会使用到表格布局,iOS和Android开发框架中都提供了独立的表格视图控件供开发者使用,例如iOS中的UIT ...
最新文章
- 参考WebStorm设置VSCode“转到编辑器中的符号”快捷键为Shift双击(这是一个频繁使用的快捷键)
- mysql单引号和双引号
- python的特点和优点-Python的优点和缺点有哪些?Python语言的特点
- 如何在Spring Boot App中集成H2数据库
- 弹性盒模型--新版与旧版比较(2)
- 电商数据库设计及架构优化实战(一) - 制定数据库开发规范
- SQL SERVER 2005 数据挖掘与商业智能完全解决方案---学习笔记(一)
- 75道程序员面试逻辑思维题
- raw格式转换jpg软件 V5.2
- 135编辑器代码是html吗,不会代码,你也能做背景样式!!!
- TalkingData三大产品创新,引领2022数字营销技术新格局
- 使用Riverbed SteelCentral NetProfiler,大海捞针不再难
- 车牌识别停车场智能管理系统
- MAC库乐队、APP残留清理
- 基于ThreeJS修改模型材质
- 什么是线程安全 什么是线程不安全
- 2014ACM亚洲区域北京邀请赛总结
- 前端开发 SSR 是什么技术?
- 《第1阶段》——正交试验法
- 常见的一些反爬虫策略(下篇)-Java网络爬虫系统性学习与实战系列(10)
热门文章
- 苹果手机iphone研发、设计、生产、供应链体系,附加值占比,以及中国为什么没有产出这样的跨国企业?
- 疼你的心一直守在你周围
- 维盟智能路由_维盟路由器
- imfinfo matlab,MATLAB函数imfinfo函数
- (转)每个架构师都应该研究下康威定律
- POST和GET区别 -- 面试重点之一
- 前程无忧发布2023年中国大学生喜爱雇主榜单,140家企业上榜 | 美通社头条
- 鸿蒙os基于安卓架构,对标Apple Watch!魅族推首款智能手表,官宣接入华为鸿蒙系统...
- 0XID定位多网卡主机 使用记录
- Mac 下使用ll命令