这篇迟迟难产的文章算是对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 未经允许严禁转载,请尊重作者劳动成果。私信联系我】
上面提到了画布,那我们就得来一个笔才能画画啊,笔就是Paint,所以我们有必要先全面的对Paint进行扫盲。这货继承自Object,是graphics家族的东西,他有一个子类TextPaint,这个就不多说了,譬如实现绘制文本时换行就需要使用StaticLayout与TextPaint两个工具类结合(当年见过一个类似的需求,是用Paint去实现的,看着那一堆计算都蛋疼,为何不用TextPaint呢?)。
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)
精确计算文字宽度,与上面两个类似。
Locale getTextLocale()
void setTextLocale(Locale locale)
获取与设置地理位置,一般直接传入Locale.getDefault()即可。用来设置文本的区域比如中文、法文等。
final boolean isSubpixelText()
void setSubpixelText(boolean subpixelText)
获取与设置文字显示效果,设置该项为true,将有助于文本在LCD屏幕上的显示效果。
final boolean isFakeBoldText()
void setFakeBoldText(boolean fakeBoldText)
获取与设置文本是否仿粗体,小字体设置效果非常差。
final boolean isLinearText()
void setLinearText(boolean linearText)
获取与设置是否打开线性文本标识。因为默认Android中文本绘制需要使用一个Bitmap作为单个字符缓存,因此会使用一定的空间,为了不使用该空间则设为true即可。
void getTextPath(char[] text, int index, int count, float x, float y, Path path)
void getTextPath(String text, int start, int end, float x, float y, Path path)
获取文字轮廓的Path值,例子如下:
Path path = new Path();
mPaint.getTextPath(STR, 0, STR.length(), 0, 800, path);
path.close();
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(5);
canvas.drawPath(path, mPaint);
//显示出来的Path路径绘制就是STR字符串,特别注意Path要close,否则无效。
void reset()
清空画笔复位。
void set(Paint src)
设置一个外来Paint画笔。
int getFlags()
void setFlags(int flags)
获取与设置Paint的一些属性flag,譬如抗锯齿、防抖等。可取值为ANTI_ALIAS_FLAG、DEV_KERN_TEXT_FLAG、DITHER_FLAG、EMBEDDED_BITMAP_TEXT_FLAG、FAKE_BOLD_TEXT_FLAG、FILTER_BITMAP_FLAG、HINTING_OFF、HINTING_ON、LINEAR_TEXT_FLAG、STRIKE_THRU_TEXT_FLAG、SUBPIXEL_TEXT_FLAG、UNDERLINE_TEXT_FLAG,这些flag大多都有对应的Paint方法设置与获取判断,多个设置可以通过按位或操作即可。
void setARGB(int a, int r, int g, int b)
int getAlpha()
void setAlpha(int a)
int getColor()
void setColor(int color)
获取与设置alpha值、颜色、ARGB等。
final boolean isAntiAlias()
void setAntiAlias(boolean aa)
获取与设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢,一般会开启。
final boolean isDither()
void setDither(boolean dither)
获取与设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满、图像更加清晰。
final boolean isFilterBitmap()
void setFilterBitmap(boolean filter)
获取与设置图像过滤处理,一般设置为true。
Paint.Cap getStrokeCap()
void setStrokeCap(Paint.Cap cap)
当画笔样式为STROKE或FILL_OR_STROKE时,获取与设置画笔开始与离开时那一点样式(譬如圆角直线顶点),可取值与效果如下图:
不设置默认值是BUTT。
Paint.Join getStrokeJoin()
void setStrokeJoin(Paint.Join join)
获取与设置画笔画线等连接处的轮廓样式,可取值与效果如下图:
不设置默认是MITER。
float getStrokeMiter()
void setStrokeMiter(float miter)
获取与设置画笔的倾斜度。
Paint.Style getStyle()
void setStyle(Paint.Style style)
获取与设置画笔样式,可取值如下:
FILL:实心。
FILL_OR_STROKE:同时实心和空心。
STROKE:空心。
注意STROKE、FILL_OR_STROKE与FILL模式下外轮廓的位置会扩大。
float getStrokeWidth()
void setStrokeWidth(float width)
获取与设置画笔的粗细,在画笔样式为STROKE或FILL_OR_STROKE时有效。
void clearShadowLayer()
void setShadowLayer(float radius, float dx, float dy, int shadowColor)
清除与设置阴影层和颜色,譬如绘制一个圆,在圆周给一个阴影扩散效果就可用它。
Shader getShader()
Shader setShader(Shader shader)
非常重要的方法,获取与设置渲染方法。Shader的直接子类有BitmapShader位图图像渲染、LinearGradient线性渲染、RadialGradient环形渲染、SweepGradient扫描渐变渲染/梯度渲染、ComposeShader组合渲染,我们可以单独或者组合使用。具体描述如下:
Shader基类有一个Shader.TileMode的枚举类,其值CLAMP是拉伸最后一个像素铺满、REPEAT是类似电脑壁纸,横向纵向不足的重复放置、MIRROR是横向纵向不足处不断翻转镜像平铺;该类还提供boolean getLoaclMatrix(Matrix localM)与void setLocalMatrix(Matrix localM)两个矩阵变换方法;下面我们来看下他的子类使用。
1、BitmapShader位图图像渲染实例(右侧毛爷爷是左侧渲染处理过的图):
/** * 渲染bitmap图片为圆角图片 * new BitmapShader(bitmap, tileX, tileY) * tileX、tileY是位图在XY方向的TileMode模式 */
Paint mPaint = new Paint();
BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.icon1);
Bitmap bitmap = drawable.getBitmap();
BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
/*如下四行Matrix可以不设置,这里是为了处理不等宽高图片渲染后铺不满问题 Matrix matrix = new Matrix(); float scale = Math.max(bitmap.getWidth(), bitmap.getHeight())*1.0f / Math.min(bitmap.getWidth(), bitmap.getHeight()); matrix.setScale(scale, scale); shader.setLocalMatrix(matrix);*/
mPaint.setShader(shader);
canvas.drawCircle(bitmap.getWidth()/2, bitmap.getHeight()/2, bitmap.getHeight()/2, mPaint); //需要啥图形就怎么画
/*如上两行还可以这么实现 ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape()); shapeDrawable.getPaint().setShader(shader); shapeDrawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getWidth()); shapeDrawable.draw(canvas); */
2、LinearGradient线性渲染:
这个就不用多介绍了,有些文字渐变或者色度条都是通过它实现的,这里给出它最多参数构造函数的解释就行了,具体和上面类似用法,如下:
/** x0为渐变起始点x坐标 y0为渐变起始点y坐标 x1为渐变结束点x坐标 y1为渐变结束点y坐标 colors数组为颜色的int数组 positions数组为相对位置的颜色数组,null则颜色沿渐变线均匀分布 tile为渲染器平铺模式Shader.TileMode */
public LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions,Shader.TileMode tile)
3、RadialGradient环形渲染:
废话不多说,和上面类似,这货可以直接结合Paint的alpha值来画画水波纹啥玩意的,如下:
/** x为圆心X坐标 y为圆心Y坐标 radius为圆半径 colors为渲染颜色数组 positions为相对位置数组,可为null,为null则颜色沿渐变线均匀分布 tile为渲染器平铺模式 */
public RadialGradient(float x, float y, float radius, int[] colors, float[] positions,Shader.TileMode tile)
4、SweepGradient扫描渐变渲染/梯度渲染:
同上,不多扯,见过微信雷达添加好友的扫描界面或者各种手机大师的扫描界面就知道这玩意的作用了,一样给出最核心的构造函数解释,如下:
/** cx为渲染中心x坐标 cy为渲染中心y坐标 color0为起始渲染颜色 color1为结束渲染颜色 */
public SweepGradient(float cx, float cy, int color0, int color1)
/**
cx为渲染中心x坐标
cy为渲染中心y坐标
colors为围绕中心渲染的颜色数组,最少要有两种颜色,切记!!!
positions为相对位置的颜色数组,为null则颜色沿渐变线均匀分布
*/
public SweepGradient(float cx, float cy, int[] colors, float[] positions)
5、ComposeShader组合渲染:
这个略叼!类似Android动画中的组合动画,可以整合上面的几种,先来看下构造函数解释,如下:
/** shaderA为Shader渲染器A shaderB为Shader渲染器B mode为两种渲染器组合的模式,使用Xfermode对象 */
public ComposeShader(Shader shaderA,Shader shaderB, Xfermode mode)
/**
shaderA为Shader渲染器A
shaderB为Shader渲染器B
mode为两种渲染器组合的模式,使用ProterDuff.Mode对象
*/
public ComposeShader(Shader shaderA,Shader shaderB, PorterDuff.Mode mode)
上面两种组合构造方法的具体混合模式和Xfermode与ProterDuff.Mode对象有关,他们又可以与我们传入的两种shader进行多重组合实现不同的效果;这两种模式紧接着的下面方法就会说到。
Xfermode getXfermode()
Xfermode setXfermode(Xfermode xfermode)
非常重要的方法设置两绘制相交时的模式,因为正常的情况下在已有图像上绘图会完全遮挡住下面已有的图,所以setXfermode()方法的作用就是来设置叠加时该如何搞的规则,传递null可以清除任何以前的xfermode设置。要想彻底说明白这两个方法的效果首先我们得看看Xfermode是啥玩;为了说明白,这里先给出一个简单的不能再简单的代码:
//canvas上原有的图片叫做dst
//Canvas上新画上去的图片叫做src
Canvas canvas = new Canvas(dstBitmap);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(srcBitmap, 0f, 0f, paint);
有了上面这个代码,下面我们就来看看和Xfermode这货相关的一些东西:
可以看见,这货本来有三个子类呢,结果两个都被Deprecated down了,那我们重点放在第三个上面,前面两个简单说下:
1、Xfermode 的AvoidXfermode子类
该类用来指定一个颜色和容差值控制Paint是否在指定容差颜色范围内绘制或不绘制。构造函数如下:
/** opColor 要匹配的目标颜色。 tolerance 容差值,0代表最小容差,也就是我们目标相素值和opcolor颜色一样的点才匹配成功,255代表最大容差,也就是我们目标像素值只要和opcolor有一点相近就匹配成功。 mode 有两个可取枚举值,如下: Avoid模式:只在目标像素值和opcolor匹配成功的地方进行绘制。 Target模式:只在目标像素值和opcolor匹配没成功的地方进行绘制。 */
public AvoidXfermode(int opColor, int tolerance, Mode mode)
下面给出一个例子的核心代码段:
//!!!由于高版本API已经Deprecated,所以必须关闭View硬件加速才有效果!!!
//例子:让红色的巨形其他背景色的五角星变成保持背景色不变的黑色五角星,我们可以由此启发做成依据手势触发渐变的效果,譬如拿出你的手机看看微信底部Tab上图标在你滑动切换页面时的渐变效果(绿色随手指滑动到灰色)。
//假设我们有一个红色的正方形五角星,五角星边缘到正方形边缘的填充区域不为红色(譬如透明或者白色)
mBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.red_star)).getBitmap();
//创建一个AvoidXfermode相关的Paint对象
mAvoidPaint = new Paint();
//设置准备将红色五角星要变成的颜色,黑色
mAvoidPaint.setColor(Color.BLACK);
//创建一个准备对红色相近的颜色进行替换的Xfermode
mAvoidXfermode = new AvoidXfermode(Color.RED, 10, Mode.TARGET);
//绘制图片
canvas.drawBitmap(mBitmap, mLeft, mTop, mAvoidPaint);
//设置图层混合模式
mAvoidPaint.setXfermode(mAvoidXfermode);
//绘制色块进行混合(让mBitmap中在mAvoidDestRect区域接近为红色的像素点变为黑色),得到最终效果
canvas.drawRect(mAvoidDestRect, mAvoidPaint);
//此时画布上的五角星就变成黑色了
2、Xfermode 的PixelXorXfermode子类
该类用来设置在覆盖已有像素颜色时进行像素的异或操作。构造函数如下:
//opColor 目标颜色
PixelXorXfermode(int opColor)
这个就没必要再给出例子了,上面那个都能看懂的话,这个就没啥意思了,也过时了,其核心意思就是重叠的像素进行opPixColor ^ srcPixColor ^ dstPixColor,变成简单的异或颜色显示。
3、Xfermode 的PorterDuffXfermode子类
该类是一个非常强大的转换模式,我们可以通过设置它PorterDuff规则的任意一条来控制Paint如何与已有的Canvas图像进行图层重叠处理。简直就是图层混合处理界的busybox瑞士军刀,哈哈,我调皮了,老想着老本行。既然这样,那我们就先看下PorterDuff.Mode(特别注意:这些模式不仅仅应用于图形叠加裁剪混合,还应用于图像色彩混合,后面ColorFilter会说到。)枚举类,该枚举类提供了十六种枚举值(你可以进去看看那些枚举值时怎么算的,自己也可以模仿搞几个,其中Sa是源alpha值的意思,同理Da是目标alpha值、Sc是源色值、Dc是目标色值,他们进行像素运算搞出来的效果),其值与作用效果如下图:
(声明:为了节省成本,此图来源自来自ApiDemos的XferModes实例,不过蛋疼的时官方竟然只实现了前16种枚举的效果,最后两种鬼知道为啥一直没更新,自己去试吧!)
可以看见这些Xfermode的类最终都可以被PorterDuffXfermode终结掉,我们可以通过搞出各种脑洞大开的花样,譬如猎豹大师那个圆形水波纹荡漾上升的进度条啥玩意的,还有就是那种文字进度条啥玩意的,还有就是各种秀秀那种橡皮擦哇、刮刮乐等等的东东。。。
MaskFilter getMaskFilter()
MaskFilter setMaskFilter(MaskFilter maskfilter)
非常重要的方法,该方法可以用来对图像进行一定的Alpha滤镜处理(ColorFilter是对RGB进行滤镜处理,下面会讲),MaskFilter类中没有任何实现方法,我们一般使用的时它的两个子类BlurMaskFilter和EmbossMaskFilter,前者为模糊遮罩滤镜,后者为浮雕遮罩滤镜。具体如下:
1、BlurMaskFilter滤镜
该类构造方法如下:
//radius 阴影范围
//style 模糊类型
BlurMaskFilter(float radius, Blur style)
用法也比较简单,直接给paint设置setMaskFilter后进行绘制即可,不过要特别注意必须关闭硬件加速才有效果,下面给出一个简单例子:
//必须关闭硬件加速才有效果
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//具备alpha通道的颜色
paint.setColor(Color.RED);
paint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.NORMAL));
canvas.drawRect(rect, paint);
//图片提取Bitmap的Alpha通道做法
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
//提取Alpha通道
Bitmap bitmapAlpha = bitmap.extractAlpha();
paint.setColor(Color.RED);
paint.setMaskFilter(new BlurMaskFilter(30, BlurMaskFilter.Blur.SOLID));
//绘制模糊背景
canvas.drawBitmap(bitmapAlpha, 300, 500, paint);
//绘制原图
canvas.drawBitmap(bitmap, 300, 500, paint);
关于构造函数里第二个参数BlurMaskFilter.Blur的值其实有四个枚举可取,具体效果如下图:
四个枚举值解释如下:
枚举 | 含义 |
---|---|
NORMAL | 整个图像被模糊掉。 |
SOLID | 在图像的Alpha边界外产生一层与Paint颜色一致的阴影效果而不影响图像本身。 |
OUTER | 在图像的Alpha边界外产生一层阴影且会将原图像变为透明效果。 |
INNER | 在图像内部边沿产生模糊效果。 |
2、EmbossMaskFilter滤镜
该类主要实现凸起立体感的浮雕效果,也需要关闭硬件加速才有效果,其构造方法如下:
/** direction 指定长度为3的数组标量[x,y,z],用来指定光源的方向 ambient 指定周边背景光,取值为0到1之间 specular 指定镜面反射系数 blurRadius 指定模糊半径 */
public EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius)
使用流程和上面差不多,这里给出一个简单的例子就行了,如下:
paint.setMaskFilter(new EmbossMaskFilter(new float[]{20, 20, 20}, 0.4f, 10, 15));
canvas.drawRect(rect5, paint);
PathEffect getPathEffect()
PathEffect setPathEffect(PathEffect effect)
非常重要的方法,设置Paint绘制路径的效果,效果来自PathEffect的子类,默认的PathEffect没有啥方法,他们关系如下:
其六个子类的作用及效果比较简单,例子如下直接说明:
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
PathEffect[] pathEffects = new PathEffect[7];
//不设置效果,默认转角处带有毛刺
pathEffects[0] = null;
/** * 用平滑的圆角代替尖角 * radius 转角处的圆滑程度 */
pathEffects[1] = new CornerPathEffect(100);
/** * 创建一个虚线的轮廓(短横线或者小圆点) * intervals[] 数组中第一个参数定义第一个实线的长度,第二个参数为虚线长度,以此类推或者重复 * phase 偏移值,动态改变其值会让路径产生动画的效果,就像进度条往前走一样 */
pathEffects[2] = new DashPathEffect(new float[]{50, 20, 5, 10}, 0);
/** * 添加了随机性,类似生锈铁丝的效果 * segmentLength 指定突出杂点的密度,值越小杂点越密集 * deviation 指定杂点突出的大小,值越大突出的距离越大 */
pathEffects[3] = new DiscretePathEffect(5f, 5f);
/** * 和DashPathEffect类似,只不过可以自定义路径虚线的样式 * shape 自定义的路径样式,这里定义为方形 * advance 每个shape间的距离 * phase 偏移值,动态改变其值会让路径产生动画的效果,就像进度条往前走一样 * style 设置连接处的样式,取值如下: * ROTATE 线段连接处的shape进行适当角度旋转连接 * MORPH 线段连接处的shape以拉伸或者压缩形变连接 * TRANSLATE 线段连接处的shape平行平移连接 */
Path pathShape = new Path();
pathShape.addRect(0, 0, 10, 10, Path.Direction.CCW);
pathEffects[4] = new PathDashPathEffect(pathShape, 20, 0, PathDashPathEffect.Style.ROTATE);
/** * 组合两种路径效果,先将路径变成innerpe的效果,再去复合outerpe的路径效果 * outerpe PathEffect效果A * innerpe PathEffect效果B */
pathEffects[5] = new ComposePathEffect(pathEffects[3], pathEffects[4]);
/** * 组合两种路径效果,把两种路径效果加起来再作用于路径 * first PathEffect效果A * second PathEffect效果B */
pathEffects[6] = new SumPathEffect(pathEffects[2], pathEffects[4]);
Path path = new Path();
path.moveTo(0, 0);
for (int index=1; index<20; index++){
path.lineTo(index*40, (float)Math.random()*150);
}
for (int index=0; index<pathEffects.length; index++){
paint.setPathEffect(pathEffects[index]);
canvas.drawPath(path, paint);
canvas.translate(0, 200);
}
ColorFilter getColorFilter()
ColorFilter setColorFilter(ColorFilter filter)
非常重要的方法,上面讲了MaskFilter是对Alpha通道过滤,这个ColorFilter是对RGB(A)进行过滤。这货强大的不能再强大,各种秀秀都是这货功不可没,下面看下他的类关系:
下面就开始装逼呗(小插曲:起初自己接触Android学到这玩意时那个郁闷啊,真是日了狗了,当初大学《数字图像处理理论》课程老师教的Low,自己学的更Low,但是后来还是硬着头皮打完了自己约的炮。),一个一个来看:
1、ColorMatrixColorFilter
颜色过滤,该类通过颜色矩阵(ColorMatrix)对图像中的像素色值(不包含Alpha)进行改变,由于图片的每个像素是以RGBA的形式加载到内存中的,所以改变图片的颜色需要Android ColorMatrix颜色矩阵类支持,而颜色矩阵是一个以一维数组存储在代码中的5x4矩阵(每一行代表RGBA中一个),如下:
但是我们图像中每个像素的展示效果却取决于存储在一个5x1颜色分量矩阵中,如下:
所以为了修改我们图像每个像素点的效果,我们只需修改ColorMatrix的颜色矩阵值,然后与分量矩阵做运算即可,运算结果如下:
R' = a*R + b*G + c*B + d*A + e;
G' = f*R + g*G + h*B + i*A + j;
B' = k*R + l*G + m*B + n*A + o;
A' = p*R + q*G + r*B + s*A + t;
可以看见,这就是标准的大学线性代数知识了,其实颜色矩阵可以实现很多效果的,我们一般只用处理ColorMatrix的参数即可得到各种变换,下面我们来看下它的构造方法:
//通过传入ColorMatrix矩阵进行使用
public ColorMatrixColorFilter(ColorMatrix matrix)
//通过传入一个自定义的矩阵,上面讲到了,这个矩阵实质是一维数组
public ColorMatrixColorFilter(float[] array)
现在给出一个使用它的例子,如下:
public void setImageArgb(float alpha, float red, float green, float blue) {
mColorMatrix = new ColorMatrix(new float[] {
red, 0, 0, 0, 0,
0, green, 0, 0, 0,
0, 0, blue, 0, 0,
0, 0, 0, alpha, 0,
});
mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
postInvalidate();
}
通过上面方法我们传入不同的参数就能得到各种过滤处理的图片,譬如老照片、QQ在线离线头像图标颜色变换啥玩意的。
2、LightingColorFilter
曝光颜色过滤,多说无用,来看它的构造方法:
/** mul (colorMultiply)色彩倍增,16进制的色彩值0xAARRGGBB。 add (colorAdd)色彩添加,16进制的色彩值0xAARRGGBB。 */
public LightingColorFilter(int mul, int add)
这构造方法参数这么奇葩?来看个例子就明白了,如下:
//mul=0xFF00FFFF
//add=0x0000FF00
mPaint.setColorFilter(new LightingColorFilter(0xFF00FFFF, 0x0000FF00));
上面这段代码应用在一个Bitmap上之后会过滤掉图片里的红色,加强图片里的绿色,自行感受效果,哈哈,其实质计算方式就是(mul * 原色值 + add)% 255,不过一定要注意,该过滤器是不处理Alpha的,也就是说只对图片里的RGB有效,A无效。
3、PorterDuffColorFilter
图层混合颜色过滤,依旧多说无用,先看构造方法,如下:
//color 16进制的颜色值。
//mode PorterDuff.Mode的混合模式,上面有介绍。
public PorterDuffColorFilter(int color, PorterDuff.Mode mode)
通过该类构造方法可以看出来,混合模式过滤其实就是将画布上的像素和我们设置的color以mode方式进行叠加产生的效果(特别注意:这些模式不仅仅应用于图像色彩混合,上面我们还将他用运在了图形叠加裁剪混合上面,可自行上翻查看)。
这个例子就不给了,自行闹着玩玩就行,没啥可说的,自己脑补对比上面换值玩玩吧。
到此关于Android Paint相关的东东就介绍完了,没啥悬念的。
上面我们已经买到了一板非常牛叉的彩笔,也尝试完了那些彩笔能画出来的各种色彩特性,下面我们就该搞一张画纸来画画了,也就是该Canvas上场了,那我们下面就来依次展示出这些Canvas的牛逼大招吧,下面我们以Canvas的方法大类来介绍他们的技巧。
Canvas()
Canvas(Bitmap bitmap)
Canvas构造方法,一般自定义View都由上层传入,单独new的Canvas()构造方法通常与setBitmap()配合使用,Canvas(Bitmap bitmap)有参构造方法的参数Bitmap必须是mutable可变化的!
setBitmap(Bitmap bitmap)
设置可变化的位图,与上面Canvas(Bitmap bitmap)比较类似。通常用在获取一张Bitmap后通过Canvas处理一下。
getDensity()
setDensity(int density)
获取与设置画布密度,默认为Bitmap的密度或者DENSITY_NONE。
getHeight()
getWidth()
获取Canvas的高与宽。
isHardwareAccelerated()
判断当前Canvas是否开启了硬件加速。可以在View或者Activity中开启关闭。
isOpaque()
判断是否支持透明度。
getDrawFilter()
setDrawFilter(DrawFilter filter)
获取与设置DrawFilter。DrawFilter为何物呢,如下:
//PaintFlagsDrawFilter介绍(DrawFilter默认啥都没有,其默认有一个子类就是PaintFlagsDrawFilter):
//唯一的方法
//clearBits 清除Paint已经存在的指定flag
//setBits 设置Paint的flag
public PaintFlagsDrawFilter(int clearBits, int setBits)
例子如下:
//抗锯齿写法一
paint.setAntiAlias(true);
//抗锯齿写法二,按位操作就行
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
上面这些方法都很基础,不管拿Canvas绘制任何东西都可能用到,而且用法相同。
这里我们主要来看Canvas绘制相关的方法怎么用,简单和没有坑的方法就不给出例子了,复杂或者有坑的会给出详细例子,具体如下:
drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
drawLines(float[] pts, Paint paint)
drawLines(float[] pts, int offset, int count, Paint paint)
绘制线,线的拐角处及顶点样式或者颜色及宽度都可有上面的Paint控制,该方法只是指定你多彩的线该画在哪。构造方法解释如下:
/** startX 开始点X坐标。 startY 开始点Y坐标。 stopX 结束点X坐标。 stopY 结束点Y坐标。 将两点连接成一条线。 */
drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
/** pts 点集合。 offset 绘制开始跳过pts集合前面多少个数值。 count 绘制从offset开始的count个数值点组成的坐标,count必须大于2。 默认将每两个点形成一条直线,譬如pts={0,0,100,100,200,200,400,400}就是两段没连在一起的直线段。 */
drawLines(float[] pts, Paint paint)
drawLines(float[] pts, int offset, int count, Paint paint)
drawPoint(float x, float y, Paint paint)
drawPoints(float[] pts, int offset, int count, Paint paint)
drawPoints(float[] pts, Paint paint)
绘制点。点的形状样式或者颜色及大小都可有上面的Paint控制,该方法只是指定你的点该画在哪。构造方法解释如下:
/** x 点X坐标。 y 点Y坐标。 绘制一个点。 */
drawPoint(float x, float y, Paint paint)
/** pts 点集合。 offset 绘制开始跳过pts集合前面多少个数值。 count 绘制从offset开始的count个数值点组成的坐标,count必须大于2。 */
drawPoints(float[] pts, int offset, int count, Paint paint)
drawPoints(float[] pts, Paint paint)
drawRect(float left, float top, float right, float bottom, Paint paint)
drawRect(RectF rect, Paint paint)
drawRect(Rect r, Paint paint)
绘制矩形。矩形的填充样式或者颜色及线宽都可有上面的Paint控制,该方法只是指定你多大的矩形该画在哪。构造方法解释如下:
/** 传入矩形的四个点(其实就是矩形的左上角和右下角坐标)绘制一个指定样式的矩形 */
drawRect(float left, float top, float right, float bottom, Paint paint)
/** 通过一个RectF或者Rect的对象确定矩形的四个点然后绘制一个矩形 */
drawRect(RectF rect, Paint paint)
drawRect(Rect r, Paint paint)
RectF与Rect是矩形的辅助类,区别不大,其根据四个点构建一个矩形结构,只是两个精度不同而已。
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
drawRoundRect(RectF rect, float rx, float ry, Paint paint)
绘制圆角矩形。圆角矩形的填充样式或者颜色及线宽都可有上面的Paint控制,该方法只是指定你多大的圆角矩形该画在哪。构造方法解释如下:
/** rx 生成圆角的椭圆的X轴半径 ry 生成圆角的椭圆的Y轴半径 其他参数与绘制矩形类似。 */
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
drawRoundRect(RectF rect, float rx, float ry, Paint paint)
drawCircle(float cx, float cy, float radius, Paint paint)
绘制圆形。圆形的填充样式或者颜色及线宽都可有上面的Paint控制,该方法只是指定你多大的圆和该画在哪。构造方法解释如下:
/** cx 圆心点X轴坐标 cy 圆心点y轴坐标 radius 圆半径 */
drawCircle(float cx, float cy, float radius, Paint paint)
drawOval(float left, float top, float right, float bottom, Paint paint)
drawOval(RectF oval, Paint paint)
绘制椭圆。类比同上,这个都一样没啥技术含量。
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
绘制圆弧。弧形的填充样式或者颜色及线宽都可有上面的Paint控制,构造方法解释如下:
/** startAngle 弧开始角度,X轴正方向为0度。 sweepAngle 弧经历角度。 useCenter 是否有弧的两边,True有两边,False只有一条弧。 其他参数不做介绍。 */
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
drawPath(Path path, Paint paint)
绘制路径。路径的样式或者颜色及线宽都可有上面的Paint控制(譬如平滑还是折线等)。该方法涉及一个Path对象,该对象的使用后面会有详细介绍,该方法的使用Paint中已经有简单的实例了。
drawText(String text, float x, float y, Paint paint)
drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
drawText(char[] text, int index, int count, float x, float y, Paint paint)
drawText(String text, int start, int end, float x, float y, Paint paint)
绘制水平方向文字。文字的填充样式或者颜色及线宽和文字大小等都可有上面的Paint控制,构造方法解释如下:
//从指定点开始横向绘制一段默认文字(或者是指定字符串中的一段文字)。
drawText(String text, float x, float y, Paint paint)
drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
drawText(char[] text, int index, int count, float x, float y, Paint paint)
drawText(String text, int start, int end, float x, float y, Paint paint)
drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)
沿路径绘制文字。文字的填充样式或者颜色及线宽和文字大小等都可有上面的Paint控制,构造方法特殊参数解释如下:
//hOffset 代表与路径起始点的水平偏移距离,也就是向上或者向下偏移。
//vOffset 代表与路径中心的垂直偏移量,譬如在圆内偏移到圆外轮廓绘制文字等。
drawPosText(char[] text, int index, int count, float[] pos, Paint paint)
drawPosText(String text, float[] pos, Paint paint)
依据每个坐标一一绘制每个文字。文字的填充样式或者颜色及线宽和文字大小等都可有上面的Paint控制,构造方法特殊参数解释如下:
//pos 每个字体的位置,每两个数字一组确定一个文字的坐标点。
drawRGB(int r, int g, int b)
drawARGB(int a, int r, int g, int b)
drawColor(int color)
drawColor(int color, PorterDuff.Mode mode)
绘制颜色,单纯的对画布进行颜色处理,默认PorterDuff.Mode为SRC_OVER模式,不过可以通过drawColor(int color, PorterDuff.Mode mode)进行指定操作。
drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)
drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
绘制各种位图,图片的视觉色彩形状等处理需要Paint参数的配合,具体前面已经介绍了,这里重点看下drawBitmap的一些特殊参数:
/** 指定图片左上角的坐标开始绘制一幅图片。 */
drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
/** src 指定bitmap裁截区域,null则不裁剪bitmap。 dst 裁剪后的bitmap在canvas中显示的区域大小,src通过放大缩小适应dst区域。 对图片裁剪后放大缩小显示在指定的区域中。 */
drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
/** 对一个bitmap进行矩阵变换绘制。 */
drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)
上面介绍Paint的ColorFilter方法时介绍了ColorMatrix颜色矩阵,我们可以通过它进行图片的色度处理,而这里的Matrix就是用来处理图像形状变换的,简单介绍Matrix如下:
//Matrix类常用方法介绍:
//Matrix平移,坐标即位置。
setTranslate(float dx, float dy)
//Matrix旋转,degrees为旋转角度,px、py为旋转轴心位置。
setRotate(float degrees, float px, float py)
//Matrix缩放,sx、sy为X和Y轴上的缩放比例,px、py为缩放的轴心位置。
setScale(float sx, float sy, float px, float py)
//Matrix倾斜,kx、ky为X和Y轴上的缩放比例。
setSkew(float kx, float ky)
drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)
绘制扭曲位图,图片的视觉色彩形状等处理需要Paint参数的配合,具体前面已经介绍了。这个方法还是很牛逼的,譬如拉窗帘、Mac关闭网页吸入效果等都可以靠他来搞,其原理就是按照网格来重新拉伸我们的图像。这里重点看下_drawBitmapMesh的一些特殊参数:
/** bitmap 原位图。 meshWidth 横向上把原位图划分为多少格。 meshHeight 纵向上把原位图划分为多少格。 verts 长度为 (meshWidth+1) * (meshHeight+1) * 2 + vertOffset的数组,记录了扭曲后位图各顶点(网格线交点)位置,每两个数值表示一个坐标。 vertOffset 控制verts数组从第几个数组元素开始对bitmap进行扭曲。 colors 作用于上面的颜色数组,一般为null,长度为(meshWidth+1) * (meshHeight+1) + colorOffset。 colorOffset 控制colors数组从第几个数值开始。 */
drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)
这个效果网上一堆,也不怎么常用,我以前也只研究了APIDemo中的例子,不再介绍,基础例子参见APIDemo。
drawVertices(Canvas.VertexMode mode, int vertexCount, float[] verts, int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, int indexOffset, int indexCount, Paint paint)
绘制扭曲位图。它是drawBitmapMesh()方法的通用格式,也称为更加通用版,其依赖于Canvas.VertexMode的设置,其他和drawBitmapMesh()类似。这个效果网上一堆,也不怎么常用,我以前也只研究了APIDemo中的例子,不再介绍,基础例子参见APIDemo。
drawPicture(Picture picture)
drawPicture(Picture picture, RectF dst)
drawPicture(Picture picture, Rect dst)
记录切高效绘制canvas的辅助工具方法。其中Picture在android.graphics.Picture包中,相对于Drawable和Bitmap而言小巧很多,因为它存储的不是实际像素,而仅仅记录了每个绘制的过程。关于Picture具体如下(特别注意:硬件加速开启后失效):
//开始记录绘制过程
Canvas beginRecording(int width, int height)
//静态方法,从输入流创建一个Pictrue对象
static Picture createFromStream(InputStream stream)
//在canvas上画这个picture对象
void draw(Canvas canvas)
//结束录制绘制过程
void endRecording()
//获取宽高
int getHeight()
int getWidth()
//将绘制结果写到输出流中
void writeToStream(OutputStream stream)
......
PictureDrawable在android.graphics.drawable.PictureDrawable中,它是从Drawable类继承而来的,具体如下(特别注意:硬件加速开启后失效):
//构造方法从Picture对象中实例化
PictureDrawable(Picture picture)
//绘制到Canvas
void draw(Canvas canvas)
//获取透明度级别
int getOpacity()
//从PictureDrawable转为Picture
Picture getPicture()
//设置透明级别
void setAlpha(int alpha)
......
介绍完了drawPicture()方法中的Picture与PictureDrawable,下面给一个简单例子代码段,如下:
//一个高效绘制自定义View的例子模板:
Picture picture = new Picture();
Canvas canvas = picture.beginRecording(300, 500);
//canvas.drawBitmap();
//等等一堆画线画字画图操作
//这些操作都是基于Picture返回的canvas的,切记!!!!
picture.endRecording();
PictureDrawable pictureDrawable = new PictureDrawable(picture);
//viewCanvas是自定义View中onDraw方法的形参,切记!!!
pictureDrawable.draw(viewCanvas) ;
可以看见,Picture可以记录我们Canvas上每个绘制操作,最后统一回放每个绘图操作。这些功能Bitmap也能实现,但是Picture只是记录了我们绘图得操作而不是绘制后的像素结果而不是Bitmap等渲染结果,所以存储大小比Bitmap要小得多,同时渲染速度也比Bitmap要快。
getMaximumBitmapHeight()
getMaximumBitmapWidth()
获取Bitmap的最大宽高。有了这组方法的限定就可以避免出现如下错误:
Bitmap too large to be uploaded into a texture (4405x9705, max=4096x4096).
上面这个错误是因为开启硬件加速时GPU对openglRender有一个限制,不同手机限制不同,而这个限制阈值就是通过这个两个方法来获取的。
可以看见,上面这些方法都是Canvas绘制相关的方法,没有啥悬念的,各种特性及注意事项都已经介绍了,用熟练就行了。
这里我们主要来看Canvas区域相关的方法怎么用(特别注意:这里所有区域切割相关的方法被调运后除过调用save、restore处理可以恢复原样Canvas以外该操作是不可逆的!),这些方法非常非常重要,因为合理的使用这些方法可以避免Android的Overdraw性能问题,具体不明白的看完这一小节的使用方法后建议再看下我的《Android应用开发性能优化完全分析》一文相关内容就彻底明白了;简单和没有坑的方法就不给出例子了,复杂或者有坑的会给出详细例子,具体如下:
clipPath(Path path)
clipPath(Path path, Region.Op op)
依据路径和区域方式切割画布。抗锯齿等属性操作需要Paint配合(特别注意:该方法开启硬件加速无效),Path路径的构成下面会详细介绍,这里我们重点关注一下Region.Op,在介绍它之前我们先看下Region类的介绍,具体如下:
/** Region区域构造方法详解: */
//构造方法,创建一个空区域。
public Region()
//构造方法,复制一个region区域。
public Region(Region region)
//构造方法,创建一个指定范围的矩形区域。
public Region(Rect r)
//构造方法,创建一个指定顶点的矩形区域。
public Region(int left, int top, int right, int bottom)
/**
Region区域set方法详解:
注意:调运set系列方法后原来Region的区域范围会被该set冲掉。
*/
//设空Region区域,类似reset操作。
public void setEmpty()
//用新区域来填充原来的区域。
public boolean set(Region region)
//利用矩形区域填充区域。
public boolean set(Rect r)
//利用矩形区域填充区域。
public boolean set(int left, int top, int right, int bottom)
//根据路径的区域与某区域的交集构造出新的不规则区域。
public boolean setPath(Path path, Region clip)
/**
Region区域判断方法详解:
*/
//判断该区域是否为空
public native boolean isEmpty()
//判断区域是否是一个矩阵
public native boolean isRect()
//判断区域是否是多个矩阵组合
public native boolean isComplex()
/**
Region区域边界获取方法详解:
*/
//各种方式的边界获取
public Rect getBounds()
public boolean getBounds(Rect r)
public Path getBoundaryPath()
public boolean getBoundaryPath(Path path)
/**
Region区域是否包含某点和是否相交判断方法详解:
*/
//是否包含某点
public native boolean contains(int x, int y)
//是否包含某矩阵
public boolean quickContains(Rect r)
//是否没有包含某矩阵
public native boolean quickContains(int left, int top, int right, int bottom)
//是否没和该矩阵相交
public boolean quickReject(Rect r)
public native boolean quickReject(int left, int top, int right, int bottom)
public native boolean quickReject(Region rgn)
/**
Region区域平移变换方法详解:
*/
public void translate(int dx, int dy)
public native void translate(int dx, int dy, Region dst)
/**
Region区域组合方法详解:
*/
//各种组合方法,都与Op有关,该Op下面有介绍
public final boolean union(Rect r)
public boolean op(Rect r, Op op) {
public boolean op(int left, int top, int right, int bottom, Op op)
public boolean op(Region region, Op op)
public boolean op(Rect rect, Region region, Op op)
可以看见,Region区域类的方法都很容易理解与使用,只是最后有一个Region.Op的参数还没解释,该参数含义如下:
//Region.Op枚举介绍(我们假设用region1去和region2组合):
//最终组合区域为region1与region2不同的区域
Region.Op.DIFFERENCE
//最终组合区域为region1与region2相交的区域
Region.Op.INTERSECT
//最终组合区域为region1与region2组合在一起的所有区域
Region.Op.UNION
//最终组合区域为region1与region2相交以外的区域
Region.Op.XOR
//最终组合区域为region2与region1不同的区域
Region.Op.REVERSE_DIFFERENCE
//最终组合区域为region2的区域
Region.Op.REPLACE
验证实例就不介绍了,这就是标准的APIDemo,官方原来的ProcessBar我猜就是这么干的,还没来得及看源码。不过要注意:被该操作的canvas可以通过save和restore复原但不影响已经的效果。
clipRect(Rect rect, Region.Op op)
clipRect(RectF rect, Region.Op op)
clipRect(int left, int top, int right, int bottom)
clipRect(float left, float top, float right, float bottom)
clipRect(RectF rect)
clipRect(float left, float top, float right, float bottom, Region.Op op)
clipRect(Rect rect)
以矩形方式切割画布。抗锯齿等属性操作需要Paint配合,其他的和上面类似,已经介绍了,没必要再多说,只是同样注意被该操作的canvas可以通过save和restore复原但不影响已经的效果就行了。
clipRegion(Region region)
clipRegion(Region region, Region.Op op)
以Region的区域方式切割画布。抗锯齿等属性操作需要Paint配合,其他的和上面类似,已经介绍了,没必要再多说,只是同样注意被该操作的canvas可以通过save和restore复原但不影响已经的效果就行了。
getClipBounds()
getClipBounds(Rect bounds)
获取边界宽高等数据,第一种默认是整个Canvas的范围,第二个是Rect指定范围的数据,这个没啥介绍的。
quickReject(float left, float top, float right, float bottom, Canvas.EdgeType type)
quickReject(Path path, Canvas.EdgeType type)
quickReject(RectF rect, Canvas.EdgeType type)
判断是否没和某个指定区域相交,用来作为重复优化判断。这个其实在Region中也有类似方法,只是这里涉及Canvas.EdgeType介绍,如下:
//Canvas.EdgeType值介绍:
//考虑抗锯齿时边缘处类型四舍五入。
Canvas.EdgeType.AA
//边缘处类型为四舍五入最接近黑白即可。
Canvas.EdgeType.BW
这组方法在考虑过度绘制性能时比较常用。
上面这些方法在绘制叠加等处理和绘制性能等处理时十分有用,大家知道就行了。
这里我们主要来看Canvas变换与状态相关的方法怎么用,简单和没有坑的方法就不给出例子了,复杂或者有坑的会给出详细例子,具体如下:
translate(float dx, float dy)
平移画布,默认不可逆。参数解释如下:
//dx 水平平移距离,正数向右平移,负数向左平移。
//dy 垂直平移距离,正数向下平移,负数向上平移。
Canvas上平移超出屏幕范围的地方不会显示,上面也说了Canvas调运绘制相关方法其实都相当于图层的叠加,所以平移前后如果进行两次同样位置坐标的绘制其实得到的是两个图,因为两侧参考点坐标被平移了,而两次绘制却叠加处理了。
scale(float sx, float sy)
scale(float sx, float sy, float px, float py)
缩放画布,默认不可逆。参数解释如下:
//sx 水平伸缩比例。
//sy 垂直伸缩比例。
//px 水平伸缩参考点,注意:没有该参数的默认为Canvas的0点。
//py 垂直伸缩参考点,注意:没有该参数的默认为Canvas的0点。
这和上面平移类似,不多说。
rotate(float degrees)
rotate(float degrees, float px, float py)
旋转画布,默认不可逆。参数解释如下:
//degrees 旋转度数,正数顺时针旋转,负数逆时针旋转。
//px 旋转中心x坐标,特别注意:没有该参数的默认为Canvas的0点。
//py 旋转中心y坐标,特别注意:没有该参数的默认为Canvas的0点。
skew(float sx, float sy)
斜拉画布,默认不可逆。参数解释如下:
//sx x方向倾斜的角度,sx为倾斜角度的tan值。
//sy y方向倾斜的角度,sy为倾斜角度的tan值。
concat(Matrix matrix)
getMatrix(Matrix ctm)
getMatrix()
setMatrix(Matrix matrix)
可以看见,这些方法也都很基础,没啥特别介绍的。
这玩意算是Canvas的重点,使用率非常高,地图层级等玩意都是这么来的,具体介绍使用前还是先看一张图片,如下:
怎么样?够形象了吧,相信结合小标题你已经领悟该图和我们Canvas的各种绘制方法的关系了吧(其实这个层级概念上面Canvas变换小节已经提到过了)。
save()
save(int saveFlags)
getSaveCount()
保存Canvas到栈和获取栈中存在多少次保存(前面不可逆操作前都应该做此操作),特别注意他们返回值的含义,可能和你想的有差距。返回值可以传入到restoreToCount()方法,以返回到某个save状态之前。特别注意有参数的save方法的参数,它用来表示当restore的时候哪些参数需要还原,参数定义在Canvas中。
譬如先通过save方法保存目前Canvas的位置,然后旋转移动后画一些图形,画完后调用restore方法返回到刚才保存的位置。具体参见上图,很直观了。
restore()
restoreToCount(int saveCount)
恢复Canvas到之前状态(恢复到前面不可逆操作前)。具体参见上图,很直观了。
saveLayer(RectF bounds, Paint paint, int saveFlags)
saveLayer(RectF bounds, Paint paint)
saveLayer(float left, float top, float right, float bottom, Paint paint)
saveLayer(float left, float top, float right, float bottom, Paint paint, int saveFlags)
saveLayerAlpha(RectF bounds, int alpha, int saveFlags)
saveLayerAlpha(RectF bounds, int alpha)
saveLayerAlpha(float left, float top, float right, float bottom, int alpha, int saveFlags)
saveLayerAlpha(float left, float top, float right, float bottom, int alpha)
和save()类似,不过这组方法具备离屏缓冲能力,也就是说通过它save后相当于新起一个offscreen bitmap,当我们在该offscreen bitmap上绘制一堆后调运restore方法时才会将我们在offscreen bitmap上绘制的东西画回Canvas上。这组方法使用要特别小心,一般场景下使用它的性能代价是比较高的,因为从Android3.0开始我们可以通View的setLayerType()方法来控制layer了,该方法模式如下:
//LAYER_TYPE_NONE View以一般方式绘制,不使用离屏缓冲,默认方式。
//LAYER_TYPE_HARDWARE View如果开启了硬件加速则会被绘制到一个硬件纹理中,否则类同LAYER_TYPE_SOFTWARE模式。
//LAYER_TYPE_SOFTWARE View被绘制到一个Bitmap中。
所以我们在直接或者间接使用saveLayerXXX()相关方法时务必注意给View选择合适的LayerType,否则会造成性能问题,因为开启硬件加速模式下Canvas的drawXXX方法在Layer(offscreen bitmap)上的绘制操作效果是在代码执行View的invalidate()方法时生效的,所以当我们做一些动画或者渐变操作时如果开启硬件加速使用GPU来操作是非常高效的操作,可以避免不必要的绘制次数。
所以这里给出和我之前Android性能优化详解博文一样的建议:
当我们自定义View或者绘制通过已存在View进行动画或者渐变等特效操作时建议通过View的setLayerType()方法设置为View.LAYER_TYPE_HARDWARE,操作完成后再设置为View.LAYER_TYPE_NONE,因为这样可以提升绘制效率,不会再次进行无用绘制。特别像多次改变View的alpha、x、y、translationX、translationY、scaleX、scaleY、pivotX、pivotY、rotation、rotationX、rotationY等属性值时其实就是间接操作layer,所以你会觉得动效卡顿。
到此关于Canvas的东西全部就介绍OK了,还是老话,实战熟练大于一切,其实这些方法都不难,组合起来确实十分的牛逼强大。
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】
提到Path相关攻略其实更加简单,它只能算是一种辅助工具类吧,关于PathEffect的效果前面已经有介绍了,这里我们只是单纯的针对Path来进行总结(简单的一笔带过,有坑和难以理解的会给出说明),如下:
public Path()
public Path(Path src)
set(Path src)
构造或者设置一个Path对象。
moveTo(float x, float y)
路径绘制的起点,从点(x, y)开始进行绘制。
lineTo(float x, float y)
直线的结束点,又是下一次绘制直线路径的开始点,lineTo()方法可以一直用。
rMoveTo(float dx, float dy)
在绘制线终点的基础上移动(dx, dy)得到新的起始点。
rLineTo(float dx, float dy)
与lineTo功能类似,但这个方法是在上个线的终点基础上进行绘制,不是(0, 0)点开始。
close()
将连续几条直线首尾点连接形成闭环。
addArc(RectF oval, float startAngle, float sweepAngle)
addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle)
addCircle(float x, float y, float radius, Path.Direction dir)
addOval(float left, float top, float right, float bottom, Path.Direction dir)
addOval(RectF oval, Path.Direction dir)
addRect(float left, float top, float right, float bottom, Path.Direction dir)
addRect(RectF rect, Path.Direction dir)
addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Path.Direction dir)
addRoundRect(float left, float top, float right, float bottom, float[] radii, Path.Direction dir)
addRoundRect(RectF rect, float[] radii, Path.Direction dir)
addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)
arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)
arcTo(RectF oval, float startAngle, float sweepAngle)
arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
类似Canvas的绘制图形方法,一堆图形路径的方法,大多数构造参数和前面Canvas的类似,所以这里不多说了,唯一不同的是Path.Direction参数,该参数取值如下:
//Path.Direction.CCW 创建逆时针方向的图形路径。
//Path.Direction.CW 创建顺时针方向的矩形路径。
Path.Direction的设置主要用在一些动画路径的方向上,单纯的绘制Path的Line是看不出来的。
offset(float dx, float dy, Path dst)
offset(float dx, float dy)
移动Path或者指定的Path dst间距为dx、dy。
reset()
清空Path路径中的信息。
rewind()
清除所有直线、曲线,但是保留内部数据结构,以便更好的重新使用。
setFillType(Path.FillType ft)
getFillType()
设置与获取Path的填充模式。填充模式FillType有四个枚举值,具体如下,使用和Paint的类似:
//FillType.WINDING 默认值,当两个图形相交则正常相交情况显示。
//FillType.EVEN_ODD 取Path所在且不相交的区域。
//FillType.INVERSE_WINDING 取Path的外部区域。
//FillType.INVERSE_EVEN_ODD 取Path外部和相交的区域。
哎呀,比较简单,自己试试吧。
isInverseFillType()
是否逆填充模式,主要针对setFillType的FillType.INVERSE_XXX参数判断。
toggleInverseFillType()
切换FillType逆状态。
isEmpty()
判断Path是否为空,如果Path不包含任何线条和曲线则返回true,否则返回false。
isRect(RectF rect)
判断Path指定的是否一个Rect,如果Path指定的是一个Rect则返回true,否则返回false,如果返回true且rect参数不为null则将该rect参数设置为Path的区域。
computeBounds(RectF bounds, boolean exact)
计算Path所在区域并将结果写入bounds,如果整个Path只包含0或者1个点则返回(0, 0, 0, 0)。exact参数现在没意义了,随意传值。
incReserve(int extraPtCount)
提示路径准备加入更多的点,可以让Path对象更有效地分配存储,差不多了就可以调用一把。
setLastPoint(float dx, float dy)
设置当前路径的最后一个点。
addPath(Path src, float dx, float dy)
addPath(Path src)
addPath(Path src, Matrix matrix)
追加路径,src为另一个已经存在的路径,追加到该路径中来。第一个方法将路径src平移dx、dy后拼接,第三个方法将路径src进行矩阵变换后添加。
transform(Matrix matrix, Path dst)
transform(Matrix matrix)
将Path进行matrix矩阵变换后保存到dst中,如果dst为null则将结果保存到当前Path中。
quadTo(float x1, float y1, float x2, float y2)
二阶贝塞尔曲线,只有一个控制点,(x1, y1)是控制点,(x2, y2)是结束点;控制点就像下图的P1坐标;如果没有使用moveTo指定起点,起点默认为(0, 0),即(0, 0)到(x1, y1)之间绘制贝塞尔曲线,(x1, y1)到(x2, y2)之间绘制贝塞尔曲线。
例子如下:
//平滑曲线
mPath.quadTo(100,500,300,800);
canvas.drawPath(mPath,mPaint);
rQuadTo(float dx1, float dy1, float dx2, float dy2)
和上面quadTo一样,只是追在屁股后面,比较简单,不多说了。
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
三阶贝塞尔曲线,有两个控制点,(x1, y1)是第一控制点,(x2, y2)是第二控制点,(x3, y3)是结束点;控制点就像下图的P1、P2坐标。
例子如下:
//平滑曲线
mPath.cubicTo(100, 80, 200, 200, 300, 300);
canvas.drawPath(mPath,mPaint);
rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
和上面CubicTo一样,只是追在屁股后面,比较简单,不多说了。
可以看见,Path其实就是一个工具类,一个用来封装路径数值的工具类,单独的Path没啥技术含量的,用熟就行。
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】
Paint也吹了、Canvas也玩了、Path也搞了,是不是这时候你已经觉得自己渐渐揭开了Android控件绘制的一些面纱呢?哈哈,其实是远远不够的,不过掌握上面这些对于百分之七十的场合是够用的了,尤其是Path可以做出很炫的动画。为了对上面Path再来一个升级版,我们有必要再罗嗦几句说说PathMeasure这个牛叉的辅助类,因为不管多复杂的Path对象在PathMeasure看来就是一条线,所以它能很方便的在Path上取位置计算对应坐标。具体如下:
PathMeasure()
PathMeasure(Path path, boolean forceClosed)
构造方法;一个构造空的PathMeasure对象,一个通过一个已存在的path构造PathMeasure对象,forceClosed决定是否close()这个path。
setPath(Path path, boolean forceClosed)
配合PathMeasure()使用效果与PathMeasure(Path path, boolean forceClosed)一样。
getLength()
测量Path的长度。
isClosed()
判断轮廓路径是否是闭环的。
nextContour()
移动到下一个轮廓,如果你的Path是由多个轮廓组成的话,那么就可以使用这个方法,有下一个轮廓存在就返回true。
getMatrix(float distance, Matrix matrix, int flags)
getPosTan(float distance, float[] pos, float[] tan)
传入一个距离distance(0<=distance<=getLength()),然后该方法会自动计算当前距离的坐标点和切线值存入pos与tan。
getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
传入一个开始和结束距离(0..getLength()),然后会返回介于这之间的Path值dst,最后那个参数决定开始距离是否从MoveTo坐标为参照物。
好了,PathMeasure略显抽象,这里给出一段核心例子代码吧,如下:
public void goPathAnim(long duration) {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
valueAnimator.setDuration(duration);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
// 获取当前Path上指定距离的坐标点到mCurrentPosition后供onDraw()方法使用
mPathMeasure.getPosTan(value, mCurrentPosition, null);
postInvalidate();
}
});
valueAnimator.start();
}
通过上面核心代码就能实现一个沿着路径以动画方式移动的动效。
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】
关于自定义View绘制方法相关的介绍基本就只有上面这些,通过上面这些对弈基本绘制也就完全KO了,结合我其他的几篇博文相信你能完全完爆Android自定义了,祝你好运:
完爆Android自定义中坐标知识原理参考我的博文《Android应用坐标系统全面详解》。
完爆Android自定义中相关触摸滑动工具类原理参考我的博文《Android应用开发Scroller详解及源码浅析》和《Android应用开发之自定义View触摸相关工具类全解》及《Android应用ViewDragHelper详解及部分源码浅析》。
完爆Android自定义中动画参考我的博文《Android应用开发之所有动画使用详解》。
哎呀,矫情一把,完爆Android应用层开发View、事件机制、性能优化、窗口原理参考我的博客!!!!!
最后说明:关于本文的例子还在构思编写中,准备和《Android应用开发之所有动画使用详解》一文例子一起编写(因为之前很多人要求动画一文配上实战),编写OK后会附带一篇分析文章的,敬请期待。
最后说一句,你的鼓励是我最大的动力,TT22222,哈哈!