android自定义View的绘制原理
每天我们都会使用很多的应用程序,尽管他们有不同的约定,但大多数应用的设计是非常相似的。这就是为什么许多客户要求使用一些其他应用程序没有的设计,使得应用程序显得独特和不同。
如果功能布局要求非常定制化,已经不能由Android内置的View创建 —这时候就需要使用自定义View了。而这意味着在大多数情况下,我们将需要相当长的时间来完成它。但这并不意味着我们不应该这样做,因为实现它是非常令人兴奋和有趣的。
我最近面临了类似的情况:我的任务是使用ViewPager实现Android应用引导页。不同于iOS,Android并没有提供这样的View,所以我不得不编写一个自定义View来实现它。
我花了一些时间来实现它。幸运的是,时下很多开源项目都有类似可复用的View,这节省了我和其他开发者的时间。我决定基于这种View创建一个公共库。如果你有类似的功能需求并且缺乏时间实现它,可以在github repo发现它。
Sample of using PageIndicatorView
绘制!
因为编写自定义View比起普通的View更耗时,你应该只在为了实现特定的功能但没有更简单的方法情况下使用自定义View,或者你希望通过自定义View解决以下问题:
性能。如果你布局里面有很多View,你想通自定义View优化它,使其更轻量。
视图层次结构复杂。
一个完全自定义的View,需要手动绘制才能实现。
如果你还没有尝试过编写自定义View,这篇文章将教会你绘制扁平的自定义View的一些技巧。我将会告诉你整体的视图结构,如何实现具体的功能,不要重犯常见的错误,以及实现动画效果!
我们需要知道的第一件事 –View的生命周期。不知出于某种原因,谷歌并没有提供View生命周期的图表,由于开发者普遍对其有误解,导致了一些意想不到的错误和问题,所以我们要认清这过程。
构造函数
每个View的生命都是从构造函数开始。而且这是一个绘制初始化,进行各种计算,设定默认值或做任何我们需要的事情很好的地方。
但是,为了使我们的View更易于使用和配置,Android提供了很有用的AttributeSet接口。它很容易实现,而且绝对值得花时间去了解和实现它,因为它会帮助你(和你的团队)通过静态参数来设置View,对于以后新特性加入或者新屏幕拓展性支持也更好。
首先,创建一个新的文件attrs.xml。所有不同的自定义View属性都可以放在该文件中。正如你看到的这个例子,我们有一个PageIndicatorView和它的唯一属性piv_count。
Custom Attributes sample
紧接着在View的构造函数中,你需要获取这个属性并使用它,如下图所示。
public PageIndicatorView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.PageIndicatorView);
int count = typedArray.getInt(R.styleable.PageIndicatorView_piv_count,0);
typedArray.recycle();
}
注意:
在创建自定义属性使用一个简单的前缀,以避免与其它View类似的属性名称冲突。一般我们使用View名称缩写,就像例子中的piv_。
如果你使用的是Android Studio,一旦你使用完属性,lint会建议你调用recycle()方法 。The reason is just to get rid of inefficiently bound data that’s not gonna be used again。[译者注:翻译有点拗口,其实就是回收TypedArray,以便后面重用]
onAttachedToWindow
父View调用addView(View)后,这个View将被依附到一个窗口。在这个阶段,我们的View会知道它被包围的其他view。如果你的View和其他View在相同的layout.xml,这是通过id找到他们的好地方(你可以通过属性进行设置),同时可以保存为全局(如果需要)的引用。
onMeasure
这意味着我们的自定义View到了处理自己的大小的时候。这是非常重要的方法,因为在大多数情况下,你的View需要有特定的大小以适应你的布局。
当你重写此方法,需要记得的是,最终要设置setMeasuredDimension(int width, int height) 。
onMeasure
当处理自定义View的大小时候,使用者可能通过layout.xml或者动态设置了具体的大小。要正确地计算它,我们需要做几件事情。
1.计算你的View内容所需的大小(宽度和高度)。
2.获取你的View MeasureSpec大小和模式(宽度和高度)。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
}
3.检查MeasureSpec 设置和调整View(宽度和高度)的尺寸模式。
int width;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(desiredWidth, widthSize);
} else {
width = desiredWidth;
}
注意:
看看MeasureSpec的值:
MeasureSpec.EXACTLY 意味着硬编码大小值,所以你应该设置指定的宽度或高度。
MeasureSpec.AT_MOST 用于表明你的View匹配父View的大小,
所以它应该和他想要的大小一样大。
[译者注:此时View尺寸只要不超过父View允许的最大尺寸即可]
MeasureSpec.UNSPECIFIED 实际上是视图包装尺寸。因此,你可以使用上面计算所需的大小。
在通过setMeasuredDimension设置最终值之前,以防万一,可以检查这些值不为负数。这可以避免在布局预览时一些问题。
onLayout
此方法分配大小和位置给它的每一个子View。正因为如此,我们正在研究一个扁平的自定义视图(继承简单的View)不具有任何子View,那么就没有理由重写此方法。[译者注:实现可以参考Custom Layouts on Android]
onDraw
这就是发生魔法的地方。在这里,使用Canvas和Paint对象你将可以画任何你需要的东西。
一个Canvas实例从onDraw参数得来,它一般用于绘制不同形状,而Paint对象定义形状颜色。简单地说,Canvas用于绘制对象,而Paint用于造型。它们无处不在,无论绘制的是一个直线,圆或长方形。
onDraw() — methods example
使自定义View,要始终牢记onDraw会花费大量的时间。当布局有一些变化,滚动、快速滑动都会导致重新绘制。所以这就是为什么Android Studio也建议:避免在onDraw中进行对象分配的操作,对象应该只创建一次并在将来重用。
onDraw() — Paint object recreation
onDraw() — Paint object reuse
注意:
在执行绘制时始终牢记重用对象,而不创建新的。不要依赖于IDE高亮一个潜在的问题,而是自己有意识地去做这件事,因为在onDraw调用一个内部会创建对象的方法时,IDE无法识别它。
同时请不要硬编码View大小。其他开发者在使用时可以定义不同的大小,所以View大小应该取决于它有什么尺寸。
View 更新
从View的生命周期图可以得知,可以重绘View自身有两种方法。invalidate()和requestLayout()方法会帮助你在运行时动态改变View状态。但为什么需要两个方法?
invalidate()用来简单重绘View。例如更新其文本,色彩或触摸交互性。View将只调用onDraw()方法再次更新其状态。
requestLayout()方法,你可以看到其将会从`onMeasure()开始更新View。这意味着你的View更新后,它改变它的大小,你需要再次测量它,并依赖于新的大小来重新绘制。
动画
在自定义View中,动画是一帧一帧的过程。这意味着,如果你想使一个圆半径从小变大,你将需要逐步增加半径并调用invalidate()来重绘它。
在自定义View动画中,ValueAnimator是你的好朋友。下面这个类将帮助你从任何值开始执行动画到最后,甚至支持Interpolator(如果需要)。
ValueAnimator animator = ValueAnimator.ofInt(0, 100);
animator.setDuration(1000);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
int newRadius = (int) animation.getAnimatedValue();
}
});
注意:
当每一次新的动画值出来时,不要忘记调用invalidate()。
android自定义View的绘制原理的更多相关文章
- Android自定义View之绘制虚线
现在实现一个效果,有个虚线分割和阴影效果.一个一个实现. 分为2中方式. 1.设计出图,我们SRC引入进来(最简单,但是需要其他资源支持). 2.code实现,有些难度,需要查资料. 现在把第2种方式 ...
- 【朝花夕拾】Android自定义View篇之(一)View绘制流程
前言 转载请申明转自[https://www.cnblogs.com/andy-songwei/p/10955062.html]谢谢! 自定义View.多线程.网络,被认为是Android开发者必须牢 ...
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
Android中View的绘制过程 onMeasure方法简述 附有自定义View例子 Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android fr ...
- 【转】Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布 ...
- Android 自定义 View 绘制
在 Android 自定义View 里面,介绍了自定义的View的基本概念.同时在 Android 控件架构及View.ViewGroup的测量 里面介绍了 Android 的坐标系 View.Vie ...
- android自定义View绘制天气温度曲线
原文:android自定义View绘制天气温度曲线 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u012942410/article/detail ...
- Android 自定义View合集
自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...
- Android自定义View
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入门程序猿来说对于Android自定义View ...
- android 自定义view 前的基础知识
本篇文章是自己自学自定义view前的准备,具体参考资料来自 Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了 ...
随机推荐
- Linux三剑客之awk最佳实践
笔者Q:972581034 交流群:605799367.有任何疑问可与笔者或加群交流 知识点: 记录与字段 模式匹配:模式与动作 基本的awk执行过程 awk常用内置变量(预定义变量) awk数组 a ...
- LINUX下SYN攻防战 [转]
LINUX下SYN攻防战 (一)SYN攻击原理SYN攻击属于DOS攻击的一种,它利用TCP协议缺陷,通过发送大量的半连接请求,耗费服务器CPU和内存资源.SYN攻击聊了能影响主机外,还可 ...
- VUE-node.js
1.什么是node.js 它是可以运行javascript的服务平台 可以把它当做一门后端程序,只是它的开发语言是Javascript2.Python:自己创建的服务 php ...
- PHP支付第3方接口使用方法。
去年写过一遍博客文章. 网站申请不到支付宝接口.微信接口,免接口收款实现方式. 网络在发展,支付宝也好,微信也好,技术在进步,这种方式已经不能使用了,明显的一个问题是,支付宝的刷新工具,会定时退出,必 ...
- 从返回的HTTP Header信息中隐藏Apache的版本号及PHP的X-Powered-By信息
默认情况下,很多apache安装时会显示版本号及操作系统版本,甚至会显示服务器上安装的是什么样的apache模块.这些信息可以为黑客所用,并且黑客还可以从中得知你所配置的服务器上的很多设置都是默认状态 ...
- Vue.js搭建路由报错 router.map is not a function,Cannot read property ‘component’ of undefined
错误: 解决办法: 2.0已经没有map了,使用npm install vue-router@0.7.13 命令兼容1.0版本vue 但是安装完之后会出现一个错误: Cannot read prope ...
- BZOJ 3238: [Ahoi2013]差异 [后缀自动机]
3238: [Ahoi2013]差异 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 2512 Solved: 1140[Submit][Status ...
- 测试人员如何使用Git部署测试环境
Git是分布式的版本控制系统. 作为一名Git的小白使用者,一开始接触很懵逼,因为总担心自己一不小心误操作影响代码仓库的代码,网络上关于Git的使用多从开发的角度,很少有人从测试的角度来介绍Git的使 ...
- Selenium_WebDriver_定位元素
版权声明:本文为博主原创文章,转载请注明出处. 定位单个元素 WebDriver提供了八种元素定位方法,Java中定位语句形如:driver.findElement(By.id()): 何为元素定位? ...
- javascript 数字字母组合的随机数
Math.random()方法用于生成,结果为0-1间的一个伪随机数(包括0,不包括1) ,通常的办法是结合parseInt().Math.floor() 或者 Math.ceil()进行四舍五入处理 ...