(4.1.36)android Graphics 图形学解析

其实在写这篇文章的时候,我一直很犹豫是按照学习顺序进行布局,还是按照类型进行布局。最终我还是选择了按照类型进行布局,因此在顺序阅读上可能会存在一定的难度,如果实在觉得很难理解的部分,可以先自行跳过
本篇内容基本都来自参考文献,有兴趣的可自行阅读查看

一、概述

像我们平时画图一样,需要两个工具,纸和笔。Android的自定义View同样需要这两样东西,其中Paint就是相当于笔,而Canvas就是纸,这里叫画布,画布由layout所设置的绘制矩阵决定画布的大小。

凡有跟要要画的东西的设置相关的,比如大小,粗细,画笔颜色,透明度,字体的样式等等,都是在Paint里设置;同样,凡是要画出成品的东西,比如圆形,矩形,文字等相关的都是在Canvas里生成

二、画笔Paint

2.1 操作函数

  • reset() 重置画笔

2.2 基本设置函数

  • setAntiAlias(boolean aa) 设置画笔是否抗锯齿

  • setAlpha(int a) 设置画笔透明度

  • setColor(int color) 设置画笔颜色

  • setARGB(int a, int r, int g, int b) 设置画笔颜色,利用ARGB分开设置

  • setStrokeWidth(float width) 设置画笔宽度

  • setStyle(Paint.Style style) 设置画笔样式,取值有

    • Paint.Style.FILL :填充内部
    • Paint.Style.FILL_AND_STROKE :填充内部和描边
    • Paint.Style.STROKE :仅描边
      这里写图片描述
  • setShadowLayer (float radius, float dx, float dy, int color) 添加阴影

    • radius:阴影的倾斜度
    • dx:水平位移
    • dy:垂直位移
paint.setShadowLayer(10, 15, 15, Color.GREEN);//设置阴影 

这里写图片描述

  • setStrokeCap(Paint.Cap cap) 设置线冒样式

    • 取值有Cap.ROUND(圆形线冒)、Cap.SQUARE(方形线冒)、Paint.Cap.BUTT(无线冒)
      这里写图片描述
  • setStrokeJoin(Paint.Join join)设置线段连接处样式

    • 取值有:Join.MITER(结合处为锐角)、Join.Round(结合处为圆弧)、Join.BEVEL(结合处为直线)
      这里写图片描述

2.2 PathEffect路径样式相关

以下针对于 drawPath 时起作用

设置路径样式;取值类型是所有派生自PathEffect的子类。在drawPath前调用,可以将一些样式赋值给路径

paint.setPathEffect(new XXXPathEffect(。。。));  
canvas.drawPath(path,paint)

2.2.1 CornerPathEffect圆形拐角效果

它的作用就是将原来Path生硬的直线拐角,变成圆形拐角:

//只有一个参数radius:即当前连接两条直线所使用的圆的半径
public CornerPathEffect(float radius)  

这里写图片描述这里写图片描述

2.2.2 DashPathEffect虚线效果

这里写图片描述

public DashPathEffect(float intervals[], float phase)  
  • intervals[]:表示组成虚线的各个线段的长度;整条虚线就是由intervals[]中这些基本线段循环组成的
    • 长度必须大于等于2;因为必须有一个实线段和一个空线段来组成虚线
    • 个数必须为偶数,如果是基数,最后一个数字将被忽略;这个很好理解,因为一组虚线的组成必然是一个实线和一个空线成对组成的
    • 比如,我们定义new float[] {20,10};那这个虚线段就是由两段线段组成的,第一个可见的线段长为20,每二个线段不可见,长度为10;
  • phase:开始绘制的偏移值

2.2.3 DiscretePathEffect离散路径效果

这里写图片描述

DiscretePathEffect就是将原来路径分隔成定长的线段,然后将每条线段随机偏移一段位置,我们可以用它来模拟一种类似生锈铁丝的效果

public DiscretePathEffect(float segmentLength, float deviation)  
  • 第一个参数segmentLength:表示将原来的路径切成多长的线段。如果值为2,那么这个路径就会被切成一段段由长度为2的小线段。所以这个值越小,所切成的小线段越多;这个值越大,所切成的小线段越少。
  • 第二参数deviation:表示被切成的每个小线段的可偏移距离。值越大,就表示每个线段的可偏移距离就越大,就显得越凌乱,值越小,每个线段的可偏移原位置的距离就越小

2.2.4 PathDashPathEffect印章路径效果

这里写图片描述
以另一个路径图案做为印章,沿着指定路径一个个盖上去

public PathDashPathEffect(Path shape, float advance, float phase,Style style)   
  • Path shape:表示印章路径,比如我们下面示例中的三角形加右上角一个点;
  • float advance:表示两个印章路径间的距离,很容易理解,印章间距离越大,间距就越大。
  • float phase:路径绘制偏移距离,与上面DashPathEffect中的float phase参数意义相同
  • Style style:表示在遇到转角时,如何操作印章以使转角平滑过渡
    • 取值有:Style.ROTATE,Style.MORPH,Style.TRANSLATE;
      Style.ROTATE表示通过旋转印章来过渡转角
      Style.MORPH表示通过变形印章来过渡转角
      Style.TRANSLATE表示通过位移来过渡转角

2.2.5 ComposePathEffect与SumPathEffect 合并效果

这里写图片描述

public ComposePathEffect(PathEffect outerpe, PathEffect innerpe)  

ComposePathEffect合并两个特效是有先后顺序的,它会先将第二个参数的PathEffect innerpe的特效作用于路径上,然后再在此加了特效的路径上作用第一个参数

public SumPathEffect(PathEffect first, PathEffect second)  

SumPathEffect是分别对原始路径分别作用第一个特效和第二个特效。然后再将这两条路径合并,做为最终结果

2.3 文字相关设置

以下针对于 drawText 时起作用

2.3.1 基本设置

  • setTextAlign(Align.CENTER)
    设置文字对齐方式,也就是文字基线上基准点位于文字区域矩形的位置
    取值:align.CENTER、align.LEFT或align.RIGHT
  • setTextSize(12) 设置文本字体大小
  • setTypeface 设置或清除字体样式
  • setFakeBoldText(true) 设置是否为粗体文字
  • setUnderlineText(true) 设置下划线
  • setStrikeThruText(true) 设置带有删除线效果
  • setTextSkewX((float) -0.25) 设置字体水平倾斜度,普通斜体字是-0.25
  • setTextScaleX(2) 只会将水平方向拉伸,高度不会变
  • measureText 测量文本大小(注意,请在设置完文本各项参数后调用)

2.3.2 setSubpixelText亚像素显示

表示是否打开亚像素设置来绘制文本。

亚像素就是把两个相邻的两个像素之间的距离再细分,再插入一些像素,这些通过程序加入的像素就是亚像素。在两个像素间插入的像素个数是通过程序计算出来的,一般是插入两个、三个或四个。
所以打开亚像素显示,是可以在增强文本显示清晰度的,但由于插入亚像素是通过程序计算而来的,所以会耗费一定的计算机性能。注意:亚像素是通过程序计算出来模拟插入的,在没有改变硬件构造的情况下,来改善屏幕分辨率大小。

2.4 测量相关

2.4.1 measureText测量文本大小

(注意,请在设置完文本各项参数后调用)

measureText(String text)  

2.4.2 breakText测量文本大小

breakText(char[] text, int index, int count, float maxWidth, float[] measuredWidth)  

2.5 图像图层相关

以下主要针对于 drawBitmap或图层对象

2.5.1 ColorFilter色彩滤镜效果

具体请参详:

public ColorFilter setColorFilter(ColorFilter filter)  

ColorFilter是一个空对象,其中派生以下几个子类:

  • ColorMatrixColorFilter 色彩矩阵颜色过滤器
ColorMatrixColorFilter(ColorMatrix matrix)  
ColorMatrixColorFilter(float[] array)  
  • LightingColorFilter 完成简单的完成色彩过滤和色彩增强功能
//muladd取值都是0xRRGGBB,分别对应R、G、B颜色,注意哦,这里是没有透明度A
//结果R值 = (r*mul.R+add.R)%255;  
//结果G值 = (g*mul.G+add.G)%255;  
//结果B值 = (b*mul.B+add.B)%255; 
public LightingColorFilter(int mul, int add)  
  • PorterDuffColorFilter图形混合滤镜
//int srcColor:0xAARRGGBB类型的颜色值。待混合的图层颜色
//PorterDuff.Mode mode:表示混合模式,枚举值有18个,表示各种图形混合模式
//主要有:Mode.ADD(饱和度相加),Mode.DARKEN(变暗),Mode.LIGHTEN(变亮),Mode.MULTIPLY(正片叠底),Mode.OVERLAY(叠加),Mode.SCREEN(滤色) 
public PorterDuffColorFilter(int srcColor, PorterDuff.Mode mode)  

2.5.2 Xfermode 图层混合模式

先看个示例:

int layerID = canvas.saveLayer(0,0,width*2,height*2,mPaint,Canvas.ALL_SAVE_FLAG);  

canvas.drawBitmap(dstBmp, 0, 0, mPaint);  
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));  
canvas.drawBitmap(srcBmp, width/2, height/2, mPaint);  
mPaint.setXfermode(null);  

canvas.restoreToCount(layerID);  

混合模式是作用在两次画布绘制之间的,也就是说在两个图层间做混合

具体请参详:

  • AvoidXfermode 替换模式
    • 将根据颜色所选定区域(正选或反选),替换为 新区域
    • 不支持硬件加速
//第一个参数opColor:一个16进制的AARRGGBB的颜色值; 
//第二个参数tolerance:表示容差。
//第三个参数mode:取值有两个Mode.TARGET正选和Mode.AVOID反选。当Mode取Mode.TARGET时,它的意义表示将opColor参数所指定的颜色替换成当前画笔的颜色
public AvoidXfermode(int opColor, int tolerance, Mode mode)  

容差是指与目标色所能容忍的最大颜色差异,所以容差越大,所覆盖的颜色区域就越大;所以当容差为0时,就表示只选择与目标色一模一样的颜色区域;当容差为100时,就表示与目标色值的颜色差异在100范围内的都是可以的;而由于最大的颜色差异是255,所以当我们的容差是255时,所有的颜色都将被选中

  • PixelXorXfermode

    • 不支持硬件加速
    • 过时了
  • PorterDuffXfermode 混合模式

    • 源图(后绘制的)SRC模式类型:优先显示原图
    • 目标图(先绘制的)DST模式类型:优先显示目标图
public PorterDuffXfermode(PorterDuff.Mode mode)  

2.5.3 setShadowLayer阴影效果 和 setMaskFilter发光浮雕效果

  • 阴影效果setShadowLayer
//float radius:意思是模糊半径,radius越大越模糊,越小越清晰,但是如果radius设置为0,则阴影消失不见;有关清除阴影的问题,下面我们会专门讲。
//float dx:阴影的横向偏移距离,正值向右偏移,负值向左偏移
//float dy:阴影的纵向偏移距离,正值向下偏移,负值向上偏移
//int color:绘制阴影的画笔颜色,即阴影的颜色(对图片阴影无效)
public void setShadowLayer(float radius, float dx, float dy, int color)  

setShadowLayer只有文字绘制阴影支持硬件加速,其它都不支持硬件加速,所以为了方便起见,我们需要在自定义控件中禁用硬件加速

  • 发光与浮雕效果 SetMaskFilter
public MaskFilter setMaskFilter(MaskFilter maskfilter)  
//float radius:用来定义模糊半径,同样是高斯模糊算法。
//Blur style:发光样式,有内发光、外发光、和内外发光,分别对应:Blur.INNER(内发光)、Blur.SOLID(外发光)、Blur.NORMAL(内外发光)、Blur.OUTER(仅发光部分可见)
public BlurMaskFilter(float radius, Blur style)  

SetMaskFilter 不支持硬件加速的,必须关闭硬件加速才可以

setMaskFilter中的MaskFilter也是没有具体实现的,也是通过派生子类来实现具体的不同功能的,MaskFilter有两个派生类BlurMaskFilter和EmbossMaskFilter,其中BlurMaskFilter就是我们这段要讲的实现发光效果的子类,而EmbossMaskFilter是用来实现浮雕效果的

2.5.4 着色器效果setShader

//Paint类中的方法
public Shader setShader(Shader shader)

Shader在三维软件中称之为着色器,就是用来给空白图形上色用的。在PhotoShop中有一个工具叫印章工具,我们能够指定印章的样式来填充图形。印章的样式可以是图像、颜色、渐变色等。这里的Shader实现的效果与印章类似。我们也是通过给Shader指定对应的图像、渐变色等来填充图形的

Shader类与ColorFiler一样,其实是一个空类,它的功能的实现,主要是靠它的派生类来实现的

  • BitmapShader图像着色
    • 这个就相当于PhotoShop中的图案印章工具,bitmap用来指定图案,tileX用来指定当X轴超出单个图片大小时时所使用的重复策略,同样tileY用于指定当Y轴超出单个图片大小时时所使用的重复策略
      TileMode.CLAMP:用边缘色彩填充多余空间
      TileMode.REPEAT:重复原图像来填充多余空间
      TileMode.MIRROR:重复使用镜像模式的图像来填充多余空间
public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY)
  • LinearGradient色彩线性渐变
public LinearGradient(float x0, float y0, float x1, float y1,int color0, int color1, TileMode tile)
  • RadialGradient色彩放射渐变
//两色渐变
RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode)
//多色渐变
RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode)

三、画布Canvas

操作类型相关API备注
绘制颜色drawColor, drawRGB, drawARGB使用单一颜色填充整个画布
绘制基本形状drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧
绘制图片drawBitmap, drawPicture绘制位图和图片
绘制文本drawText, drawPosText, drawTextOnPath依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字
绘制路径drawPath绘制路径,绘制贝塞尔曲线时也需要用到该函数
顶点操作drawVertices, drawBitmapMesh通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用
画布剪裁clipPath, clipRect设置画布的显示区域
画布快照save, restore, saveLayerXxx, restoreToCount, getSaveCount依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数
画布变换translate, scale, rotate, skew依次为 位移、缩放、 旋转、错切
Matrix(矩阵)getMatrix, setMatrix, concat实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。

PS: Canvas常用方法在上面表格中已经全部列出了,当然还存在一些其他的方法未列出,具体可以参考官方文档 Canvas

2.1 画布的由来

方法一的画布是最终会展示在屏幕上的画布,方法二的画布是为了进行多画布同时绘制最后合并的工作

2.1.2 最终展示画布:自定义view时, 重写onDraw、dispatchDraw方法

protected void onDraw(Canvas canvas) {  
    super.onDraw(canvas);  
}  

protected void dispatchDraw(Canvas canvas) {  
    super.dispatchDraw(canvas);  
}  

onDraw、dispatchDraw方法中的画布的坐标及大小,都是在View绘制阶段的layout阶段中被父布局指定,整个画布操作都是相对于该绘制矩阵的左上角开始,也就是说原点是当前控件绘制区域的左上角

这个canvas对象是View中的Canvas对象,利用这个canvas对象绘图,效果会直接反应在View中;

2.1.2 副本画布:使用Bitmap创建

先构建一个Bitmap对象,在据此图像对象生成画布对象

。需要注意的是:只有将该bitmap图像绘制到onDraw()所提供的画布上时,副本画布的内容才会显示在View上

//【步骤1】
//方法一:新建一个空白bitmap  
Bitmap bmp = Bitmap.createBitmap(width ,height Bitmap.Config.ARGB_8888);  
//方法二:从图片中加载  
Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.wave_bg,null);  

//【步骤2】
//方法一:
Canvas c = new Canvas(bitmap);  
//方法二:
Canvas c = new Canvas();   
c.setBitmap(bitmap);  

//【步骤3】
@Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  

        mPaint.setTextSize(100);  
        c.drawText("xx",0,100,mPaint);  

        canvas.drawBitmap(bitmap,0,0,mPaint);  

    }   

2.2 画布操作函数

在正式开始讲解画布操作函数之前,我们先聊一下几个有趣的想法

为什么需要画布操作函数?

假设一个需求是: 从坐标原点为起点,绘制一个长度为20dp,与水平线夹角为30度的线段。

我们想当然的做法自然是:先使用三角函数计算出线段结束点的坐标(0, 20 * sin(30)),然后调用drawLine即可

然而,实际上我们还有另一种实现方法:我们先绘制一个长度为20dp的水平线,然后将这条水平线旋转30度,则最终看起来效果是相同的,而且不用进行三角函数计算。

这样是否更加简单了一点呢?合理的使用画布操作可以帮助你用更容易理解的方式创作你想要的效果,这也是画布操作存在的原因PS: 所有的画布操作都只影响后续的绘制,对之前已经绘制过的内容没有影响。

我们也需要理解这句话:画布操作是针对图形绘制的一种补充协助手段,规格揭底是为了更方便的绘制图形

画布操作都是会叠加的

我们下文所讲解的位移、旋转、缩放、错切等操作,都是在原基础的进一步行动,而不是相对于原始0位置

什么是画布和图层?
在这里,我们必须了解下画布和图层的关系。实际上我们看到的画布是由多个图层构成的,所进行的绘制操作和画布操作都是在默认图层上进行的。在通常情况下,使用默认图层就可满足需求,但是如果需要绘制比较复杂的内容,如地图(地图可以有多个地图层叠加而成,比如:政区层,道路层,兴趣点层)等,则分图层绘制比较好一些

2.2.1 位移

/**
*float dx:水平方向平移的距离,正数指向正方向(向右)平移的量,负数为向负方向(向左)平移的量
*flaot dy:垂直方向平移的距离,正数指向正方向(向下)平移的量,负数为向负方向(向上)平移的量
*/
translate(float dx, float dy)
  • 位移是坐标原点的移动。画布的原状是以左上角为原点,向左是X轴正方向,向下是Y轴正方向,translate函数其实实现的相当于平移坐标系,即平移坐标系的原点的位置
  • 位移是基于当前位置移动,而不是每次基于屏幕左上角的(0,0)点移动

2.2.2 缩放

从缩放点处缩放

/**
 * x轴和y轴的缩放比例,相对于
 */
public void scale (float sx, float sy)
/**
 * x轴和y轴的缩放比例
 * 缩放中心位置偏移:从该点出开始缩放
 */
public final void scale (float sx, float sy, float px, float py)
  • 我们可以将缩放认为是绘制密度的缩放,当绘制点坐标不变(假设从0,0到100,100),那么当密度变大时,绘制的点更多,但是屏幕密度一定,所以线会变长;当密度变小时,绘制的点更少,屏幕密度一定,所以线会变短。也就是说这个缩放其实是绘制图形缩放的正比
  • 缩放时是基于当前缩放尺寸的缩放

缩放比例(sx,sy)取值范围详解:

取值范围(n)说明
[-∞, -1)先根据缩放中心放大n倍,再根据中心轴进行翻转
-1根据缩放中心轴进行翻转
(-1, 0)先根据缩放中心缩小到n,再根据中心轴进行翻转
0不会显示,若sx为0,则宽度为0,不会显示,sy同理
(0, 1)根据缩放中心缩小到n
1没有变化
(1, +∞)根据缩放中心放大n倍

我们做一个简单的示例:

// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);

RectF rect = new RectF(0,-400,400,0);   // 矩形区域

mPaint.setColor(Color.BLACK);           // 绘制黑色矩形
canvas.drawRect(rect,mPaint);

canvas.scale(0.5f,0.5f);                // [1]画布缩放,相对于原点
//canvas.scale(-0.5f,-0.5f);           // [2]画布缩放,当缩放比例为负数的时候会根据缩放中心轴进行翻转
//canvas.scale(-0.5f,-0.5f,200,0);     // [3]画布缩放  <-- 缩放中心向右偏移了200个单位


mPaint.setColor(Color.BLUE);            // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);

这里写图片描述 这里写图片描述 这里写图片描述

脑洞时刻

// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);

RectF rect = new RectF(-400,-400,400,400);   // 矩形区域

for (int i=0; i<=20; i++)
{
    canvas.scale(0.9f,0.9f);
    canvas.drawRect(rect,mPaint);
}

这里写图片描述

2.2.3 旋转

从旋转点处旋转

/**
* 旋转的角度,默认的旋转中心是坐标原点
*/
public void rotate (float degrees)
/**
 *和缩放一样,第二种方法多出来的两个参数依旧是控制旋转中心点的
 * 从该点出开始旋转
*/
public final void rotate (float degrees, float px, float py)
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);

RectF rect = new RectF(0,-400,400,0);   // 矩形区域

mPaint.setColor(Color.BLACK);           // 绘制黑色矩形
canvas.drawRect(rect,mPaint);

canvas.rotate(180);                     // 旋转180度 <-- 默认旋转中心为原点
//canvas.rotate(180,200,0);             // 旋转180度 <-- 旋转中心向右偏移200个单位

mPaint.setColor(Color.BLUE);            // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);

这里写图片描述 这里写图片描述

脑洞时刻

// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);

canvas.drawCircle(0,0,400,mPaint);          // 绘制两个圆形
canvas.drawCircle(0,0,380,mPaint);

for (int i=0; i<=360; i+=10){               // 绘制圆形之间的连接线
   canvas.drawLine(0,380,0,400,mPaint);
   canvas.rotate(10);
}

这里写图片描述

2.2.4 错切

//float sx:将画布在x方向上倾斜相应的角度,sx倾斜角度的tan值
//float sy:将画布在y轴方向上倾斜相应的角度,sy为倾斜角度的tan值
public void skew (float sx, float sy)
    Paint paint_green = generatePaint(Color.GREEN, Style.STROKE, 5);  
    Paint paint_red   = generatePaint(Color.RED, Style.STROKE, 5);  

    Rect rect1 = new Rect(10,10,200,100);  

    canvas.drawRect(rect1, paint_green);  
    canvas.skew(1.732f,0);//X轴倾斜60度,Y轴不变  
    canvas.drawRect(rect1, paint_red);  

这里写图片描述

2.2.5 裁剪

裁剪画布是利用Clip系列函数,通过与Rect、Path、Region取交、并、差等集合运算来获得最新的画布形状。除了调用Save、Restore函数以外,这个操作是不可逆的,一但Canvas画布被裁剪,就不能再被恢复!

boolean clipPath(Path path)
boolean clipPath(Path path, Region.Op op)
boolean clipRect(Rect rect, Region.Op op)
boolean clipRect(RectF rect, Region.Op op)
boolean clipRect(int left, int top, int right, int bottom)
boolean clipRect(float left, float top, float right, float bottom)
boolean clipRect(RectF rect)
boolean clipRect(float left, float top, float right, float bottom, Region.Op op)
boolean clipRect(Rect rect)
boolean clipRegion(Region region)
boolean clipRegion(Region region, Region.Op op)

我们简单做个演示:先把背景色整个涂成红色。显示在屏幕上然后裁切画布,最后最新的画布整个涂成绿色。可见绿色部分,只有一小块,而不再是整个屏幕了

 canvas.drawColor(Color.RED);  
    canvas.clipRect(new Rect(100, 100, 200, 200));  
    canvas.drawColor(Color.GREEN);  

这里写图片描述

2.2.6 状态快照与回滚

为什存在快照与回滚操作?

画布的操作是默认叠加的,而且很多画布操作会影响后续的步骤(例如一旦我们将坐标原点移动到了中心,那么以后的绘制都是在中心开始的)。然而,现实中我们可能需要快捷的将画布状态重设回某个之前的阶段,所以会对画布的一些状态进行保存和回滚(只是状态回滚,之前画了的东西还是会有的,相当于已经绘制到屏幕上了)

  • save() 把当前的整屏画布状态进行保存,然后放入特定的栈中
  • restore() 把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布
  • restoreToCount(int) 弹出指定位置及其以上所有的状态,并按照指定位置的状态进行恢复
  • getSaveCount() 获取保存的次数,即状态栈中保存状态的数量。
    • 该函数的最小返回值为1,即使弹出了所有的状态,返回值依旧为1,代表默认状态
//当前画布状态A
save();      //保存状态:将当前状态A入栈
...          //canvas画布的一系列操作
restore();   //回滚到之前的状态:状态A出栈,并恢复为该状态下的画布状态
  • save 提供了带参数的使用方法,使用这个参数可以只保存一部分状态,更加灵活,这个saveFlags参数具体可参考上面表格中的内容
// 根据saveFlags参数保存一部分状态
public int save (int saveFlags)
名称简介
ALL_SAVE_FLAG默认,保存全部状态
CLIP_SAVE_FLAG保存剪辑区
CLIP_TO_LAYER_SAVE_FLAG剪裁区作为图层保存
FULL_COLOR_LAYER_SAVE_FLAG保存图层的全部色彩通道
HAS_ALPHA_LAYER_SAVE_FLAG保存图层的alpha(不透明度)通道
MATRIX_SAVE_FLAG保存Matrix信息( translate, rotate, scale, skew)

2.2.7 saveLayerXxx图层操作

注意:saveLayerXxx方法会让你花费更多的时间去渲染图像(图层多了相互之间叠加会导致计算量成倍增长),使用前请谨慎,如果可能,尽量避免使用

目标:新建一个图层,并放入特定的栈中

// 无图层alpha(不透明度)通道
public int saveLayer (RectF bounds, Paint paint)
public int saveLayer (RectF bounds, Paint paint, int saveFlags)
public int saveLayer (float left, float top, float right, float bottom, Paint paint)
public int saveLayer (float left, float top, float right, float bottom, Paint paint, int saveFlags)

// 有图层alpha(不透明度)通道
public int saveLayerAlpha (RectF bounds, int alpha)
public int saveLayerAlpha (RectF bounds, int alpha, int saveFlags)
public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha)
public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha, int saveFlags)

更多内容参看《自定义控件三部曲之绘图篇(十三)——Canvas与图层(一)》《自定义控件三部曲之绘图篇(十四)——Canvas与图层(二)》

2.3 绘制颜色与基本形状

2.3.1 颜色

  • drawRGB(int r, int g, int b)
  • drawColor(int color)
  • drawARGB(int a, int r, int g, int b)

2.3.2 点

  • void drawPoint (float x, float y, Paint paint) 绘制一个点
  • void drawPoints (float[] pts, Paint paint) 绘制多个点

  • void drawPoints (float[] pts, int offset, int count, Paint paint) 选择性绘制多个点

    • float[] pts:点的合集,与上面直线一直,样式为{x1,y1,x2,y2,x3,y3,……}
    • int offset:集合中跳过的数值个数,注意不是点的个数!一个点是两个数值;
    • count:参与绘制的数值的个数,指pts[]里人数值个数,而不是点的个数,因为一个点是两个数值
//(10,10)、(100,100),(200,200),(400,400)
float []pts={10,10,100,100,200,200,400,400};  
//跳过前2个值,也就是说跳过第一个点
//绘制参与4个数值,也就是仅画出第二、三个点
canvas.drawPoints(pts, 2, 4, paint);  

2.3.3 线

  • void drawLine (float startX, float startY, float stopX, float stopY, Paint paint)

  • void drawLines (float[] pts, Paint paint) // 绘制一组线 每四数字(两个点的坐标)确定一条线,组与组之间不连线

    • pts的组织方式为{x1,y1,x2,y2,x3,y3,……}
  • void drawLines (float[] pts, int offset, int count, Paint paint) 选择性绘制直线

    • float[] pts:点的合集,与上面点一直,样式为{x1,y1,x2,y2,x3,y3,……}
    • int offset:集合中跳过的数值个数,注意不是点的个数!一个点是两个数值;
    • count:参与绘制的数值的个数,指pts[]里人数值个数,而不是点的个数,因为一个点是两个数值
//(10,10)、(100,100) :第一条线
//(200,200),(400,400):第二条线
float []pts={10,10,100,100,200,200,400,400};  
canvas.drawLines(pts, paint);  

2.3.4 矩形

确定确定一个矩形最少需要四个数据,就是对角线的两个点的坐标值,这里一般采用左上角和右下角的两个点的坐标。

关于绘制矩形,Canvas提供了三种重载方法,第一种就是提供四个数值(矩形左上角和右下角两个点的坐标)来确定一个矩形进行绘制。 其余两种是先将矩形封装为Rect或RectF(实际上仍然是用两个坐标点来确定的矩形),然后传递给Canvas绘制:

  • void drawRect (float left, float top, float right, float bottom, Paint paint)
  • void drawRect (RectF rect, Paint paint) 直接传入矩形的四个点,画出矩形
  • void drawRect (Rect r, Paint paint) 根据传入RectF或者Rect矩形变量来指定所画的矩形的
//以下三种方法所绘制出来的结果是完全一样的
// 第一种
canvas.drawRect(100,100,800,400,mPaint);

// 第二种
Rect rect = new Rect(100,100,800,400);
canvas.drawRect(rect,mPaint);

// 第三种
RectF rectF = new RectF(100,100,800,400);
canvas.drawRect(rectF,mPaint);

看到这里,相信很多观众会产生一个疑问,为什么会有Rect和RectF两种?两者有什么区别吗?

答案当然是存在区别的,两者最大的区别就是精度不同,Rect是int(整形)的,而RectF是float(单精度浮点型)的。除了精度不同,两种提供的方法也稍微存在差别,在这里我们暂时无需关注,想了解更多参见官方文档 RectRectF

2.3.5 圆角矩形

同样与矩形的绘制方式,圆角矩形的绘制方式,也是两种:

  • drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,Paint paint)
  • drawRoundRect(@NonNull RectF rect, float rx, float ry, Paint paint)
    • float rx:生成圆角的椭圆的X轴半径
    • float ry:生成圆角的椭圆的Y轴半径
// 第一种
RectF rectF = new RectF(100,100,800,400);
canvas.drawRoundRect(rectF,30,30,mPaint);

// 第二种
canvas.drawRoundRect(100,100,800,400,30,30,mPaint);

这里写图片描述

上面两种方法绘制效果也是一样的,但鉴于第二种方法在API21的时候才添加上,所以我们一般使用的都是第一种。

与矩形相比,圆角矩形多出来了两个参数rx 和 ry,这两个参数是干什么的呢?
确定一个圆形需要圆心+半径。由于矩形位置已经确定,所以其边角位置也是确定的,这就是圆心位置。而半径在这里却是x轴和y轴区分设置的
这里写图片描述
同样的道理,如果rx为宽度的一半,ry为高度的一半时,正好是椭圆

2.3.6 椭圆

相对于绘制圆角矩形,绘制椭圆就简单的多了。绘制椭圆实际上就是绘制一个矩形的内切图形,因此他只需要一个矩形作为参数:

PS: 如果你传递进来的是一个长宽相等的矩形(即正方形),那么绘制出来的实际上就是一个圆

  • void drawOval (RectF oval, Paint paint)
  • drawOval(float left, float top, float right, float bottom,Paint paint)
// 第一种
RectF rectF = new RectF(100,100,800,400);
canvas.drawOval(rectF,mPaint);

// 第二种
canvas.drawOval(100,100,800,400,mPaint);

同样,以上两种方法效果完全一样,但一般使用第一种。

2.3.7 圆

  • drawCircle (float cx, float cy, float radius, Paint paint)
    • float cx:圆心点X轴坐标
    • float cy:圆心点Y轴坐标
    • float radius:圆的半径
canvas.drawCircle(500,500,400,mPaint);  // 绘制一个圆心坐标在(500,500),半径为400 的圆。

2.3.8 圆弧

  • (4.1.6.2)角度与弧度

  • drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}

  • drawArc(float left, float top, float right, float bottom, float startAngle,float sweepAngle, boolean useCenter, @NonNull Paint paint)

    • float startAngle:弧开始的角度,以X轴正方向为0度
    • float sweepAngle:弧持续的角度
    • boolean useCenter:是否使用中心。使用了中心点之后绘制出来类似于一个扇形,而不使用中心点则是圆弧起始点和结束点之间的连线加上圆弧围成的图形
Paint paint=new Paint();  
paint.setColor(Color.RED);  //设置画笔颜色      
paint.setStyle(Style.STROKE);//填充样式改为描边   
//paint.setStyle(Style.FILL);//填充样式改为填充
paint.setStrokeWidth(5);//设置画笔宽度  

RectF rect1 = new RectF(100, 10, 300, 100);  
canvas.drawArc(rect1, 0, 90, true, paint);  

RectF rect2 = new RectF(400, 10, 600, 100);  
canvas.drawArc(rect2, 0, 90, false, paint); 

这里写图片描述
这里写图片描述

2.4 绘制图片

绘制有两种方法,drawPicture(矢量图) 和 drawBitmap(位图)

2.4.1 位图drawBitmap

2.4.1.1 获取Bitmap的方式
序号获取方式场景备注
1通过Bitmap创建复制一个已有的Bitmap(新Bitmap状态和原有的一致) 或者 创建一个空白的Bitmap(内容可改变)
2通过BitmapDrawable获取从资源文件 内存卡 网络等地方获取一张图片并转换为内容不可变的Bitmap不推荐使用
3通过BitmapFactory获取从资源文件 内存卡 网络等地方获取一张图片并转换为内容不可变的Bitmap

通过BitmapFactory从不同位置获取Bitmap:

//资源文件(drawable/mipmap/raw):
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),R.raw.bitmap);
//资源文件(assets):
Bitmap bitmap=null;
try {
    InputStream is = mContext.getAssets().open("bitmap.png");
    bitmap = BitmapFactory.decodeStream(is);
    is.close();
} catch (IOException e) {
    e.printStackTrace();
}
//内存卡文件:
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/bitmap.png");
//网络文件:
Bitmap bitmap = BitmapFactory.decodeStream(is);// 此处省略了获取网络输入流的代码
is.close();
2.4.1.1 绘制Bitmap
//第一种:数(matrix, paint)是在绘制的时候对图片进行一些改变
//只是需要将图片内容绘制出来:canvas.drawBitmap(bitmap,new Matrix(),new Paint());
public void drawBitmap (Bitmap bitmap, Matrix matrix, Paint paint)
// 第二种:绘制时指定图片左上角的坐标(距离坐标原点的距离)
public void drawBitmap (Bitmap bitmap, float left, float top, Paint paint)
// 第三种
//Rect src  指定绘制图片的区域:把图片的哪一块区域画到屏幕上
//Rect dst 或RectF dst   指定图片在屏幕上显示(绘制)的区域:把图片画到屏幕的哪一块区域
public void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)

我们给出几个示例:


//1
canvas.drawBitmap(bitmap,new Matrix(),new Paint());

//2
canvas.drawBitmap(bitmap,200,500,new Paint());

//3
// 将画布坐标系移动到画布中央
canvas.translate(mWidth/2,mHeight/2);
// 指定图片绘制区域(左上角的四分之一)
Rect src = new Rect(0,0,bitmap.getWidth()/2,bitmap.getHeight()/2);
// 指定图片在屏幕上显示的区域
Rect dst = new Rect(0,0,200,400);
// 绘制图片
canvas.drawBitmap(bitmap,src,dst,null);

这里写图片描述

第三种方法可以用来实现动画,在一张集成图上,只绘制一部分,具体可查看原文或者原文示例
这里写图片描述

2.4.2 矢量图drawPicture

使用Picture前请关闭硬件加速,以免引起不必要的问题!
使用Picture前请关闭硬件加速,以免引起不必要的问题!
使用Picture前请关闭硬件加速,以免引起不必要的问题!

在AndroidMenifest文件中application节点下添上 android:hardwareAccelerated=”false”以关闭整个应用的硬件加速。
更多请参考这里:Android的硬件加速及可能导致的问题

以把Picture看作是一个录制Canvas操作的录像机,我们把Canvas绘制点,线,矩形等诸多操作用Picture录制下来,下次需要的时候拿来就能用。

使用Picture相比于再次调用绘图API,开销是比较小的,也就是说对于重复的操作可以更加省时省力

相关方法简介
public int getWidth ()获取宽度
public int getHeight ()获取高度
public Canvas beginRecording (int width, int height)开始录制 (返回一个Canvas,在Canvas中所有的绘制都会存储在Picture中)
public void endRecording ()结束录制
public void draw (Canvas canvas)将Picture中内容绘制到Canvas中
public static Picture createFromStream (InputStream stream)(已废弃)通过输入流创建一个Picture
public void writeToStream (OutputStream stream) (已废弃)将Picture中内容写出到输出流中

上面表格中基本上已经列出了Picture的所有方法,其中getWidth和getHeight没什么好说的,最后两个已经废弃也自然就不用关注了,排除了这些方法之后,只剩三个方法了,接下来我们就比较详细的了解一下

2.4.2.1 录制

很明显,beginRecording 和 endRecording 是成对使用的,一个开始录制,一个是结束录制,两者之间的操作将会存储在Picture中

录制内容,即将一些Canvas操作用Picture存储起来,录制的内容是不会直接显示在屏幕上的,只是存储起来了而已

// 1.创建Picture
private Picture mPicture = new Picture();

---------------------------------------------------------------

// 2.录制内容方法
private void recording() {
    // 开始录制 (接收返回值Canvas)
    Canvas canvas = mPicture.beginRecording(500, 500);
    // 创建一个画笔
    Paint paint = new Paint();
    paint.setColor(Color.BLUE);
    paint.setStyle(Paint.Style.FILL);

    // 在Canvas中具体操作
    // 位移
    canvas.translate(250,250);
    // 绘制一个圆
    canvas.drawCircle(0,0,100,paint);

    mPicture.endRecording();
}

---------------------------------------------------------------

// 3.在使用前调用(我在构造函数中调用了)
  public Canvas3(Context context, AttributeSet attrs) {
    super(context, attrs);

    recording();    // 调用录制
}
2.4.2.2 绘制

由于录制的内容不会直接显示,就像存储的视频不点击播放不会自动播放一样,同样,想要将Picture中的内容显示出来就需要手动调用播放(绘制),将Picture中的内容绘制出来可以有以下几种方法:

序号简介备注
1使用Picture提供的draw方法绘制。这种方法在比较低版本的系统上绘制后可能会影响Canvas状态,一般不会使用
2使用Canvas提供的drawPicture方法绘制。
3将Picture包装成为PictureDrawable,使用PictureDrawable的draw方法绘制。

以上几种方法主要区别:

主要区别分类简介
是否对Canvas有影响1有影响 2,3不影响此处指绘制完成后是否会影响Canvas的状态(Matrix clip等)
可操作性强弱1可操作性较弱 2,3可操作性较强此处的可操作性可以简单理解为对绘制结果可控程度。

我们给出相关的示例:

// 1
mPicture.draw(canvas);  
// 2
canvas.drawPicture(mPicture,new RectF(0,0,mPicture.getWidth(),200));
//3
// 包装成为Drawable
PictureDrawable drawable = new PictureDrawable(mPicture);
// 设置绘制区域 -- 注意此处所绘制的实际内容不会缩放
drawable.setBounds(0,0,250,mPicture.getHeight());
// 绘制
drawable.draw(canvas);

这里写图片描述

2.5 绘制文字

在开始讲解绘制文字函数之前,我们必须先了解下文字基准点和基准线的相关概念,系统api对文字的绘制默认都是从基准点开始,按照基准线进行绘制。(更多内容可以查看这里(4.1.36.5) android Graphics( 五):drawText()详解)

这里写图片描述

上图所示的基准点就是“h”的左下角,需要注意的是:绘制文本默认是在基准点的右侧,这是因为画笔Paint的文字对齐方式是LEFT导致的,这个可以根据setTextAlign做对应调整

// 第一类:仅指定文本基准点位置的绘制指定截取范围的文本(基线x默认在字符串左侧,基线y默认在字符串下方)
public void drawText (String text, float x, float y, Paint paint)
public void drawText (String text, int start, int end, float x, float y, Paint paint)
public void drawText (CharSequence text, int start, int end, float x, float y, Paint paint)
public void drawText (char[] text, int index, int count, float x, float y, Paint paint)

// 第二类:可以分别指定每个文字的基准点位置
// 一般不使用:必须指定所有字符位置,否则直接crash掉;性能不佳,在大量使用的时候可能导致卡顿;不支持emoji等特殊字符,不支持字形组合与分解
public void drawPosText (String text, float[] pos, Paint paint)
public void drawPosText (char[] text, int index, int count, float[] pos, Paint paint)

// 第三类:指定一个路径作为基线,根据路径绘制文字
//float hOffset  : 与路径起始点的水平偏移距离
//float vOffset  : 与路径中心的垂直偏移量
public void drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint)
public void drawTextOnPath (char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)

我们给出以上三种方法的调用示例:

//1:绘制BC,仅指定文本基准点位置
String str = "ABCDEFG";
// 参数分别为 (字符串 开始截取位置 结束截取位置 基线x 基线y 画笔)
canvas.drawText(str,1,3,200,500,textPaint);

//2 分别指定每个文字的基准点位置
String str = "SLOOP";
canvas.drawPosText(str,new float[]{
      100,100,    // 第一个字符位置
      200,200,    // 第二个字符位置
      300,300,    // ...
      400,400,
      500,500
},textPaint);

//3 指定一个路径作为基线,根据路径绘制文字
String string="风萧萧兮易水寒,壮士一去兮不复返";  
//先创建两个相同的圆形路径,并先画出两个路径原图  
Path circlePath=new Path();  
circlePath.addCircle(220,200, 180, Path.Direction.CCW);//逆向绘制
canvas.drawPath(circlePath, paint);//绘制出路径原形  

Path circlePath2=new Path();  
circlePath2.addCircle(750,200, 180, Path.Direction.CCW);  
canvas.drawPath(circlePath2, paint);//绘制出路径原形  
paint.setColor(Color.GREEN);  
//hoffset、voffset参数值全部设为0,看原始状态是怎样的  
canvas.drawTextOnPath(string, circlePath, 0, 0, paint);  
//第二个路径,改变hoffset、voffset参数值
canvas.drawTextOnPath(string, circlePath2, 80, 30, paint);  

这里写图片描述

2.6 路径Path,路径测量PathMeasure和SVG

本表格中去除了部分API21(即安卓版本5.0)以上才添加的方法

由于路径包含内容挺多,而且在绘制中占有的重要地位,最终我决定还是另起一篇单独总结,这里只列出简述表格

作用相关方法备注
移动起点moveTo移动下一次操作的起点位置
设置终点setLastPoint重置当前path中最后一个点位置,如果在绘制之前调用,效果和moveTo相同
连接直线lineTo添加上一个点到当前点之间的直线到Path
闭合路径close连接第一个点连接到最后一个点,形成一个闭合区域
添加内容addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo 添加(矩形, 圆角矩形, 椭圆, 圆, 路径, 圆弧) 到当前Path (注意addArc和arcTo的区别)
是否为空isEmpty判断Path是否为空
是否为矩形isRect判断path是否是一个矩形
替换路径set用新的路径替换到当前路径所有内容
偏移路径offset对当前路径之前的操作进行偏移(不会影响之后的操作)
贝塞尔曲线quadTo, cubicTo分别为二次和三次贝塞尔曲线的方法
rXxx方法rMoveTo, rLineTo, rQuadTo, rCubicTo不带r的方法是基于原点的坐标系(偏移量), rXxx方法是基于当前点坐标系(偏移量)
填充模式setFillType, getFillType, isInverseFillType, toggleInverseFillType设置,获取,判断和切换填充模式
提示方法incReserve 提示Path还有多少个点等待加入(这个方法貌似会让Path优化存储结构)
布尔操作(API19)op对两个Path进行布尔运算(即取交集、并集等操作)
计算边界computeBounds计算Path的边界
重置路径reset, rewind清除Path中的内容reset不保留内部数据结构,但会保留FillType.rewind会保留内部的数据结构,但不保留FillType
矩阵操作transform矩阵变换

三、矩阵

矩阵在图形学中的运用十分广泛,鉴于需要一定的数学基础,我们这里不对原理进行过多解读,有兴趣请自行查阅

Matrix是一个矩阵,主要功能是坐标映射,数值转换,它看起来大概是下面这样:

这里写图片描述

Matrix作用就是坐标映射,那么为什么需要Matrix呢? 举一个简单的例子:

我的的手机屏幕作为物理设备,其物理坐标系是从左上角开始的,但我们在开发的时候通常不会使用这一坐标系,而是使用内容区的坐标系

以下图为例,我们的内容区和屏幕坐标系还相差一个通知栏加一个标题栏的距离,所以两者是不重合的,我们在内容区的坐标系中的内容最终绘制的时候肯定要转换为实际的物理坐标系来绘制,Matrix在此处的作用就是转换这些数值。

假设通知栏高度为20像素,导航栏高度为40像素,那么我们在内容区的(0,0)位置绘制一个点,最终就要转化为在实际坐标系中的(0,60)位置绘制一个点

这里写图片描述

  • Matrix特点

    • 作用范围更广,Matrix在View,图片,动画效果等各个方面均有运用,相比与之前讲解等画布操作应用范围更广。

    • 更加灵活,画布操作是对Matrix的封装,Matrix作为更接近底层的东西,必然要比画布操作更加灵活。

    • 封装很好,Matrix本身对各个方法就做了很好的封装,让开发者可以很方便的操作Matrix。

    • 难以深入理解,很难理解中各个数值的意义,以及操作规律,如果不了解矩阵,也很难理解前乘,后乘

方法类别相关API摘要
基本方法equals hashCode toString toShortString比较、 获取哈希值、 转换为字符串
数值操作set reset setValues getValues设置、 重置、 设置数值、 获取数值
数值计算mapPoints mapRadius mapRect mapVectors计算变换后的数值
设置(set)setConcat setRotate setScale setSkew setTranslate设置变换
前乘(pre)preConcat preRotate preScale preSkew preTranslate前乘变换
后乘(post)postConcat postRotate postScale postSkew postTranslate后乘变换
特殊方法setPolyToPoly setRectToRect rectStaysRect setSinCos一些特殊操作
矩阵相关invert isAffine isIdentity求逆矩阵、 是否为仿射矩阵、 是否为单位矩阵 …

简单运用的话,只需要记住以下几个准则:

  • 前乘(pre)相当于矩阵的右乘,后乘(post)相当于矩阵的左乘
  • 正常的平移、旋转、错切、缩放等,使用Prexxx函数
  • 所有的操作(旋转、平移、缩放、错切)默认都是以坐标原点为基准点的
  • 之前操作的坐标系状态会保留,并且影响到后续状态

示例,围绕某点做旋转的操作如下:

Matrix matrix = new Matrix();
matrix.preTranslate(pivotX,pivotY);//先将坐标系原点移动到指定位置,使用平移 T
matrix.preRotate(angle);//对坐标系进行旋转,使用旋转 S (围绕原点旋转)
matrix.preTranslate(-pivotX, -pivotY);// 再将坐标系平移回原来位置,使用平移 -T

//以上方式:两个调整中心的平移函数就拉的太开了
//post有点像:要先执行的操作,放在了后面

Matrix matrix = new Matrix();
matrix.preRotate(angle);//对坐标系进行旋转,使用旋转 S (围绕原点旋转)
matrix.postTranslate(pivotX,pivotY);
matrix.preTranslate(-pivotX, -pivotY);// 再将坐标系平移回原来位置,使用平移 -T

其他

字体样式设置Typeface

在Paint中设置字体样式:
paint.setTypeface(typeface);

Typeface是专门用来设置字体样式的,通过paint.setTypeface()来指定。可以指定系统中的字体样式,也可以指定自定义的样式文件中获取。要构建Typeface时,可以指定所用样式的正常体、斜体、粗体等,如果指定样式中,没有相关文字的样式就会用系统默认的样式来显示,一般默认是宋体。

 Typeface   create(String familyName, int style) //直接通过指定字体名来加载系统中自带的文字样式
 Typeface   create(Typeface family, int style)     //通过其它Typeface变量来构建文字样式
 Typeface   createFromAsset(AssetManager mgr, String path) //通过从Asset中获取外部字体来显示字体样式
 Typeface   createFromFile(String path)//直接从路径创建
 Typeface   createFromFile(File path)//从外部路径来创建字体样式
 Typeface   defaultFromStyle(int style)//创建默认字体
  • 上面的各个参数都会用到Style变量,Style的枚举值如下:
    • Typeface.NORMAL //正常体
    • Typeface.BOLD //粗体
    • Typeface.ITALIC //斜体
    • Typeface.BOLD_ITALIC //粗斜体

我们给出相关的示例代码:

//1
String familyName = "宋体";  
Typeface font = Typeface.create(familyName,Typeface.NORMAL);  
paint.setTypeface(font);  
canvas.drawText("欢迎光临博客",10,100, paint);  

//2
AssetManager mgr=m_context.getAssets();//得到AssetManager  
Typeface typeface=Typeface.createFromAsset(mgr, "fonts/jian_luobo.ttf");//根据路径得到Typeface  
paint.setTypeface(typeface);  
Log.v("msg",typeface.toString());  
canvas.drawText("欢迎光临博客",10,100, paint);//两个构造函数  

硬件加速

硬件加速是个好东西,但是处理不好会引起诸多问题,硬件加速在 Android 4.0 以上是默认开启的

在GPU出现之前,CPU一直负责着所有的运算工作,CPU的架构是有利于X86指令集的串行架构,CPU从设计思路上适合尽可能快的完成一个任务。但当面对类似多媒体、图形图像处理类型的任务时,就显得力不从心。因为在多媒体计算中通常要求更高的运算密度、多并发线程和频繁地存储器访问;显然当你打游戏时,屏幕上的动画是需要实时刷新的,这些都需要频繁的计算、存取动作;如果CPU不能及时响应,那么屏幕就会显得很卡……

对于Andorid来讲,在API 11之前是没有GPU的概念的,在API 11之后,在程序集中加入了对GPU加速的支持,在API 14之后,硬件加速是默认开启的!我们可以显式地强制图像计算时使用GPU而不使用CPU. 在CPU绘制和GPU绘制时,在流程上是有区别的:

  • 在基于软件的绘制模型下,CPU主导绘图,视图按照两个步骤绘制:
    • 让View层次结构失效
    • 绘制View层次结构
  • 在基于硬件加速的绘制模式下,GPU主导绘图,绘制按照三个步骤绘制:
    • 让View层次结构失效
    • 记录、更新显示列表
    • 绘制显示列表

可以看到在GPU加速时,流程中多了一项“记录、更新显示列表”,它表示在第一步View层次结构失效后,并不是直接开始逐层绘制,而是首先把这些View的绘制函数作为绘制指令记录一个显示列表中,然后再读取显示列表中的绘制指令调用OpenGL相关函数完成实际绘制。所以在GPU加速时,实际是使用OpenGL的函数来完成绘制的

使用GPU加速的优点显而易见:硬件加速提高了Android系统显示和刷新的速度;
它有缺点也显而易见:

  1. 兼容性问题:由于是将绘制函数转换成OpenGL命令来绘制,定然会存在OpenGL并不能完全支持原始绘制函数的问题,所以这就会造成在打开GPU加速时,效果会失效的问题。
  2. 内存消耗问题:由于需要OpenGL的指令,所以需要把系统中的OpenGL相关的包加载到内存中来,所以单纯OpenGL API调用就会占用8MB,而实际上会占用更多内存;
  3. 电量消耗问题:多使用了一个部件,当然会更耗电……

同时,由于绘图中大量使用Matrix : Matrix作用就是坐标映射 ,其核心功能就是将单个 View 的坐标系转化为屏幕(物理)坐标系,虽然转换一次费不了多少时间,但是当执行动画效果等需要大量快速重绘的情况下,耗费的时间就需要考量一下了,于是乎,硬件加速干了一件非常精明的事情,把所有画布坐标系都设置为屏幕(物理)坐标系,之后在 View 绘制区域设置一个遮罩,保证绘制内容不会超过 View 自身的大小,这样就直接跳过坐标转换过程,可以节省坐标系之间数值转换耗费的时间。因此导致了以下问题:

  1. 开启硬件加速情况下 event.getX() 和 不开启情况下 event.getRawX() 等价,获取到的是屏幕(物理)坐标
  2. 开启硬件加速情况下 event.getRawX() 数值是一个错误数值,因为本身就是全局的坐标又叠加了一次 View 的偏移量,所以肯定是不正确的
  3. 从 Canvas 获取到的 Matrix 是全局的,默认情况下 x,y 偏移量始终为0,因此你不能从这里拿到当前 View 的偏移量
  4. 由于其使用的是遮罩来控制绘制区域,所以如果重绘 path 时,如果 path 区域变大,但没有执行单步操作会导致 path 绘制不完整或者看起来比较奇怪

参考文献

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值