Android 画布绘图

我们已经介绍了Canvas,在那里,已经学习了如何创建自己的View。在第7章中也使用了Canvas来为MapView标注覆盖。

画布(Canvas)是图形编程中一个很普通的概念,通常由三个基本的绘图组件组成:


Canvas 提供了绘图方法,可以向底层的位图绘制基本图形。

Paint 也称为"刷子",Paint可以指定如何将基本图形绘制到位图上。
Bitmap 绘图的表面。

Android绘图API支持透明度、渐变填充、圆边矩形和抗锯齿。遗憾的是,由于资源限制,它还不支持矢量图形,它使用的是传统光栅样式的重新绘图。
这种光栅方法的结果是提高了效率,但是改变一个Paint对象不会影响已经画好的基本图形,它只会影响新的元素。

提示:
如果你拥有Windows开发背景,那么Android的2D绘图能力大致相当于GDI+的能力。

1. 可以画什么?

Canvas类封装了用作绘图表面的位图;它还提供了draw*方法来实现设计。

下面的列表提供了对可用的基本图形的简要说明,但并没有深入地探讨每一个draw方法的详细内容:

drawARGB / drawRGB / drawColor 使用单一的颜色填充画布。
drawArc 在一个矩形区域的两个角之间绘制一个弧。
drawBitmap 在画布上绘制一个位图。可以通过指定目标大小或者使用一个矩阵来改变目标位图的外观。
drawBitmapMesh 使用一个mesh(网)来绘制一个位图,它可以通过移动网中的点来操作目标的外观。
drawCircle 以给定的点为圆心,绘制一个指定半径的圆。
drawLine(s) 在两个点之间画一条(多条)直线。
drawOval 以指定的矩形为边界,画一个椭圆。
drawPaint 使用指定的Paint填充整个Canvas
drawPath 绘制指定的Path。Path对象经常用来保存一个对象中基本图形的集合。
drawPicture 在指定的矩形中绘制一个Picture对象。
drawPosText 绘制指定了每一个字符的偏移量的文本字符串。
drawRect 绘制一个矩形。
drawRoundRect 绘制一个圆角矩形。
drawText 在Canvas上绘制一个文本串。文本的字体、大小和渲染属性都设置在用来渲染文本的Paint对象中。
drawTextOnPath 在一个指定的path上绘制文本。
drawVertices 绘制一系列三角形面片,通过一系列顶点来指定它们。

这些绘图方法中的每一个都需要指定一个Paint对象来渲染它。在下面的部分中,将学习如何创建和修改Paint对象,从而在绘图中完成大部分工作。

2. 从Paint中完成工作

Paint类相当于一个笔刷和调色板。它可以选择如何使用上面描述的draw方法来渲染绘制在画布上的基本图形。通过修改Paint对象,可以在绘图的时候控制颜色、样式、字体和特殊效果。最简单地,setColor可以让你选择一个Paint的颜色,而Paint对象的样式(使用setStyle控制)则可以决定是绘制绘图对象的轮廓(STROKE),还是只填充每一部分(FILL),或者是两者都做(STROKE_AND_FILL)

除了这些简单的控制之外,Paint类还支持透明度,另外,它也可以通过使用各种各样的阴影、过滤器和效果进行修改,从而提供由更丰富的、复杂的画笔和颜料组成的调色板。

Android SDK包含了一些非常好的实例,它们说明了Paint类中可用的大部分功能。你可以在API demos的graphics子目录中找到它们:

sdk root folder]\samples\ApiDemos\src\com\android\samples\graphics

在下面的部分中,将学习和使用其中的部分功能。这些部分只是简单地罗列了它们能实现的效果(例如渐变和边缘浮雕),而没有详细地列出所有可能的情况。

使用透明度

Android中的所有颜色都包含了一个不透明组件(alpha通道)。
当创建一个颜色的时候,可以使用argb或者parseColor方法来定义它的alpha值,如下所示:

Java代码:

  1. // 使用红色,并让它50%透明
  2. int opacity = 127;
  3. int intColor = Color.argb(opacity, 255, 0, 0);
  4. int parsedColor = Color.parseColor("#7FFF0000");
  5.  

复制代码

或者,也可以使用setAlpha方法来设置已存在的Paint对象的透明度:

Java代码:

  1. // 让颜色50%透明
  2. int opacity = 127;
  3. myPaint.setAlpha(opacity);

复制代码

创建一个不是100%透明的颜色意味着,使用它绘制的任何基本图形都将是部分透明的--也就是说,在它下面绘制的所有基本图形都是部分可见的。

可以在任何使用了颜色的类或者方法中使用透明效果,包括Paint、Shader和Mask Filter。

Shader介绍

Shader类的派生类可以创建允许使用多种固体颜色填充绘图对象的Paint。

对Shader最常见的使用是定义渐变填充;渐变是在2D图像中添加深度和纹理的最佳方式之一。Android包含了一个Bitmap Shader和一个Compose Shader,同时,还包含了三个渐变的Shader。
试图用语言来描述绘图的效果本来就是没有意义的,所以看一下图就应该可以知道每一种Shader是如何工作的。图中从左到右依次代表的是LinearGradient、RadialGradient和 SweepGradient.
提示:

没有包含的是ComposerShader,它可以创建多个Shader和BitmapShader的组合,从而可以在一个位图图像的基础上创建一个绘图刷。

要在绘图的时候使用一个Shader,可以使用setShader方法将其应用到一个Paint中,如下面的代码所示:
Paint shaderPaint = new Paint(); 
shaderPaint.setShader(myLinearGradient);

你使用这个Paint所绘制的任何东西都将使用你指定的Shader进行填充,而不是使用Paint本身的颜色进行填充。

定义渐变Shader

如上所示,使用渐变Shader可以让你使用交替改变的颜色来填充图片;你可以将颜色渐变定义为两种颜色的简单交替,如下所示:

java代码:

  1. int colorFrom = Color.BLACK;
  2. int colorTo = Color.WHITE;
  3. LinearGradient linearGradientShader = new LinearGradient(x1, y1, x2, y2, colorFrom, colorTo, TileMode.CLAMP);
  4.  

复制代码

或者,你还可以定义更复杂的按照设定比例进行分布的颜色序列,如下面的RadialGradientShader例子所示:

java代码:

  1. int[] gradientColors = new int[3];
  2. gradientColors[0] = Color.GREEN;
  3. gradientColors[1] = Color.YELLOW;
  4. gradientColors[2] = Color.RED;
  5. float[] gradientPositions = new float[3];
  6. gradientPositions[0] = 0.0f;
  7. gradientPositions[1] = 0.5f;
  8. gradientPositions[2] = 1.0f;
  9. RadialGradient radialGradientShader=new RadialGradient(centerX,centerY, radius, gradientColors, gradientPositions, TileMode.CLAMP);
  10.  

复制代码

每一种渐变Shader(线性的、辐射形的和扫描状的)都可以使用以上这两种技术来定义渐变填充。

使用Shader TileModes
渐变Shader的画刷大小既可以显式地使用有边界的矩形来定义,也可以使用中心点和半径长度来定义。Bitmap Shader可以通过它的位图大小来决定它的画刷大小。

如果Shader画刷所定义的区域比要填充的区域小,那么TileMode将会决定如何处理剩余的区域:
CLAMP 使用Shader的边界颜色来填充剩余的空间。
MIRROR 在水平和垂直方向上拉伸Shader图像,这样每一个图像就都能与上一个缝合了。
REPEAT 在水平和垂直方向上重复Shader图像,但不拉伸它。

使用MaskFilter

MaskFilter类可以为Paint分配边缘效果。
对MaskFilter的扩展可以对一个Paint边缘的alpha通道应用转换。Android包含了下面几种MaskFilter:

BlurMaskFilter 指定了一个模糊的样式和半径来处理Paint的边缘。
EmbossMaskFilter 指定了光源的方向和环境光强度来添加浮雕效果。

要应用一个MaskFilter,可以使用setMaskFilter方法,并传递给它一个MaskFilter对象。下面的例子是对一个已经存在的Paint应用一个EmbossMaskFilter:

java代码:

  1. // 设置光源的方向
  2. float[] direction = new float[]{ 1, 1, 1 };
  3. //设置环境光亮度
  4. float light = 0.4f;
  5. // 选择要应用的反射等级
  6. float specular = 6;
  7. // 向mask应用一定级别的模糊
  8. float blur = 3.5f;
  9. EmbossMaskFilter emboss=new EmbossMaskFilter(direction,light,specular,blur);
  10. // 应用mask myPaint.setMaskFilter(emboss);
  11.  

复制代码

SDK中包含的FingerPaint API demo是说明如何使用MaskFilter的一个非常好的例子。它展示了这两种filter的效果。

使用ColorFilter

MaskFilter是对一个Paint的alpha通道的转换,而ColorFilter则是对每一个RGB通道应用转换。所有由ColorFilter所派生的类在执行它们的转换时,都会忽略alpha通道。


Android包含三个ColorFilter:

ColorMatrixColorFilter 可以指定一个4×5的ColorMatrix并将其应用到一个Paint中。ColorMatrixes通常在程序中用于对图像进行处理,而且由于它们支持使用矩阵相乘的方法来执行链接转换,所以它们很有用。

LightingColorFilter 乘以第一个颜色的RGB通道,然后加上第二个颜色。每一次转换的结果都限制在0到255之间。
PorterDuffColorFilter 可以使用数字图像合成的16条Porter-Duff 规则中的任意一条来向Paint应用一个指定的颜色。

使用setColorFilter方法应用ColorFilter,如下所示:
myPaint.setColorFilter(new LightingColorFilter(Color.BLUE, Color.RED));

API中的ColorMatrixSample是说明如何使用ColorFilter和Color Matrix的非常好的例子。

使用PathEffect

到目前为止,所有的效应都会影响到Paint填充图像的方式;PathEffect是用来控制绘制轮廓(线条)的方式。
PathEffect对于绘制Path基本图形特别有用,但是它们也可以应用到任何Paint中从而影响线条绘制的方式。

使用PathEffect,可以改变一个形状的边角的外观并且控制轮廓的外表。Android包含了多个PathEffect,包括:
CornerPathEffect 可以使用圆角来代替尖锐的角从而对基本图形的形状尖锐的边角进行平滑。

DashPathEffect 可以使用DashPathEffect来创建一个虚线的轮廓(短横线/小圆点),而不是使用实线。你还可以指定任意的虚/实线段的重复模式。

DiscretePathEffect 与DashPathEffect相似,但是添加了随机性。当绘制它的时候,需要指定每一段的长度和与原始路径的偏离度。

PathDashPathEffect 这种效果可以定义一个新的形状(路径)并将其用作原始路径的轮廓标记。
下面的效果可以在一个Paint中组合使用多个Path Effect。

SumPathEffect 顺序地在一条路径中添加两种效果,这样每一种效果都可以应用到原始路径中,而且两种结果可以结合起来。

ComposePathEffect 将两种效果组合起来应用,先使用第一种效果,然后在这种效果的基础上应用第二种效果。
对象形状的PathEffect的改变会影响到形状的区域。这就能够保证应用到相同形状的填充效果将会绘制到新的边界中。

使用setPathEffect方法可以把PathEffect应用到Paint对象中,如下所示:

java代码:

  1. borderPaint.setPathEffect(new CornerPathEffect(5));

复制代码

PathEffect API示例给出了如何应用每一种效果的指导说明。

修改Xfermode

可以通过修改Paint的Xfermode来影响在Canvas已有的图像上面绘制新的颜色的方式。
在正常的情况下,在已有的图像上绘图将会在其上面添加一层新的形状。如果新的Paint是完全不透明的,那么它将完全遮挡住下面的Paint;如果它是部分透明的,那么它将会被染上下面的颜色。

下面的Xfermode子类可以改变这种行为:
AvoidXfermode 指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图)。
PixelXorXfermode 当覆盖已有的颜色时,应用一个简单的像素XOR操作。
PorterDuffXfermode 这是一个非常强大的转换模式,使用它,可以使用图像合成的16条Porter-Duff规则的任意一条来控制Paint如何与已有的Canvas图像进行交互。

要应用转换模式,可以使用setXferMode方法,如下所示:

java代码:

  1. AvoidXfermode avoid = new AvoidXfermode(Color.BLUE, 10, AvoidXfermode.Mode. AVOID); borderPen.setXfermode(avoid);

复制代码

3. 使用抗锯齿效果提高Paint质量

在绘制一个新的Paint对象时,可以通过传递给它一些标记来影响它被渲染的方式。ANTI_ALIAS_FLAG是其中一种很有趣的标记,它可以保证在绘制斜线的时候使用抗锯齿效果来平滑该斜线的外观。

在绘制文本的时候,抗锯齿效果尤为重要,因为经过抗锯齿效果处理之后的文本非常容易阅读。要创建更加平滑的文本效果,可以应用SUBPIXEL_TEXT_FLAG,它将会应用子像素抗锯齿效果。

也可以手工地使用setSubpixelText和setAntiAlias方法来设置这些标记,如下所示:

java代码:

  1. myPaint.setSubpixelText(true);
  2. myPaint.setAntiAlias(true);

复制代码

2D图形的硬件加速

在当前这个到处都是2D图形爱好者的时代,Android允许你使用硬件加速来渲染你的应用程序。

如果设备可以使用硬件加速,那么通过设置这个标记可以让活动中的每一个View都能使用硬件渲染。尽管减少了系统处理程序的负载,但在极大地提高了图像处理速度的同时,硬件加速也带来了相应的负面效果。

使用requestWindowFeature方法,可以在你的活动中应用Window.FEATURE_OPENGL标记来打开硬件加速,如下所示:

java代码:

  1. myActivity.requestWindowFeature(Window.FEATURE_OPENGL);

复制代码

并不是Android中所有的2D绘图基本图形都被硬件支持(特别是前面描述的大部分PathEffect)。

与此同时,由于整个活动实际上是作为一个Canvas进行渲染的,所以对任何View的无效请求都将会导致整个活动被重新绘制。


Canvas绘图最佳实践经验

2D自绘操作是非常耗费处理程序资源的;低效的绘图方法会阻塞GUI线程,并且会对应用程序的响应造成不利的影响。对于那些只有一个处理程序的资源受限的环境来说,这一点就更加现实了。

这里需要注意onDraw方法的资源消耗以及CPU周期的耗费,这样才能保证不会把一个看起来很吸引人的应用程序变得完全没有响应。

目前有很多技术可以帮助将与自绘控件相关的资源消耗最小化。我们关心的不是一般的原则,而是某些Android特定的注意事项,从而保证你可以创建外观时尚、而且能够保持交互的活动(注意,以下这个列表并不完整):

考虑硬件加速 OpenGL硬件加速对2D图形的支持是非常好的,所以你总是应该考虑它是否适合你的活动。另一种比较优秀的方法是只用一个单独的View和迅速的、耗时的更新来组成活动。一定要保证你使用的基本图形能够被硬件支持。

考虑大小和方向 当在设计View和布局的时候,一定要保证考虑(和测试)它们在不同的分辨率和大小下的外观。

只创建一次静态对象 在Android中对象的创建是相当昂贵的。因此,在可能的地方,应用只创建一次像Paint对象、Path和Shader这样的绘图对象,而不是在View每次无效的时候都重新创建它们。

记住onDraw是很消耗资源的 执行onDraw方法是很消耗资源的处理,它会强制Android执行多个图片组合和位图构建操作。下面有几点建议可以让你修改Canvas的外观,而不用重新绘制它:

使用Canvas转换 可以使用像rotate和translate这样的转换,来简化Canvas中元素复杂的相关位置。例如,相比放置和旋转一个表盘周围的每一个文本元素,你可以简单地将canvas旋转22.5?,然后在相同的位置绘制文本。

使用动画 可以考虑使用动画来执行View的预设置的转换,而不是手动地重新绘制它。在活动的View中可以执行缩放、旋转和转换动画,并可以提供一种能够有效利用资源的方式来提供缩放、旋转或者抖动效果。

考虑使用位图和9 Patch 如果View使用了静态背景,那么你应该考虑使用一个图片,如位图或者9 patch,而不是手动地重新绘制。

高级指南针表盘的例子

已经创建了一个简单的指南针。而在上一章,你又回到了这个例子,对它进行了扩展从而使它够使用加速计硬件来显示横向和纵向方向。

那些例子中的UI都很简单,从而保证了那些章节中的代码都尽可能地清晰。

在下面的例子中,将对CompassView的onDraw方法做一些重要的改动,从而把它从一个简单的、平面的指南针,变成一个动态的航空地平仪(artificial horizon ),如图所示。

由于上面的图片是黑白的,所以需要实际动手创建这个控件来看到完全的效果。

(1) 首先,通过修改colors.xml资源文件来包含边界、表盘阴影以及天空和地面的颜色值。同时还要更新边界和盘面标记所使用的颜色。

java代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <color name="text_color">#FFFF</color>
  4. <color name="background_color">#F000</color>
  5. <color name="marker_color">#FFFF</color>
  6. <color name="shadow_color">#7AAA</color>
  7. <color name="outer_border">#FF444444</color>
  8. <color name="inner_border_one">#FF323232</color>
  9. <color name="inner_border_two">#FF414141</color>
  10. <color name="inner_border">#FFFFFFFF</color>
  11. <color name="horizon_sky_from">#FFA52A2A</color>
  12. <color name="horizon_sky_to">#FFFFC125</color>
  13. <color name="horizon_ground_from">#FF5F9EA0</color>
  14. <color name="horizon_ground_to">#FF00008B</color>
  15. </resources>

复制代码

(2) 用作航空地平仪的天空和地面的Paint和Shader对象是根据当前View的大小创建的,所以它们不能像你在创建的Paint对象那样,是静态的。因此,不再创建Paint对象,取而代之的是构造它们所使用的渐变数组和颜色。

java代码:

  1. int[] borderGradientColors;
  2. float[] borderGradientPositions;
  3. int[] glassGradientColors;
  4. float[] glassGradientPositions;
  5. int skyHorizonColorFrom;
  6. int skyHorizonColorTo;
  7. int groundHorizonColorFrom;
  8. int groundHorizonColorTo;

复制代码

(3) 更新CompassView的initCompassView方法,来使用第(1)步中所创建的资源来初始化第(2)步中所创建的变量。现存的方法代码大部分可以保留,而只需要对textPaint、circlePaint和markerPaint变量做些许改动,如下所示:

java代码:

  1. protected void initCompassView() {
  2. setFocusable(true);
  3. // 获得外部资源
  4. Resources r = this.getResources();
  5. circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  6. circlePaint.setColor(R.color.background_color);
  7. circlePaint.setStrokeWidth(1);
  8. circlePaint.setStyle(Paint.Style.STROKE);
  9. northString = r.getString(R.string.cardinal_north);
  10. eastString = r.getString(R.string.cardinal_east);
  11. southString = r.getString(R.string.cardinal_south);
  12. westString = r.getString(R.string.cardinal_west);
  13. textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  14. textPaint.setColor(r.getColor(R.color.text_color));
  15. textPaint.setFakeBoldText(true);
  16. textPaint.setSubpixelText(true);
  17. textPaint.setTextAlign(Align.LEFT);
  18. textHeight = (int)textPaint.measureText("yY");
  19. markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  20. markerPaint.setColor(r.getColor(R.color.marker_color));
  21. markerPaint.setAlpha(200);
  22. markerPaint.setStrokeWidth(1);
  23. markerPaint.setStyle(Paint.Style.STROKE);
  24. markerPaint.setShadowLayer(2, 1, 1, r.getColor(R.color.shadow_color));

复制代码

a. 创建径向Shader用来绘制外边界所使用的颜色和位置数组。

java代码:

  1. borderGradientColors = new int[4];
  2. borderGradientPositions = new float[4];
  3. borderGradientColors[3] = r.getColor(R.color.outer_border);
  4. borderGradientColors[2] = r.getColor(R.color.inner_border_one);
  5. borderGradientColors[1] = r.getColor(R.color.inner_border_two);
  6. borderGradientColors[0] = r.getColor(R.color.inner_border);
  7. borderGradientPositions[3] = 0.0f;
  8. borderGradientPositions[2] = 1-0.03f;
  9. borderGradientPositions[1] = 1-0.06f;

复制代码

b. 现在创建径向渐变的颜色和位置数组,它们将用来创建半透明的"glass dome"(玻璃圆顶),它放置在View的上面,从而使人产生深度的幻觉。

java代码:

  1. glassGradientColors = new int[5];
  2. glassGradientPositions = new float[5];
  3. int glassColor = 245;
  4. glassGradientColors[4]=Color.argb(65,glassColor,glassColor, glassColor);
  5. glassGradientColors[3]=Color.argb(100,glassColor,glassColor,glassColor);
  6. glassGradientColors[2]=Color.argb(50,glassColor,glassColor, glassColor);
  7. glassGradientColors[1]=Color.argb(0,glassColor,glassColor, glassColor);
  8. glassGradientColors[0]=Color.argb(0,glassColor,glassColor, glassColor);
  9. glassGradientPositions[4] = 1-0.0f;
  10. glassGradientPositions[3] = 1-0.06f;
  11. glassGradientPositions[2] = 1-0.10f;
  12. glassGradientPositions[1] = 1-0.20f;
  13. glassGradientPositions[0] = 1-1.0f;

复制代码

c. 最后,获得创建线性颜色渐变所使用的颜色,它们将用来表示航空地平仪中的天空和地面。

java代码:

  1. skyHorizonColorFrom = r.getColor(R.color.horizon_sky_from);
  2. skyHorizonColorTo = r.getColor(R.color.horizon_sky_to);
  3. groundHorizonColorFrom = r.getColor(R.color.horizon_ground_from);
  4. groundHorizonColorTo = r.getColor(R.color.horizon_ground_to);

复制代码

(6) 创建用来填充圆的每个部分(地面和天空)的路径。每一部分的比例应该与形式化之后的俯仰值有关。

java代码:

  1. Path skyPath = new Path();
  2. skyPath.addArc(innerBoundingBox, -tiltDegree, (180 + (2 * tiltDegree)));
  3.  

复制代码

(7) 将Canvas围绕圆心,按照与当前翻转角相反的方向进行旋转,并且使用在第(4)步中所创建的Paint来绘制天空和地面路径。

java代码:

  1. canvas.rotate(-rollDegree, px, py);
  2. canvas.drawOval(innerBoundingBox, groundPaint);
  3. canvas.drawPath(skyPath, skyPaint);
  4. canvas.drawPath(skyPath, markerPaint);
  5.  

复制代码

(8) 接下来是盘面标记,首先计算水平的水平标记的起止点。

java代码:

  1. int markWidth = radius / 3; int startX = center.x - markWidth; int endX = center.x + markWidth;
  2.  

复制代码

(9) 要让水平值更易于读取,应该保证俯仰角刻度总是从当前值开始。下面的代码计算了天空和地面的接口在水平面上的位置:

java代码:

  1. double h = innerRadius*Math.cos(Math.toRadians(90-tiltDegree)); double justTiltY = center.y - h;
  2.  

复制代码

(10) 找到表示每一个倾斜角的像素的数目。

java代码:

  1. float pxPerDegree = (innerBoundingBox.height()/2)/45f;
  2.  

复制代码

(11) 现在遍历180度,以当前的倾斜值为中心,给出一个可能的俯仰角的滑动刻度。

java代码:

  1. for (int i = 90; i >= -90; i -= 10) {
  2. double ypos = justTiltY + i*pxPerDegree;
  3. // 只显示内表盘的刻度
  4. if ((ypos < (innerBoundingBox.top + textHeight)) || (ypos > innerBoundingBox.bottom - textHeight)) continue;
  5. // 为每一个刻度增加画一个直线和一个倾斜角
  6. canvas.drawLine(startX, (float)ypos, endX, (float)ypos, markerPaint);
  7. t displayPos = (int)(tiltDegree - i);
  8. String displayString = String.valueOf(displayPos);
  9. float stringSizeWidth = textPaint.measureText(displayString);
  10. canvas.drawText(displayString, (int)(center.x-stringSizeWidth/2), (int)(ypos)+1, textPaint);
  11. }
  12.  

复制代码

(12) 现在,在大地/天空接口处绘制一条更粗的线。在画线之前,改变markerPaint对象的线条粗度(然后把它设置回以前的值)。

java代码:

  1. markerPaint.setStrokeWidth(2);
  2. canvas.drawLine(center.x - radius / 2, (float)justTiltY, center.x + radius / 2, (float)justTiltY, markerPaint);
  3. markerPaint.setStrokeWidth(1);
  4.  

复制代码

(13) 要让用户能够更容易地读取精确的翻转值,应该画一个箭头,并显示一个文本字符串来表示精确值。

创建一个新的Path,并使用moveTo/lineTo方法构建一个开放的箭头,它指向直线的前方。然后绘制路径和一个文本字符串来展示当前的翻转。

java代码:

  1. // 绘制箭头
  2. Path rollArrow = new Path();
  3. rollArrow.moveTo(center.x - 3, (int)innerBoundingBox.top + 14);
  4. rollArrow.lineTo(center.x, (int)innerBoundingBox.top + 10);
  5. rollArrow.moveTo(center.x + 3, innerBoundingBox.top + 14);
  6. rollArrow.lineTo(center.x, innerBoundingBox.top + 10);
  7. canvas.drawPath(rollArrow, markerPaint);
  8. // 绘制字符串
  9. String rollText = String.valueOf(rollDegree);
  10. double rollTextWidth = textPaint.measureText(rollText);
  11. canvas.drawText(rollText, (float)(center.x - rollTextWidth / 2), innerBoundingBox.top + textHeight + 2, textPaint);
  12.  

复制代码

(14) 将Canvas旋转到正上方,这样就可以绘制其他的盘面标记了。

java代码:

  1. canvas.restore();
  2.  

复制代码

(15) 每次将Canvas旋转10度,然后画一个标记或者一个值,直到画完翻转值表盘为止。当完成表盘之后,把Canvas恢复为正上方的方向。

java代码:

  1. canvas.save();
  2. canvas.rotate(180, center.x, center.y);
  3. for (int i = -180; i < 180; i += 10) {
  4. // 每30度显示一个数字值
  5. if (i % 30 == 0) {
  6. String rollString = String.valueOf(i*-1);
  7. float rollStringWidth = textPaint.measureText(rollString);
  8. PointF rollStringCenter = new PointF(center.x-rollStringWidth / 2, innerBoundingBox.top+1+textHeight);
  9. canvas.drawText(rollString, rollStringCenter.x, rollStringCenter.y, textPaint);
  10. }
  11. // 否则,绘制一个标记直线
  12. else { canvas.drawLine(center.x, (int)innerBoundingBox.top, center.x, (int)innerBoundingBox.top + 5, markerPaint);
  13. }
  14. canvas.rotate(10, center.x, center.y);
  15. }
  16. canvas.restore();
时间: 12-18

Android 画布绘图的相关文章

Android SurfaceView 绘图覆盖刷新及脏矩形刷新方法

http://www.cnblogs.com/SkyD/archive/2010/11/08/1871423.html Android SurfaceView 绘图覆盖刷新及脏矩形刷新方法 SurfaceView在Android中用作游戏开发是最适宜的,本文就将演示游戏开发中常用的两种绘图刷新策略在SurfaceView中的实现方法. 首先我们来看一下本例需要用到的两个素材图片: bj.jpg就是一个渐变图,用作背景. question.png是一个半透明的图像,我们希望将它放在上面,围绕其圆心

&lt;Android&gt;画布的移动和翻转

Android画布翻转是个利器,尤其在图像处理上,不需要数组的转置颠倒一堆线性变化就可以轻松实现原点的改变. 就像酱紫,开始的时候,画布妹妹是和显示区哥哥重叠在一起的,默契的就像一个人一样,三观一致,出发点统一在左上角.我们画的图像都是在画布上的,呈现在显示区域中.此时在画布上画一个进度bar是这个效果的. 如果想要得到一个竖向的bar,那我们就来翻转画布,首先逆时针翻转90°,得到下图 这时画布妹妹和显示区哥哥的世界已经不是同一个坐标系了 它们的世界有了分歧,为了使它们看到的世界是同一个世界,

安卓高级画布绘图

          我们已经介绍了Canvas,在那里,已经学习了如何创建自己的View.在第7章中也使用了Canvas来为MapView标注覆盖. 画布(Canvas)是图形编程中一个很普通的概念,通常由三个基本的绘图组件组成: Canvas  提供了绘图方法,可以向底层的位图绘制基本图形.        Paint  也称为"刷子",Paint可以指定如何将基本图形绘制到位图上.        Bitmap  绘图的表面. Android绘图API支持透明度.渐变填充.圆边矩形和抗

从零开始学android&lt;android基本绘图.四十六.&gt;

在一般的图形绘制中用户往往只需要覆写onDraw()方法即可,可是如果要想真正的完成绘图的操作,还需要掌握四个核心的操作类: android.graphics.Bitmap:主要表示的是一个图片的存储空间,所包含的图片可以来自于文件或由程序创建: android.graphics.Paint:主要的绘图工具类,可以指定绘图的样式: android.graphics.Canvas:是一个操作绘图以及Bitmap的平台,相当于提供了一个画板的功能,在onDraw()方法的参数中也定义了此类型的参数,

Android Canvas绘图详解(图文) - 泡在网上的日子

body { font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI",Tahoma,Helvetica,Sans-Serif,"Microsoft YaHei", Georgia,Helvetica,Arial,sans-serif,宋体, PMingLiU,serif; font-size: 10.5pt; line-height: 1.5;

Android Canvas绘图详解(图文)

编辑推荐:稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识.前端.后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过! Android中使用图形处理引擎,2D部分是android SDK内部自己提供,3D部分是用Open GL ES 1.0.今天我们主要要了解的是2D相关的,如果你想看3D的话那么可以跳过这篇文章. 大 部分2D使用的api都在android.graphics和android.graphics.drawabl

android xfermode绘图

1.fermode:是来自 transfer-modes,在画笔中设置xfermode后,画笔在使用的时候就会应用设置的像素转换模式.就类似于ps中的选取功能,留下的选取是从原选区中去除还是合并,取交还是除去,就这么回事. 2.fermode相关类之间的继承关系: 3.直接继承子类有AvoidXfermode(避免绘图或者只在上边绘制).PixelXorfermode(与目标做xor操作)和PorterDuffXfermode(一下几种模式)

第三章 Android绘图机制与处理技巧

1.屏幕尺寸信息 屏幕大小:屏幕对角线长度,单位“寸”:分辨率:手机屏幕像素点个数,例如720x1280分辨率:PPI(Pixels Per Inch):即DPI(Dots Per Inch),它是对角线的像素点数除以屏幕大小得到的:系统屏幕密度:android系统定义了几个标准的DPI值作为手机的固定DPI.(注,最后一个有笔误,正确的是1080x1920)独立像素密度(DP):android系统使用mdpi屏幕作为标准,在这个屏幕上1dp=1px,其他屏幕可以通过比例进行换算.在hdpi中,

Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解

概述 类android.graphics.PorterDuffXfermode继承自android.graphics.Xfermode.在用Android中的Canvas进行绘图时,可以通过使用PorterDuffXfermode将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新Canvas中最终的像素颜色值,这样会创建很多有趣的效果.当使用PorterDuffXfermode时,需要将将其作为参数传给Paint.setXfermode(Xfermo

进阶篇-用户界面:5.android绘图api自定义View(视图)

1.自定义视图并为其添加属性     我们平时用的Button啊 TextView啊都是安卓中系统自带的控件供开发者使用,但是,这些事远远不够的,有时候我们需要自定义控件. (1)新建一个类MyView使其继承View 类 import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; /** * C