如果你喜欢讨论源码,一起加入群:524727903

在ViewGroup中会调用onLayout方法(在ViewGroup类中是抽象的,在子元素中实现,一会会用LinearLayout进行举例子)去遍历所有的子元素,并调用其layout方法,在layout方法中又会调用子元素的onLayout方法。

LinearLayout的onLayout方法

@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {if (mOrientation == VERTICAL) {layoutVertical(l, t, r, b);} else {layoutHorizontal(l, t, r, b);}}

可以看到与onMeasure方法类似,分为水平方向和垂直方向,我们只分析一下垂直方向的源码:

 void layoutVertical(int left, int top, int right, int bottom) {final int paddingLeft = mPaddingLeft;int childTop;int childLeft;// Where right end of child should gofinal int width = right - left;int childRight = width - mPaddingRight;// Space available for childint childSpace = width - paddingLeft - mPaddingRight;final int count = getVirtualChildCount();final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;switch (majorGravity) {case Gravity.BOTTOM:// mTotalLength contains the padding alreadychildTop = mPaddingTop + bottom - top - mTotalLength;break;// mTotalLength contains the padding alreadycase Gravity.CENTER_VERTICAL:childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;break;case Gravity.TOP:default:childTop = mPaddingTop;break;}for (int i = 0; i < count; i++) {final View child = getVirtualChildAt(i);if (child == null) {childTop += measureNullChild(i);} else if (child.getVisibility() != GONE) {final int childWidth = child.getMeasuredWidth();final int childHeight = child.getMeasuredHeight();final LinearLayout.LayoutParams lp =(LinearLayout.LayoutParams) child.getLayoutParams();int gravity = lp.gravity;if (gravity < 0) {gravity = minorGravity;}final int layoutDirection = getLayoutDirection();final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {case Gravity.CENTER_HORIZONTAL:childLeft = paddingLeft + ((childSpace - childWidth) / 2)+ lp.leftMargin - lp.rightMargin;break;case Gravity.RIGHT:childLeft = childRight - childWidth - lp.rightMargin;break;case Gravity.LEFT:default:childLeft = paddingLeft + lp.leftMargin;break;}if (hasDividerBeforeChildAt(i)) {childTop += mDividerHeight;}childTop += lp.topMargin;setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);i += getChildrenSkipCount(child, i);}}}

大家可以看到在此方法中遍历所有的子View,并对其进行测量final int childHeight = child.getMeasuredHeight(); 最后有一个childTop变量每次都进行累加

 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

这也正好符合我们LinearLayout的垂直方向的布局。最后调用

setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);
 private void setChildFrame(View child, int left, int top, int width, int height) {        child.layout(left, top, left + width, top + height);}

到这里我们就明白了整个View的onLayout过程,接着会调用子类的layout方法:

public void layout(int l, int t, int r, int b) {if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}int oldL = mLeft;int oldT = mTop;int oldB = mBottom;int oldR = mRight;boolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {onLayout(changed, l, t, r, b);mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;ListenerInfo li = mListenerInfo;if (li != null && li.mOnLayoutChangeListeners != null) {ArrayList<OnLayoutChangeListener> listenersCopy =(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();int numListeners = listenersCopy.size();for (int i = 0; i < numListeners; ++i) {listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);}}}mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;}

主要说一下这段代码

boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

isLayoutModeOptical

Return true if o is a ViewGroup that is laying out using optical bounds.

  public static boolean isLayoutModeOptical(Object o) {return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();}
    boolean isLayoutModeOptical() {return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;}

这句话主要是判断LayoutMode的模式是不是为LAYOUT_MODE_OPTICAL_BOUNDS,如果为真就会运行setOpticalFrame(l, t, r, b)否则就会运行setFrame(l, t, r, b)

首先我们要知道

private int mLayoutMode = LAYOUT_MODE_UNDEFINED;

mLayoutMode的值默认是LAYOUT_MODE_UNDEFINED

也就是说:”mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS”默认是返回false

This constant is a layoutMode. Optical bounds describe where a widget appears to be. They sit inside the clip bounds which need to cover a larger area to allow other effects, such as shadows and glows, to be drawn.

这里有注释,大致意思是说,设置完这个属性之后,在布局的时候,View需要放在一个较大区域的布局内,以便留出来阴影之类位置(在后面代码的部分会有讲解)。

可以通过setLayoutMode方法,手动设置成ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS

另外

setOpticalFrame(l, t, r, b)
setFrame(l, t, r, b);

这两个方法的源码在后面讲解


先看看整个项目代码:

==MainActivity==

public class MainActivity extends AppCompatActivity{private ViewPager mViewPager;private MyFragmentPagerAdapter myFragmentPagerAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);LinearLayout root = (LinearLayout) findViewById(R.id.root);ViewGroup parent = (ViewGroup) root.getParent();parent.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);try {//通过反射,拿到私有的Insets类。Class classz = Class.forName("android.graphics.Insets");/*以下调用带参的、私有构造函数*/Constructor c1=classz.getDeclaredConstructor(new Class[]{int.class,int.class,int.class,int.class});c1.setAccessible(true);//初始化4个int型参数Object o = c1.newInstance(new Object[]{50,50,50,50});View view=parent;Class<? extends View> viewClassz = view.getClass();//得到Insets的setOpticalInsets方法Method setOpticalInsets = viewClassz.getMethod("setOpticalInsets", o.getClass());setOpticalInsets.invoke(view,o);} catch (Exception e) {e.printStackTrace();}}

==布局文件==

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/root"android:elevation="10dp"android:background="#fff"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity"><Button
        android:layout_width="match_parent"android:layout_height="10dp"></LinearLayout>

当isLayoutModeOptical返回true的时候,会运行setOpticalFrame

private boolean setOpticalFrame(int left, int top, int right, int bottom) {Insets parentInsets = mParent instanceof View ?((View) mParent).getOpticalInsets() : Insets.NONE;Insets childInsets = getOpticalInsets();return setFrame(left   + parentInsets.left - childInsets.left,top    + parentInsets.top  - childInsets.top,right  + parentInsets.left + childInsets.right,bottom + parentInsets.top  + childInsets.bottom);}
在这里很容易发现,setOpticalFrame最后还是调用了setFrame方法,只不过对左上右下的值进行了重新的设置。

这里要将一下Insets类

/** Copyright (C) 2006 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package android.graphics;/*** An Insets instance holds four integer offsets which describe changes to the four* edges of a Rectangle. By convention, positive values move edges towards the* centre of the rectangle.* <p>* Insets are immutable so may be treated as values.** @hide*/
public class Insets {public static final Insets NONE = new Insets(0, 0, 0, 0);public final int left;public final int top;public final int right;public final int bottom;private Insets(int left, int top, int right, int bottom) {this.left = left;this.top = top;this.right = right;this.bottom = bottom;}// Factory methods/*** Return an Insets instance with the appropriate values.** @param left the left inset* @param top the top inset* @param right the right inset* @param bottom the bottom inset** @return Insets instance with the appropriate values*/public static Insets of(int left, int top, int right, int bottom) {if (left == 0 && top == 0 && right == 0 && bottom == 0) {return NONE;}return new Insets(left, top, right, bottom);}/*** Return an Insets instance with the appropriate values.** @param r the rectangle from which to take the values** @return an Insets instance with the appropriate values*/public static Insets of(Rect r) {return (r == null) ? NONE : of(r.left, r.top, r.right, r.bottom);}/*** Two Insets instances are equal iff they belong to the same class and their fields are* pairwise equal.** @param o the object to compare this instance with.** @return true iff this object is equal {@code o}*/@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Insets insets = (Insets) o;if (bottom != insets.bottom) return false;if (left != insets.left) return false;if (right != insets.right) return false;if (top != insets.top) return false;return true;}@Overridepublic int hashCode() {int result = left;result = 31 * result + top;result = 31 * result + right;result = 31 * result + bottom;return result;}@Overridepublic String toString() {return "Insets{" +"left=" + left +", top=" + top +", right=" + right +", bottom=" + bottom +'}';}
}

这个类很简单,主要是通单例模式得到Insets

An Insets instance holds four integer offsets which describe changes to the four edges of a Rectangle. By convention, positive values move edges towards the centre of the rectangle.
Insets are immutable so may be treated as values.

通过这段注释大致了解到Insets其实就是封装了四个参数的偏移量,只不过这个类是hide(隐藏),所以上面的代码通过反射得到Insets,之后看效果图,就能明白这个类的大致含义了。

同样在View类中setOpticalInsets也是hide模式的,所以我们要转换成View对象,然后接着用反射,去setOpticalInsets(代码参上)。

接着


protected boolean setFrame(int left, int top, int right, int bottom) {boolean changed = false;if (DBG) {Log.d("View", this + " View.setFrame(" + left + "," + top + ","+ right + "," + bottom + ")");}if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {changed = true;// Remember our drawn bitint drawn = mPrivateFlags & PFLAG_DRAWN;int oldWidth = mRight - mLeft;int oldHeight = mBottom - mTop;int newWidth = right - left;int newHeight = bottom - top;boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);// Invalidate our old positioninvalidate(sizeChanged);mLeft = left;mTop = top;mRight = right;mBottom = bottom;mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);mPrivateFlags |= PFLAG_HAS_BOUNDS;if (sizeChanged) {sizeChange(newWidth, newHeight, oldWidth, oldHeight);}if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {// If we are visible, force the DRAWN bit to on so that// this invalidate will go through (at least to our parent).// This is because someone may have invalidated this view// before this call to setFrame came in, thereby clearing// the DRAWN bit.mPrivateFlags |= PFLAG_DRAWN;invalidate(sizeChanged);// parent display list may need to be recreated based on a change in the bounds// of any childinvalidateParentCaches();}// Reset drawn bit to original value (invalidate turns it off)mPrivateFlags |= drawn;mBackgroundSizeChanged = true;if (mForegroundInfo != null) {mForegroundInfo.mBoundsChanged = true;}notifySubtreeAccessibilityStateChangedIfNeeded();}return changed;}

这时,如果布局改变之后changed为true,还会回调

 sizeChange(newWidth, newHeight, oldWidth, oldHeight);

这里面是一个空实现,我们可以根据逻辑进行添加,最后方法返回changed的值。


紧接着

 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {onLayout(changed, l, t, r, b);mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;ListenerInfo li = mListenerInfo;if (li != null && li.mOnLayoutChangeListeners != null) {ArrayList<OnLayoutChangeListener> listenersCopy =(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();int numListeners = listenersCopy.size();for (int i = 0; i < numListeners; ++i) {listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);}}}

当布局改变changd为true。mPrivateFlags 这个属性之后讲。
会调用

onLayout(changed, l, t, r, b)

方法,主要是用于布局所用,你可以自定义ViewGroup,在这里根据逻辑设置你想要的布局。


最后:

上图:

==可以看到上下左右都空出了些距离,这就是通过Insets的构造函数产生的!==

如果你的View对象添加了

addOnLayoutChangeListener(this);

然后逻辑就很简单了,用过ArrayList把回调的复制一份,然后遍历,最后调用onLayoutChange方法,里面传的当前View和现在的左上右下和之前的左上右下。

@Overridepublic void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {System.out.println("v = " + v);System.out.println("left = " + left);System.out.println("top = " + top);System.out.println("right = " + right);System.out.println("bottom = " + bottom);System.out.println("oldLeft = " + oldLeft);System.out.println("oldTop = " + oldTop);System.out.println("oldRight = " + oldRight);System.out.println("oldBottom = " + oldBottom);System.out.println("\"----------------------------\" = " + "----------------------------");}

自定义View之Layout方法详解相关推荐

  1. 【5年Android从零复盘系列之二十】Android自定义View(15):Matrix详解(图文)【转载】

    [转载]本文转载自麻花儿wt 的文章<android matrix 最全方法详解与进阶(完整篇)> [5年Android从零复盘系列之二十]Android自定义View(15):Matri ...

  2. 精通Android自定义View(四)自定义属性使用详解

    1.简述 对于自定义属性,遵循以下几步,就可以实现: 自定义一个CustomView(extends View )类 编写values/attrs.xml,在其中编写styleable和item等标签 ...

  3. 【5年Android从零复盘系列之六】Android自定义View(1):基础详解(图文)

    1.基础一:坐标计算 1.1 Android窗口坐标系计算以屏幕左上角为原点, 向右为X轴正向,向下为Y轴正向 1.2 View坐标系 [注意获取的坐标是像素值,不是dp值] [注意获取的坐标是像素值 ...

  4. Android 自定义View 之 RectF用法详解

    在之前通过Circle画了一个奥运五环,这次通过RectF来画矩形,常规的就是长方形正方形之类的. 还是新建一个自定义View,CustomViewRectF,然后继承View,实现里面的两个基本的构 ...

  5. 自定义view(二) Path绘画详解 圆形进度条

    目录 简介 基础api 圆形进度条 总结 简介 view的绘制可以由无数个形状组成,在canvas基础图形绘制中,我们已经把api提供好的基本图形讲过了.Path之所以单独一章出来是因为path可以由 ...

  6. android自定义view案例,Android自定义View的实现方法实例详解

    一.自绘控件 下面我们准备来自定义一个计数器View,这个View可以响应用户的点击事件,并自动记录一共点击了多少次.新建一个CounterView继承自View,代码如下所示: 可以看到,首先我们在 ...

  7. View的绘制-layout流程详解

    目录 作用 根据 measure 测量出来的宽高,确定所有 View 的位置. 具体分析 View 本身的位置是通过它的四个点来控制的: 以下涉及到源码的部分都是版本27的,为方便理解观看,代码有所删 ...

  8. object类中的equals与自定义equals方法详解

    object类中的equals与自定义equal方法详解 1.this怎么理解?this == obj表示什么? this就是当前你new出来的对象,这里指谁调用equal方法this指的就是谁,ob ...

  9. php7自定义异常处理,基于PHP7错误处理与异常处理方法(详解)

    PHP7错误处理 PHP 7 改变了大多数错误的报告方式.不同于传统(PHP 5)的错误报告机制,现在大多数错误被作为 Error 异常抛出. 这种 Error 异常可以像 Exception 异常一 ...

  10. android组件用法说明,Android第三方控件PhotoView使用方法详解

    Android第三方控件PhotoView使用方法详解 发布时间:2020-10-21 15:06:09 来源:脚本之家 阅读:74 作者:zhaihaohao1 PhotoView的简介: 这是一个 ...

最新文章

  1. 举个卡戴珊的例子,讲讲Hinton的Capsule是怎么回事 | 教程+代码
  2. 那些关于区块链革命的事情
  3. java实现下载压缩文件_java实现文件压缩下载----压缩下载zip
  4. Spring Cloud Alibaba 新一代微服务解决方案
  5. 【从蛋壳到满天飞】JS 数据结构解析和算法实现-堆和优先队列(一)
  6. 开发转测试没人要_iOS13beta8发布,微信再次测试新功能
  7. 【android】安卓高仿京东分类页
  8. 网络工程师 运维工程师 面试题
  9. 队列总结(六)DelayQueue
  10. rpm的mysql怎么安装_MySQL的rpm安装教程
  11. ora-20011 ora-01555
  12. 2345广告,够了!
  13. 计算机关闭系统默认共享,win10如何关闭默认共享_win10关闭默认共享的图文步骤...
  14. 网站变更服务器要重新备案吗,网站更换服务器要重新备案吗
  15. 《创新者的基因》读书笔记
  16. tp5设置参数全局过滤
  17. 6 如何保障项目按期完工? 人人都是项目经理系列(第6/13篇)
  18. 论文代码复现之:AMR Parsing as Sequence-to-Graph Transduction
  19. 【笔记】IP地址详解、Linux网络及常用命令
  20. 如何在Windows系统搭建Node.js环境

热门文章

  1. 3D 锥形图表echarts
  2. 修复40G的老IDE硬盘
  3. 第1章练习题-SQL基础教程
  4. Python配置OpenCV
  5. 【笔记】QCA9531无线校准
  6. 如何用 Python 翻译语言?
  7. YOLO v2详细解读
  8. mac 学习 java_Mac 新手从零学习JAVA 环境配置篇
  9. win7计算机病毒制作教程,了解病毒的秘密,为win7打造安全盔甲
  10. 区块链--大白话说明