转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持!

简单介绍:

在自己定义view的时候。事实上非常easy。仅仅须要知道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同理)。

而确定这个位置非常easy,仅仅须要mLeft,mTop,mRight,mBottom四个值(注意:这4个值是子View相对于父View的值。以下会具体介绍)。

在代码中怎样设置这4个值呢?

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

ViewGroup-onlayout:

@Override
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:

    /**
* 给View和其全部子View分配大小和位置
*
* 这是布局的第二个阶段(第一个阶段是測量)。在这个阶段中。每个父视图须要去调用layout去为他全部的子视图确定位置
* 派生的子类不应该重写layout方法,应该重写onLayout方法,在onlayout方法中应该去调用每个view的layout
*/
public void layout(int l, int t, int r, int b) {
// 将当前视图的左上右下记录为old值(參数中传入的为新的l,t,r,b值)
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight; // setFrame方法的作用就是将新传入的ltrb属性赋值给View。然后推断当前View大小和位置是否发生了变化并返回
boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 调用onLayout回调方法。具体实现由重写了onLayout方法的ViewGroup的子类去实现(后面具体说明)
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; // 调用全部重写了onLayoutChange监听的方法。通知View大小和位置发生了改变
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;
}

在这段代码中我们仅仅要知道:假设视图的大小和位置发生变化后。会调用我们前面分析过的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

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

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

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

最后介绍一下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值。加上长、宽值即可了。

长宽值非常easy,使用getWidth/Height和getMeasuredWidth/Height都能够。

因为这个View须要居中显示。剩下的问题就是怎样计算该View的mLeft值和mTop值。我的思路例如以下:

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

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

我的代码例如以下:

	@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { // 循环全部子View
for (int i=0; i<getChildCount(); i++) {
View child = getChildAt(i);
// 取出当前子View长宽
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight(); // 计算当前的mLeft和mTop值(r,b为传递进来的父View的mRight和mBottom值)
int mLeft = (r - width) / 2;
int mTop = (b - height) / 2; // 调用layout并传递计算过的參数为子view布局
child.layout(mLeft, mTop, mLeft + width, mTop + height);
}
}

布局文件例如以下:

<com.gxy.text.CostomViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#eee999" > <Button
android:text="ChildView"
android:layout_width="200dip"
android:layout_height="200dip"
android:background="#333444"
android:id="@+id/textView2" />
</com.gxy.text.CostomViewGroup>

效果图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYTM5NjkwMTk5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="400" height="600" alt="">

总结:

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的绘制刷新流程等等等。

。。

太多不会的了,慢慢来吧。

ANDROID自己定义视图——onLayout源代码 流程 思路具体解释的更多相关文章

  1. Android自己定义视图(一):带下划线的TextView

    package com.francis.underlinetextviewtest; import android.content.Context; import android.content.re ...

  2. ANDROID定义自己的看法——onMeasure,MeasureSpec源代码 过程 思考具体解释

    一个简短的引论: 在他们的定义view什么时候,其实很easy,只需要知道3: 1.測量--onMeasure():决定View的大小 2.布局--onLayout():决定View在ViewGrou ...

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

    原文地址:http://blog.csdn.net/a396901990/article/details/36475213 简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量—— ...

  4. ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解

    简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量--onMeasure():决定View的大小 2.布局--onLayout():决定View在ViewGroup中的位置 3. ...

  5. android自己定义控件系列教程----视图

    理解android视图 对于android设备我们所示区域事实上和它在底层的绘制有着非常大的关系,非常多时候我们都仅仅关心我们所示,那么在底层一点它究竟是怎么样的一个东西呢?让我们先来看看这个图. w ...

  6. android 自己定义ViewGroup实现可记载并呈现选择的ListView

    转载请注明出处:王亟亟的大牛之路 之前也做过一些用TextView之类的记录ListView选项的东西.可是总认为好难看.发现个不错的实现就贴给大家. 项目文件夹 执行效果: 自己定义视图: @Tar ...

  7. Android自己定义控件:进度条的四种实现方式

    前三种实现方式代码出自: http://stormzhang.com/openandroid/2013/11/15/android-custom-loading/ (源代码下载)http://down ...

  8. Android自己定义控件系列五:自己定义绚丽水波纹效果

    尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自己定义控件实现一个比較有趣的效果 ...

  9. Android 4.4 Kitkat Phone工作流程浅析(八)__Phone状态分析

    本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文代码以MTK平台Android 4.4为分析对象.与Google原生AOSP有些许差异.请读者知悉. ...

随机推荐

  1. 解决Oracle 11gR2 空闲连接过多,导致连接数满的问题

    今天又遇到了11gR2连接数满的问题,以前也遇到过,因为应用那边没有深入检查,没有找到具体原因,暂且认为是这个版本Oracle的BUG吧. 上次的处理办法是用Shell脚本定时在系统中kill  v$ ...

  2. J2SE知识点摘记(二十四)

     覆写hashCode() 在明白了HashMap具有哪些功能,以及实现原理后,了解如何写一个hashCode()方法就更有意义了.当然,在HashMap中存取一个键值对涉及到的另外一个方法为equa ...

  3. linux关闭防火墙方法

    在关闭防火墙之前需要查看防火墙的状态,可以使用service iptables status命令来查看,确定防火墙是否开启再来进行关闭操作. 如果想临时开启防火墙使用命令service iptable ...

  4. Get Intellisense for .axml files in Visual Studio

    原文Get Intellisense for .axml files in Visual Studio So in order to get some intellisense support for ...

  5. mongoose 查询子文档的方法

    { "__v": 1, "_id": "538f5f0f6195a184108c8bd8", "title": &quo ...

  6. Swift初体验(三)

    /*******************************************************************************/ // 协议 protocol Des ...

  7. 高效的SQLSERVER分页查询(推荐)

    Sqlserver数据库分页查询一直是Sqlserver的短板,闲来无事,想出几种方法,假设有表ARTICLE,字段ID.YEAR...(其他省略),数据53210条(客户真实数据,量不大),分页查询 ...

  8. SQL练习之不破坏应用程序现有查询的修改模式

    当我还是一个菜鸟的时候,当然现在也是,当我的软件需求发生变化时,并且数据库设计同样要求发生变化,我通常会放弃原有的代码(或者对原有的代码进行大改),先在我知道了两个不破坏应用程序现有查询的修改模式,下 ...

  9. vs vsvim viemu vax 备忘

    使用gt和gT往返标签 gd:到达光标所在处函数或者变量的定义处. *:读取光标处的字符串,并且移动光标到它再次出现的地方. #:和上面的类似,但是是往反方向寻找. /text:从当前光标处开始搜索字 ...

  10. Javascript获取浏览器版本

    前两天有朋友请教我这个问题,说现在网上的资料太过于混乱不完整,我这里先简单整理一部分. 0 (function(){ 1   2 var ver = window.navigator.userAgen ...