Android应用自定义View绘制方法手册
背景
这篇迟迟难产的文章算是对2015前半年的一个交代吧,那时候有一哥们要求来一发Android Canvas相关总结,这哥们还打赏了,实在不好意思,可是这事一放就给放忘了,最近群里小伙伴催着说没更新博客,坐等更新啥的,随先有这么一篇Android应用开发超级基础的文章诞生了(因为这种文章最好写哈,就是用熟了就行)。不得不说下这么久为何一直没更新博客的原因了,首先遇上了过年,我个人崇尚过节就该放下一切好好陪陪亲人,珍惜在一起的时光;其次今年开年很是蛋疼,不是不顺当就是深深的觉得被坑,所以心情也就低落那么一段时间,好在最近调整了一下,所以期待的文章日后还会持续,当年吹过的牛逼还得继续努力。
提到自定义绘制首先需要知道Canvas(android.graphics.Canvas),其实质就是一块画布,我们不仅可以设置画布的一些属性,还可以在上面画想画的任何东西。记不记得我们在自定义View时会重写如下方法:
protected void onDraw(Canvas canvas) {
}
该方法有一个牛逼的形参,那就是Canvas,当我们实现自己的自定义绘制时基本都是将内容画到这个Canvas画布上以后交给系统框架显示的;通过之前《Android应用层View绘制流程与源码分析》一文我们知道,整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法中触发的,该方法执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘(draw),在ViewRootImpl中我们有一个Surface成员,当ViewRootImpl触发performTraversals()进行重绘时会将该Surface的Canvas通过draw方法进行递归传递,从ViewGroup派发传递到最小的View元素的onDraw(Canvas canvas)方法。
这就解释了View中onDraw(Canvas canvas)形参的由来,所以当我们将自定义逻辑绘制到该Canvas后系统框架会通过调用SurfaceFlinger服务把测量、布局、绘制后的Surface渲染到屏幕上的(关于这个过程是很复杂的,后面会写文章分析的,这一原理不作为本文重点)。
有了画布这玩意的背景知识,那我们下面就来开始绘制相关的东东讲解;PS一句,Android的所有View控件无非都是文中这样为基石搞出来,搞懂基础后剩下的就是万变不离其宗了,掌握本文系列文章你可以随处装逼随处飞了。
温馨提示:文章巨长!!!做好分小节阅读准备(因为自己不想为了提升PV而搞成多篇,自己比较懒,一篇好管理,回顾时只需要Ctrl+F在这一篇搜索关键字就行了)。
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】
Android Paint攻略
上面提到了画布,那我们就得来一个笔才能画画啊,笔就是Paint,所以我们有必要先全面的对Paint进行扫盲。这货继承自Object,是graphics家族的东西,他有一个子类TextPaint,这个就不多说了,譬如实现绘制文本时换行就需要使用StaticLayout与TextPaint两个工具类结合(当年见过一个类似的需求,是用Paint去实现的,看着那一堆计算都蛋疼,为何不用TextPaint呢?)。
Paint的方法使用技巧
Paint的方法主要可以抽象成两大类,一类负责设置获取文字相关的东西,一类负责设置获取图形绘制相关的东西;其实就像是我们拿到了一张白纸,如何调色及选笔的过程就是Paint方法使用的过程,具体如下:
float getFontSpacing()
获取字符行间距。
float getLetterSpacing()
void setLetterSpacing(float letterSpacing)
设置和获取字符间距。
final boolean isUnderlineText()
void setUnderlineText(boolean underlineText)
是否有下划线和设置下划线。
final boolean isStrikeThruText()
void setStrikeThruText(boolean strikeThruText)
获取与设置是否有文本删除线。
float getTextSize()
void setTextSize(float textSize)
获取与设置文字大小,注意:Paint.setTextSize传入的单位是px,TextView.setTextSize传入的单位是sp,注意使用时不同分辨率处理问题。
Typeface getTypeface()
Typeface setTypeface(Typeface typeface)
获取与设置字体类型。Android默认有四种字体样式:BOLD(加粗)、BOLD_ITALIC(加粗并倾斜)、ITALIC(倾斜)、NORMAL(正常),我们也可以通过Typeface类来自定义个性化字体。
boolean hasGlyph(String string)
确定Paint设置的Typeface是否支持该字符串。
float getTextSkewX()
void setTextSkewX(float skewX)
获取与设置文字倾斜,参数没有具体范围,官方推荐值为-0.25,值为负则右倾,为正则左倾,默认值为0。
float getTextScaleX()
void setTextScaleX(float scaleX)
获取与设置文本沿X轴水平缩放值,默认为1,当值大于1会沿X轴水平放大文本,当值小于1会沿X轴水平缩小文本,不仅会改变文本宽度,还会拉伸或压缩字符。
Paint.Align getTextAlign()
void setTextAlign(Paint.Align align)
获取与设置文本对齐方式,取值为CENTER、LEFT、RIGHT,也就是文字绘制是左边对齐、右边还是局中的。
int breakText(char[] text, int index, int count, float maxWidth, float[] measuredWidth)
int breakText(CharSequence text, int start, int end, boolean measureForwards, float maxWidth, float[] measuredWidth)
int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)
计算指定参数长度能显示多少个字符,同时可以获取指定参数下可显示字符的真实长度,譬如:
private static final String STR = "我们XXOOCC";
mPaint.setTextSize(50);
float[] value = new float[1];
int ret = mPaint.breakText(STR, true, 200, value);
Log.i("YYYY", "breakText="+ret+", STR="+STR.length()+", value="+value[1]);
//breakText=5, STR=8, value=195.0
float getFontMetrics(Paint.FontMetrics metrics)
Paint.FontMetrics getFontMetrics()
Paint.FontMetricsInt getFontMetricsInt()
int getFontMetricsInt(Paint.FontMetricsInt fmi)
getFontMetrics()返回FontMetrics对象;getFontMetrics(Paint.FontMetrics metrics)返回文本的行间距,metrics的值不为空则返回FontMetrics对象的值;getFontMetricsInt()返回FontMetricsInt对象,FontMetricsInt和FontMetrics对象一样,只不过FontMetricsInt返回的是int而FontMetrics返回的是float。FontMetrics与FontMetricsInt都有top、ascent、descent、bottom、leading这几个属性,具体介绍如下图所示(此图来自网络,致谢出处):
可以看见,我们使用canvas的drawText绘制文字传入的y其实是上图的base线,绘制文字坐标是以base基线为参考的;FontMetrics中的top是base到最高字符的最大值(即ascent的最大值),ascent是base到最高字符的推荐值,descent是base到最低字符的推荐值,bottom是base到最低字符的最大值(即decent的最大值),leading是文本行之间推荐的额外高度,单行文字一般为0。所以获取文字的高就是mPaint.ascent() + mPaint.descent(),获取文字的宽就是mPaint.measureText(text)。
float ascent()
float descent()
ascent获取baseline之上至字符最高处的距离,具体参见FontMetrics。descent获取baseline之下至字符最低处的距离,具体参见FontMetrics。
void getTextBounds(char[] text, int index, int count, Rect bounds)
void getTextBounds(String text, int start, int end, Rect bounds)
获取文本的宽高,通过bounds的Rect拿到整型。
float measureText(String text)
float measureText(CharSequence text, int start, int end)
float measureText(String text, int start, int end)
float measureText(char[] text, int index, int count)
粗略获取文本的宽度,和上面的getTextBounds比较类似,返回浮点数。
int getTextWidths(String text, int start, int end, float[] widths)
int getTextWidths(String text, float[] widths)
int getTextWidths(CharSequence text, int start, int end, float[] widths)
int getTextWidths(char[] text, int index, int count, float[] widths)<