【转】ANDROID自定义视图——onLayout源码 流程 思路详解

转载(http://blog.csdn.net/a396901990

简介:

在自定义view的时候,其实很简单,只需要知道3步骤:

1.测量——onMeasure():决定View的大小

2.布局——onLayout():决定View在ViewGroup中的位置

3.绘制——onDraw():如何绘制这个View。

而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了。

第一步的测量,可以参考我之前的文章:(ANDROID自定义视图——onMeasure流程,MeasureSpec详解)

而这篇文章就来谈谈第二步布局(Layout)

知识点回顾:

在谈如何使用onLayout方法前,先简单回忆一下知识点:

View视图结构:

View视图可以是单一的一个如TextView,也可以是一个视图组(ViewGroup)如LinearLayout。

如图:对于多View的视图他的结构是树形结构,最顶层是ViewGroup,ViewGroup下可能有多个ViewGroup或View。

这个树的概念很重要,因为无论我们是在测量大小或是调整布局的时候都是从树的顶端开始一层一层,一个分支一个分支的进行(树形递归)。

Measure简单回顾:

measure的作用就是为整个View树计算实际的大小,而通过刚才对View树的介绍知道,想计算整个View树的大小,就需要递归的去计算每一个子视图的大小(Layout同理)。

对每一个视图通过onMeasure方法的一系列测量流程后计算出实际的高(mMeasuredHeight)和宽(mMeasureWidth)传入setMeasuredDimension()方法完成单个View的测量,如果所测的视图是ViewGroup则可以通过measureChild方法递归的计算其中的每一个子view。对于每个View的实际宽高都是由父视图本身视图决定的。

Layout(源码分析):

Layout的作用就是为整个View树计算实际的位置,而通过刚才对View树的介绍知道,想计算整个View树的位置,就需要递归的去计算每一个子视图的位置(Measure同理)。

而确定这个位置很简单,只需要mLeft,mTop,mRight,mBottom四个值(注意:这4个值是子View相对于父View的值,下面会详细介绍)。

在代码中如何设置这4个值呢?

首先,无论是系统提供的LinearLayout还是我们自定义的View视图,他都需要继承自ViewGroup类,之后必须要做的就是重写onLayout方法(因为在onLayout在ViewGroup中被定义为抽象方法)。

ViewGroup-onlayout:

1 @Override
2 protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

onLayout被定义为抽象方法,所以在继承ViewGroup时必须要重写该方法(onMeasure不需要)。另外这个方法也被override标注,所以也是重写的方法,他重写的是其父类view中的onLayout方法。

View-onlayout:

    /**
     * 当这个view和其子view被分配一个大小和位置时,被layout调用。
     * @param changed 当前View的大小和位置改变了
     * @param left 左部位置(相对于父视图)
     * @param top 顶部位置(相对于父视图)
     * @param right 右部位置(相对于父视图)
     * @param bottom 底部位置(相对于父视图)
     */
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}  

注解说:当这个view和其子view被分配一个大小和位置时,被layout调用。所以我们去看看layout中做了什么。(注解没有完全按照英文翻译,并且有省略)

View-layout:

 1     /**
 2      * 给View和其所有子View分配大小和位置
 3      *
 4      * 这是布局的第二个阶段(第一个阶段是测量)。在这个阶段中,每个父视图需要去调用layout去为他所有的子视图确定位置
 5      * 派生的子类不应该重写layout方法,应该重写onLayout方法,在onlayout方法中应该去调用每一个view的layout
 6      */
 7     public void layout(int l, int t, int r, int b) {
 8         // 将当前视图的左上右下记录为old值(参数中传入的为新的l,t,r,b值)
 9         int oldL = mLeft;
10         int oldT = mTop;
11         int oldB = mBottom;
12         int oldR = mRight;
13
14         // setFrame方法的作用就是将新传入的ltrb属性赋值给View,然后判断当前View大小和位置是否发生了变化并返回
15         boolean changed = setFrame(l, t, r, b);
16
17         if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
18             // 调用onLayout回调方法,具体实现由重写了onLayout方法的ViewGroup的子类去实现(后面详细说明)
19             onLayout(changed, l, t, r, b);
20             mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
21
22             // 调用所有重写了onLayoutChange监听的方法,通知View大小和位置发生了改变
23             ListenerInfo li = mListenerInfo;
24             if (li != null && li.mOnLayoutChangeListeners != null) {
25                 ArrayList<OnLayoutChangeListener> listenersCopy =
26                         (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
27                 int numListeners = listenersCopy.size();
28                 for (int i = 0; i < numListeners; ++i) {
29                     listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
30                 }
31             }
32         }
33         mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
34     }  

在这段代码中我们只要知道:如果视图的大小和位置发生变化后,会调用我们前面分析过的onLayout方法。

对于onLayout方法的最终实现全部依靠我们在自定义ViewGroup类中重写的onLayout去实现。

计算View位置

在重写的onLayout方法中,唯一的目的就是:

对当前视图和其所有子View设置它们在父视图中具体位置(确定这个位置就依靠mLeft,mTop,mRight,mBottom这四个值)

之前介绍过,mLeft,mTop,mRight,mBottom这四个值表示的是子view相对于父view的位置。下面我贴出我画的图看一下就明白了。

如图,黄色区域是我们的父view,而中间的深色的区域就是我们的子view。

所以对于这个View来说,我列出它相对于父view的各个值是如何计算和相关函数:


mLeft,mTop,mRight,mBottom:

view.getLeft()——mLeft:子View左边界到父view左边界的距离

public final int getLeft() {
    return mLeft;
} 

view.getTop()——mTop:子View上边界到父view上边界的距离

view.getRight()——mRight:子View右边界到父view左边界的距离

view.getBottom()——mBottom:子View下边距到父View上边界的距离

视图宽高:

视图宽度 view.getWidth();子View的右边界 - 子view的左边界。

    public final int getWidth() {
        return mRight - mLeft;
    }  

视图高度 view.getHeight() ;子View的下边界 - 子view的上边界。

    public final int getHeight() {
        return mBottom - mTop;
    }  

测量宽高:

view.getMeasuredWidth();measure过程中返回的mMeasuredWidth

1     public final int getMeasuredWidth() {
2         return mMeasuredWidth & MEASURED_SIZE_MASK;
3     }  

view.getMeasuredHeight();measure过程中返回的mMeasuredHeight

1     public final int getMeasuredHeight() {
2         return mMeasuredHeight & MEASURED_SIZE_MASK;
3     }  

最后介绍一下getWidth/Height和getMeasuredWidth/Height的区别:

getWidth,和getLeft等这些函数都是View相对于其父View的位置。而getMeasuredWidth,getMeasuredHeight是测量后该View的实际值(有点绕,下面摘录一段jafsldkfj所写的Blog中的解释).

实际上在当屏幕可以包裹内容的时候,他们的值是相等的,只有当view超出屏幕后,才能看出他们的区别:

getMeasuredHeight()是实际View的大小,与屏幕无关,而getHeight的大小此时则是屏幕的大小。

当超出屏幕后,getMeasuredHeight()等于getHeight()加上屏幕之外没有显示的大小

在计算子View在父View中的位置时,主要就是应用上面这几个函数。下面就来看看如何去重写onLayout。

onLayout:

对于重写onLayout的思路和重写onMeasure相同:

如果只需要测量单个View,则单独测量它自己就行。如果需要测量的View其下还有子View,则需要测量其所有的子View。

就以上面的View为例子,他最外面是一个黄色的父View,中间一个居中的深色子View。

我的思路如下:

如果想画出一个View,就要计算它的l,t,r,b值。并传递到onlayout( l, t, r, b )中;

mRight = view.getWidth + mLeft;

mBottom = view.getHeight + mTop;

所以最后可以用如下形式传入:onlayout( l, t, l+width, t+height );

剩下的任务就只需要知道它的mLeft值,mTop值,加上长、宽值就行了。

长宽值很简单,使用getWidth/Height和getMeasuredWidth/Height都可以。

由于这个View需要居中显示,剩下的问题就是如何计算该View的mLeft值和mTop值。我的思路如下:

r(父View的mRight) = mLeft + width + mLeft(因为左右间距一样)

b(父View的mBottom) = mTop + height + mTop(因为上下间距一样)

我的代码如下:

 1     @Override
 2     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 3
 4            // 循环所有子View
 5            for (int i=0; i<getChildCount(); i++) {
 6                View child = getChildAt(i);
 7                // 取出当前子View长宽
 8                int width = child.getMeasuredWidth();
 9                int height = child.getMeasuredHeight();
10
11                // 计算当前的mLeft和mTop值(r,b为传递进来的父View的mRight和mBottom值)
12                int mLeft = (r - width) / 2;
13                int mTop = (b - height) / 2;
14
15                // 调用layout并传递计算过的参数为子view布局
16                child.layout(mLeft, mTop, mLeft + width, mTop + height);
17            }
18        }  

布局文件如下:

 1 <com.gxy.text.CostomViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
 2     android:layout_width="wrap_content"
 3     android:layout_height="wrap_content"
 4     android:background="#eee999" >
 5
 6     <Button
 7         android:text="ChildView"
 8         android:layout_width="200dip"
 9         android:layout_height="200dip"
10         android:background="#333444"
11         android:id="@+id/textView2" />
12 </com.gxy.text.CostomViewGroup> 

效果图:

总结:

onMeasure和
onLayout的大致总结完了,在自定义View的时候最关键的是onLayout,因为无论你如何Measure这个View的大小,最后的决定权永
远在onLayout手中,onLayout会决定具体View的大小和位置。当然onMeasure也很重要,有的情况控件的宽高不确定或者需要自定
义,这时候需要我们人工Measure它。而在复杂的自定义View时,很多计算也需要在onMeasure中完成,并且些值会记录下来在
onLayout中重新使用(个人理解,欢迎指正)。

写onMeasure和onLayout的时候只是想自己总结一下,整理一下思路。因为网上有太多写的好了,这里推荐一下qinjuning这位大神的blog,关于View的内容他总结的相当全面和深入。

虽然有很好的了,但我还会坚持自己总结一遍,接下来的计划是写一个稍微复杂点的小例子,将onMeasure和onLayout结合起来(已经写完了,链接如下:ANDROID自定义视图——仿瀑布布局)。之后深入的研究一下View和ViewGroup的源码,总结一下LayoutParams,LayoutInflater等简单常用的知识点,了解一下View的绘制刷新流程等等等。。。太多不会的了,慢慢来吧。

时间: 2024-10-07 05:45:24

【转】ANDROID自定义视图——onLayout源码 流程 思路详解的相关文章

onLayout源码 流程 思路详解(ANDROID自定义视图)

简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量--onMeasure():决定View的大小 2.布局--onLayout():决定View在ViewGroup中的位置 3.绘制--onDraw():如何绘制这个View. 而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 第一步的测量,可以参考我之前的文章:(ANDROID自定义视图--onMeasure流程,MeasureSpec详解) 而这篇文章就来谈谈第二步:"

【转】ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解

原文地址:http://blog.csdn.net/a396901990/article/details/36475213 简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量——onMeasure():决定View的大小 2.布局——onLayout():决定View在ViewGroup中的位置 3.绘制——onDraw():如何绘制这个View. 而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 而这篇文章就来谈谈第一步

Android自定义视图 View 之重要知识点 Path 详解

惯例:先上图后说话,谢谢各位伙伴的支持! 有你们是我的福分! 欢迎一起讨论和学习,QQ:732258496  微信:15520726587 每日一言:梦想一定要有万一实现了呢! Path常用方法 方法 作用 备注 moveTo 移动起点 移动下一次操作的起点位置 lineTo 连接直线 连接上一个点到当前点之间的直线 setLastPoint 设置终点 重置最后一个点的位置 close 闭合路劲 从最后一个点连接最初的一个点,形成一个闭合区域 addRect 添加矩形 添加矩形到当前Path a

Spring Boot源码中模块详解

Spring Boot源码中模块详解 一.源码 spring boot2.1版本源码地址:https://github.com/spring-projects/spring-boot/tree/2.1.x 二.模块 Spring Boot 包含许多模块,以下是一些简单的概述: 1,spring-boot 为Spring Boot其他部分功能提供主要的lib包,其中包含:(1)SpringApplication类提供了静态便利的方法使编写独立的SpringApplication更加容易.它唯一的任

linux上源码安装MySQL详解

最近需要使用MySQL Fabric,这货是MySQL5.6.10之后才出现的utility.手头机器装的是MySQL5.1,所以需要先把旧版MySQL升级成5.6版本.之前没有玩过MySQL,所以这次稍微费了点事.在此,把过程记录下来,希望能给有需求的人提供一点帮助.下面我们就正式开始. 1. 删除老版本MySQL 其实删除老版MySQL是一件很简单的事,但是开始时候由于担心各个包的依赖会导致各种问题,亦步亦趋来得很慢.其实只需要做到这么几步就可以了: 1.1 查看已安装的mysql版本并删除

机器学习Spark Mllib算法源码及实战详解进阶与提高视频教程

38套大数据,云计算,架构,数据分析师,Hadoop,Spark,Storm,Kafka,人工智能,机器学习,深度学习,项目实战视频教程 视频课程包含: 38套大数据和人工智能精品高级课包含:大数据,云计算,架构,数据挖掘实战,实时推荐系统实战,电视收视率项目实战,实时流统计项目实战,离线电商分析项目实战,Spark大型项目实战用户分析,智能客户系统项目实战,Linux基础,Hadoop,Spark,Storm,Docker,Mapreduce,Kafka,Flume,OpenStack,Hiv

Android 自定义相机Demo源码

Github源码:https://github.com/LinJZong/AndroidProject.git 模仿360相机,图片资源来源于360相机,仅供学习使用.截图如下: 目前完成了拍照.保存.图片压缩.触摸聚焦.拍照成功附带动画效果.闪光灯切换.手势缩放等功能,功能持续更新中. 最近更新的较频繁,就不放csdn了,等功能全做完了再传csdn.介绍下目前主要几个功能类凑够200字. public class CameraView extends SurfaceView implement

Android源码目录结构详解

Android |– Makefile |– bionic (bionic C库) |– bootable (启动引导相关代码) |– build (存放系统编译规则及generic等基础开发包配置) |– cts (Android兼容性测试套件标准) |– dalvik (dalvik JAVA虚拟机) |– development (应用程序开发相关) |– external (android使用的一些开源的模组) |– frameworks (核心框架--java及C++语言) |– ha

12.Android源码目录结构详解 (转)

转载:http://blog.csdn.net/google_huchun/article/details/59576654 Android 2.1 |– Makefile |– bionic (bionic C库) |– bootable (启动引导相关代码) |– build (存放系统编译规则及generic等基础开发包配置) |– cts (Android兼容性测试套件标准) |– dalvik (dalvik Java虚拟机) |– development (应用程序开发相关) |–