对View绘制流程的一些理解
2016-01-24 13:32
441 查看
这里只是记录一下个人对View绘制这一块的理解,写的会比较粗糙,如果想要详细了解,请参考如下博客:
/article/1503761.html
在view的绘制过程中,一般会分如下三个过程:
measure() —— 测量view的大小
layout() —— 计算view在父view中的位置
draw() —— 绘制view
measure方法总调用了onMeasure方法,layout方法中调用了onLayout方法,draw中调用了onDraw方法,一般我们自定义view只需要实现这三个方法就可以。(measure在view中是final类型,不能被子类覆盖)
首先,我们来看onMeasure方法:
在此方法中,最重要的就是调用setMeasuredDimension方法给变量mMeasuredWidth和mMeasuredHeight赋值。onMeasure方法在参数widthMeasureSpec和heightMeasureSpec是父view的测量规格,其中包括父view的size和mode,一般我们会根据父view的测量规格来计算子view的大小。平时我们使用getMeasureWidth()和getMeasureHeight()方法就是获得mMeasuredWidth和mMeasuredHeight的值。上边的代码是View类中onMeasure的默认实现,自定义view时,我们需要根据自己的需求去测量子View的大小。如果是自定义ViewGroup,则还需调用measureChild方法去测量子View的大小。
再来看onLayout方法:
View类中:
ViewGroup类中:
可以看到,在View中onLayout是一个空方法,而在ViewGroup中则是一个虚方法,也就是说自定义的ViewGroup必须实现此方法。那么onLayout的作用到底是什么呢?
其实onLayout就是计算mLeft,mTop,mRight,mBottom这四个变量的值,这四个变量就决定了view在父View中的位置。这四个变量是相对父View左上点的位置(不理解的请参考:/article/3604782.html)。其中changed表示view的位置是否变化了,而l, t, r, b分别是父View的left, top, right, bottom, 我们就是根据父View的这几个变量来计算子view的位置。
onDraw方法:
这个方法一般如果没有什么特殊需求的话,不需要重写,使用默认的就可以,因为通过onMeasure和onLayout方法已经确定了view的大小和位置,所以onDraw中只需按照参数绘制即可。
下面我们自己自定义一个ViewGroup:
在onMeasure方法中,我们首先调用super.onMeasure()方法去设置MyViewGroup的mMeasuredWidth和mMeasuredHeight的值,然后循环调用measureChild方法去测量子View的大小。
在onLayout方法中,我们首先判断changed是否为true,然后让子View以垂直的方法排列显示,实际上就是计算每个子View的mTop的值。当然,我们也可以任意的设置子View的排列方式。
在XML文件中使用MyViewGroup:
效果图如下:
灰色的背景为MyViewGroup区域,绿色为子View1,蓝色为子View2,我们还可以看到四周的一些白色背景,这个是MyViewGroup的父View。
如果细心,我们可以发现一个问题,就是在XML文件中,MyViewGroup的layout_height属性设置的是wrap_content, 但是我们实际看到的效果并不是适应内容,而好像是match_parent的效果,这是为什么?
还记得我们在onMeasure中调用super.onMeasure方法去设置MyViewGroup的mMeasuredWidth和mMeasuredHeight的值吗?我们说过,onMeasure中的两个参数实际上是父View的测量规格,这个规格中包括了父View的size和mode,在onMeasure中我们并没有根据子View的高度去计算MyViewGroup的高度,而是直接将父的大小设置给MyViewGroup,所以MyViewGroup的layout_width和layout_height属性不管你设置match_parent还是wrap_content,都会之父View的大小。
接下来,我们就去更改onMeasure方法,使其可以自适应View的高度
我们在测量完子View的大小后,然后根据每个子View测量的高度去计算MyViewGroup的 高度,最后调用setMeasureDimension方法用我计算出来的高度去设置mMeasuredHeight的值。再看效果图:
可以看到,这次MyViewGroup的高度和子View的高度一样。可能有人还问,上下不是还能看到一部分灰色背景吗?这个是因为我们设置了paddingTop和paddingBottom的值,而在计算高度时,我们将这两个值也加了进去。
/article/1503761.html
在view的绘制过程中,一般会分如下三个过程:
measure() —— 测量view的大小
layout() —— 计算view在父view中的位置
draw() —— 绘制view
measure方法总调用了onMeasure方法,layout方法中调用了onLayout方法,draw中调用了onDraw方法,一般我们自定义view只需要实现这三个方法就可以。(measure在view中是final类型,不能被子类覆盖)
首先,我们来看onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
在此方法中,最重要的就是调用setMeasuredDimension方法给变量mMeasuredWidth和mMeasuredHeight赋值。onMeasure方法在参数widthMeasureSpec和heightMeasureSpec是父view的测量规格,其中包括父view的size和mode,一般我们会根据父view的测量规格来计算子view的大小。平时我们使用getMeasureWidth()和getMeasureHeight()方法就是获得mMeasuredWidth和mMeasuredHeight的值。上边的代码是View类中onMeasure的默认实现,自定义view时,我们需要根据自己的需求去测量子View的大小。如果是自定义ViewGroup,则还需调用measureChild方法去测量子View的大小。
再来看onLayout方法:
View类中:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
ViewGroup类中:
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
可以看到,在View中onLayout是一个空方法,而在ViewGroup中则是一个虚方法,也就是说自定义的ViewGroup必须实现此方法。那么onLayout的作用到底是什么呢?
其实onLayout就是计算mLeft,mTop,mRight,mBottom这四个变量的值,这四个变量就决定了view在父View中的位置。这四个变量是相对父View左上点的位置(不理解的请参考:/article/3604782.html)。其中changed表示view的位置是否变化了,而l, t, r, b分别是父View的left, top, right, bottom, 我们就是根据父View的这几个变量来计算子view的位置。
onDraw方法:
这个方法一般如果没有什么特殊需求的话,不需要重写,使用默认的就可以,因为通过onMeasure和onLayout方法已经确定了view的大小和位置,所以onDraw中只需按照参数绘制即可。
下面我们自己自定义一个ViewGroup:
package com.example.testmeasure; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } public MyViewGroup(Context context) { super(context); // TODO Auto-generated constructor stub } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (changed) { final int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); // 让子View垂直排列 int height = getPaddingTop(); if (i > 0) { for (int j = 0; j <= i - 1; j++) { height += getChildAt(j).getMeasuredHeight(); } } child.layout(getPaddingLeft(), height, child.getMeasuredWidth() + getPaddingLeft(), child.getMeasuredHeight() + height); } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int mode = MeasureSpec.getMode(heightMeasureSpec); int size = MeasureSpec.getSize(heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
在onMeasure方法中,我们首先调用super.onMeasure()方法去设置MyViewGroup的mMeasuredWidth和mMeasuredHeight的值,然后循环调用measureChild方法去测量子View的大小。
在onLayout方法中,我们首先判断changed是否为true,然后让子View以垂直的方法排列显示,实际上就是计算每个子View的mTop的值。当然,我们也可以任意的设置子View的排列方式。
在XML文件中使用MyViewGroup:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/id_root" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="5dp" > <com.example.testmeasure.MyViewGroup android:id="@+id/id_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/darker_gray" android:paddingBottom="5dp" android:paddingLeft="5dp" android:paddingRight="10dp" android:paddingTop="5dp" > <TextView android:id="@+id/id_text" android:layout_width="100dp" android:layout_height="40dp" android:background="@android:color/holo_green_dark" android:text="@string/hello_world" /> <TextView android:id="@+id/id_text2" android:layout_width="100dp" android:layout_height="40dp" android:background="@android:color/holo_blue_light" android:text="你好,世界!" /> </com.example.testmeasure.MyViewGroup> </RelativeLayout>
效果图如下:
灰色的背景为MyViewGroup区域,绿色为子View1,蓝色为子View2,我们还可以看到四周的一些白色背景,这个是MyViewGroup的父View。
如果细心,我们可以发现一个问题,就是在XML文件中,MyViewGroup的layout_height属性设置的是wrap_content, 但是我们实际看到的效果并不是适应内容,而好像是match_parent的效果,这是为什么?
还记得我们在onMeasure中调用super.onMeasure方法去设置MyViewGroup的mMeasuredWidth和mMeasuredHeight的值吗?我们说过,onMeasure中的两个参数实际上是父View的测量规格,这个规格中包括了父View的size和mode,在onMeasure中我们并没有根据子View的高度去计算MyViewGroup的高度,而是直接将父的大小设置给MyViewGroup,所以MyViewGroup的layout_width和layout_height属性不管你设置match_parent还是wrap_content,都会之父View的大小。
接下来,我们就去更改onMeasure方法,使其可以自适应View的高度
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int mode = MeasureSpec.getMode(heightMeasureSpec); int size = MeasureSpec.getSize(heightMeasureSpec); int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); } LayoutParams lp = getLayoutParams(); // 让父的height适应子view的高度(就是让wrap_content起作用) int height = 0; if (lp.height == LayoutParams.MATCH_PARENT){ height = size; }else if (lp.height == LayoutParams.WRAP_CONTENT){ for (int i = 0; i < count; i++) { final View child = getChildAt(i); height += child.getMeasuredHeight(); } height += getPaddingTop(); height += getPaddingBottom(); }else { height = lp.height; } setMeasuredDimension(widthMeasureSpec, height); }
我们在测量完子View的大小后,然后根据每个子View测量的高度去计算MyViewGroup的 高度,最后调用setMeasureDimension方法用我计算出来的高度去设置mMeasuredHeight的值。再看效果图:
可以看到,这次MyViewGroup的高度和子View的高度一样。可能有人还问,上下不是还能看到一部分灰色背景吗?这个是因为我们设置了paddingTop和paddingBottom的值,而在计算高度时,我们将这两个值也加了进去。
相关文章推荐
- jquery animate扩展
- 刷过一题之NOIP2013转圈游戏
- Data Binding的报错集合: 例如Error:(10, 54) 错误: 程序包com.kodulf.recyclerviewdatabinding.databinding不存在
- python序列通用操作符
- Java 进阶——单例模式
- CSS实现文字随屏幕变化
- ubuntu(linux)下source、sh/bash、./ 和 直接 执行脚本的区别
- BZOJ1077 天平
- 刷过一题之NOIP2013表达式求值
- 2015年第六届蓝桥杯C/C++程序设计本科B组省赛 九数组分数(代码填空)
- 告诉你自己一定要努力
- Android消息循环分析
- [总结]PHP字符串处理
- C++:泛型编程vector(成绩排序)
- Qualcomm Atheros Device [168c:0041] (rev 20) ubuntu wifi driver
- Android 5.1 以太网服务启动过程
- 刷过一题之NOIP2013花匠
- bzoj2141 排队
- poj1502 MPI Maelstrom(最短路)
- Listview焦点问题