自定义View系列(三)View的绘制分析
来源:互联网 发布:stc15w201s数据手册 编辑:程序博客网 时间:2024/06/05 04:16
都知道View的绘制流程是onMeasure -> onLayout -> onDraw了。
这里主要是从源代码分析onMeasure的过程,解决自定义View wrap_content不起作用的问题。
一、onMeasure
自定义View如果不重写onMeasure的话,layoutWidth/height 设置wrap_content就不会生效。
要说明这个问题的原因,首先有必要先说明一下MeasureSpec的3种模式UNSPECIFIED 、EXACTLY、AT_MOST。
<span style="white-space:pre"></span>/** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * Measure specification mode: The child can be as large as it wants up * to the specified size. */ public static final int AT_MOST = 2 << MODE_SHIFT;
具体值可以无视,反正就是用MeasureSpec的某几位来表示模式,用&运算来区分。
只要只要它们的对应情况就可以了,看注释:
UNSPECIFIED : 任意模式,可以是任意值。(貌似不会用到)
EXACTLY:精确模式, 比如 layout_width = "10dp" 。
还有种情况 layout_width = "match_parent"
AT_MOST: 最大值模式,即 wrap_content
从下面源码中可以得到验证:默认情况是EXACTLY。WRAP_CONTENT对应AT_MOST,MATCH_PARENT对应EXACTLY。
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
之所以自定义View不实现onMeasure会导致wrap_content不生效,
是因为在View的默认onMeasure实现中,AT_MOST会被当成EXACTLY来处理。
还是看源代码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }默认的测量方法中并没有对AT_MOST进行特殊处理,其处理和EXACTLY是一样的。
当xml中设置layout_width / layou_height为wrap_content时,是不起作用的。
对症下药,手动实现的办法就是对AT_MOST也做一套测量方案。
可以套用下面的模板:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getMyDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getMyDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } private int getMyDefaultSize(int size, int measureSpec) { int result = 200;//可以自己设置 int specMode = View.MeasureSpec.getMode(measureSpec); int specSize = View.MeasureSpec.getSize(measureSpec); switch (specMode) { case View.MeasureSpec.UNSPECIFIED: result = size; break; case View.MeasureSpec.AT_MOST: break; case View.MeasureSpec.EXACTLY: result = Math.min(result, specSize); break; } return result; }
其中result = 200 可以根据你想给自己的View设置的长/宽来确定。设置一个大概的值就可以了。
当然也可以不用wrap_content直接设置dp值。
二、onLayout
这个基本可以不管的
三、onDraw
这个就真的很神奇了, 一般不用去重写。但是见过一些通过重写onDraw来实现很炫的效果的View的。
想玩转onDraw,有必要熟练掌握Canvas类。
一些动画的自定义View。通过反复调用onDraw来达到自己的动画效果。
比如这个项目的阴影和动画就是在onDraw中实现的,有兴趣的朋友可以看一下:
https://github.com/dolphinwang/ImageCoverFlow
先做为一块空缺吧。
最后提一下手动绘制View的方式。
第一种, invalidate,只能在UI 线程中调用。
** * Invalidate the whole view. If the view is visible, * {@link #onDraw(android.graphics.Canvas)} will be called at some point in * the future. * <p> * This must be called from a UI thread. To call from a non-UI thread, call * {@link #postInvalidate()}. */ public void invalidate() { invalidate(true); }
第二种,postInvalidate,可以在非UI线程中调用。
其内部是通过Handler来切换到UI线程中进行View绘制的。
/** * <p>Cause an invalidate to happen on a subsequent cycle through the event loop. * Use this to invalidate the View from a non-UI thread.</p> * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * * @see #invalidate() * @see #postInvalidateDelayed(long) */ public void postInvalidate() { postInvalidateDelayed(0); }
0 0
- 自定义View系列(三)View的绘制分析
- 自定义view的绘制
- 自定义view 的绘制
- Android绘图系列(三)——自定义View绘制仪表盘
- google 开发者自定义view的系列(创建view,绘制,交互,view的优化)
- Android 自定义View之View的绘制
- Android 自定义View基础-View的绘制
- 自定义view:view的绘制流程
- 自定义view--虚线的绘制
- view类的自定义绘制
- Android自定义View的绘制
- 自定义view的绘制流程
- android--自定义view的绘制
- 实现自定义View的绘制
- 自定义VIEW(学习笔记三)-基本图形的绘制
- 自定义View(三)的常用方法(测量、绘制、位置)
- 自定义View(三)---分析JazzViewPager
- 自定义控件View(三)___绘制饼状图
- linux 下配置eclipse的启动图标
- 第二十四讲项目4-个人所得税计算器if语句版
- 文字排版中的设计四原则(二)
- 如何在Beyond Compare合并文本时设置对齐方式
- crontab定时任务
- 自定义View系列(三)View的绘制分析
- 第十三讲 ionic css布局介绍
- 解放程序猿(媛)的双手—iOS UI自动化测试
- 安卓 自己动手实现守望先锋动画
- 使用Red Hat Linux遇到的问题
- Linux常见命令关机与重启命令
- 键盘按键对应的ASCII码值
- Java学习笔记----打印基本数据类型范围
- form表单序列化提交处理当中的时间类型