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代码:
- // 使用红色,并让它50%透明
- int opacity = 127;
- int intColor = Color.argb(opacity, 255, 0, 0);
- int parsedColor = Color.parseColor("#7FFF0000");
复制代码
或者,也可以使用setAlpha方法来设置已存在的Paint对象的透明度:
Java代码:
- // 让颜色50%透明
- int opacity = 127;
- 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代码:
- int colorFrom = Color.BLACK;
- int colorTo = Color.WHITE;
- LinearGradient linearGradientShader = new LinearGradient(x1, y1, x2, y2, colorFrom, colorTo, TileMode.CLAMP);
复制代码
或者,你还可以定义更复杂的按照设定比例进行分布的颜色序列,如下面的RadialGradientShader例子所示:
java代码:
- int[] gradientColors = new int[3];
- gradientColors[0] = Color.GREEN;
- gradientColors[1] = Color.YELLOW;
- gradientColors[2] = Color.RED;
- float[] gradientPositions = new float[3];
- gradientPositions[0] = 0.0f;
- gradientPositions[1] = 0.5f;
- gradientPositions[2] = 1.0f;
- RadialGradient radialGradientShader=new RadialGradient(centerX,centerY, radius, gradientColors, gradientPositions, TileMode.CLAMP);
复制代码
每一种渐变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代码:
- // 设置光源的方向
- float[] direction = new float[]{ 1, 1, 1 };
- //设置环境光亮度
- float light = 0.4f;
- // 选择要应用的反射等级
- float specular = 6;
- // 向mask应用一定级别的模糊
- float blur = 3.5f;
- EmbossMaskFilter emboss=new EmbossMaskFilter(direction,light,specular,blur);
- // 应用mask myPaint.setMaskFilter(emboss);
复制代码
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代码:
- 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代码:
- 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代码:
- myPaint.setSubpixelText(true);
- myPaint.setAntiAlias(true);
复制代码
2D图形的硬件加速
在当前这个到处都是2D图形爱好者的时代,Android允许你使用硬件加速来渲染你的应用程序。
如果设备可以使用硬件加速,那么通过设置这个标记可以让活动中的每一个View都能使用硬件渲染。尽管减少了系统处理程序的负载,但在极大地提高了图像处理速度的同时,硬件加速也带来了相应的负面效果。
使用requestWindowFeature方法,可以在你的活动中应用Window.FEATURE_OPENGL标记来打开硬件加速,如下所示:
java代码:
- 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代码:
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <color name="text_color">#FFFF</color>
- <color name="background_color">#F000</color>
- <color name="marker_color">#FFFF</color>
- <color name="shadow_color">#7AAA</color>
- <color name="outer_border">#FF444444</color>
- <color name="inner_border_one">#FF323232</color>
- <color name="inner_border_two">#FF414141</color>
- <color name="inner_border">#FFFFFFFF</color>
- <color name="horizon_sky_from">#FFA52A2A</color>
- <color name="horizon_sky_to">#FFFFC125</color>
- <color name="horizon_ground_from">#FF5F9EA0</color>
- <color name="horizon_ground_to">#FF00008B</color>
- </resources>
复制代码
(2) 用作航空地平仪的天空和地面的Paint和Shader对象是根据当前View的大小创建的,所以它们不能像你在创建的Paint对象那样,是静态的。因此,不再创建Paint对象,取而代之的是构造它们所使用的渐变数组和颜色。
java代码:
- int[] borderGradientColors;
- float[] borderGradientPositions;
- int[] glassGradientColors;
- float[] glassGradientPositions;
- int skyHorizonColorFrom;
- int skyHorizonColorTo;
- int groundHorizonColorFrom;
- int groundHorizonColorTo;
复制代码
(3) 更新CompassView的initCompassView方法,来使用第(1)步中所创建的资源来初始化第(2)步中所创建的变量。现存的方法代码大部分可以保留,而只需要对textPaint、circlePaint和markerPaint变量做些许改动,如下所示:
java代码:
- protected void initCompassView() {
- setFocusable(true);
- // 获得外部资源
- Resources r = this.getResources();
- circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- circlePaint.setColor(R.color.background_color);
- circlePaint.setStrokeWidth(1);
- circlePaint.setStyle(Paint.Style.STROKE);
- northString = r.getString(R.string.cardinal_north);
- eastString = r.getString(R.string.cardinal_east);
- southString = r.getString(R.string.cardinal_south);
- westString = r.getString(R.string.cardinal_west);
- textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- textPaint.setColor(r.getColor(R.color.text_color));
- textPaint.setFakeBoldText(true);
- textPaint.setSubpixelText(true);
- textPaint.setTextAlign(Align.LEFT);
- textHeight = (int)textPaint.measureText("yY");
- markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- markerPaint.setColor(r.getColor(R.color.marker_color));
- markerPaint.setAlpha(200);
- markerPaint.setStrokeWidth(1);
- markerPaint.setStyle(Paint.Style.STROKE);
- markerPaint.setShadowLayer(2, 1, 1, r.getColor(R.color.shadow_color));
复制代码
a. 创建径向Shader用来绘制外边界所使用的颜色和位置数组。
java代码:
- borderGradientColors = new int[4];
- borderGradientPositions = new float[4];
- borderGradientColors[3] = r.getColor(R.color.outer_border);
- borderGradientColors[2] = r.getColor(R.color.inner_border_one);
- borderGradientColors[1] = r.getColor(R.color.inner_border_two);
- borderGradientColors[0] = r.getColor(R.color.inner_border);
- borderGradientPositions[3] = 0.0f;
- borderGradientPositions[2] = 1-0.03f;
- borderGradientPositions[1] = 1-0.06f;
复制代码
b. 现在创建径向渐变的颜色和位置数组,它们将用来创建半透明的"glass dome"(玻璃圆顶),它放置在View的上面,从而使人产生深度的幻觉。
java代码:
- glassGradientColors = new int[5];
- glassGradientPositions = new float[5];
- int glassColor = 245;
- glassGradientColors[4]=Color.argb(65,glassColor,glassColor, glassColor);
- glassGradientColors[3]=Color.argb(100,glassColor,glassColor,glassColor);
- glassGradientColors[2]=Color.argb(50,glassColor,glassColor, glassColor);
- glassGradientColors[1]=Color.argb(0,glassColor,glassColor, glassColor);
- glassGradientColors[0]=Color.argb(0,glassColor,glassColor, glassColor);
- glassGradientPositions[4] = 1-0.0f;
- glassGradientPositions[3] = 1-0.06f;
- glassGradientPositions[2] = 1-0.10f;
- glassGradientPositions[1] = 1-0.20f;
- glassGradientPositions[0] = 1-1.0f;
复制代码
c. 最后,获得创建线性颜色渐变所使用的颜色,它们将用来表示航空地平仪中的天空和地面。
java代码:
- skyHorizonColorFrom = r.getColor(R.color.horizon_sky_from);
- skyHorizonColorTo = r.getColor(R.color.horizon_sky_to);
- groundHorizonColorFrom = r.getColor(R.color.horizon_ground_from);
- groundHorizonColorTo = r.getColor(R.color.horizon_ground_to);
复制代码
java代码:
- Path skyPath = new Path();
- skyPath.addArc(innerBoundingBox, -tiltDegree, (180 + (2 * tiltDegree)));
复制代码
(7) 将Canvas围绕圆心,按照与当前翻转角相反的方向进行旋转,并且使用在第(4)步中所创建的Paint来绘制天空和地面路径。
java代码:
- canvas.rotate(-rollDegree, px, py);
- canvas.drawOval(innerBoundingBox, groundPaint);
- canvas.drawPath(skyPath, skyPaint);
- canvas.drawPath(skyPath, markerPaint);
复制代码
(8) 接下来是盘面标记,首先计算水平的水平标记的起止点。
java代码:
- int markWidth = radius / 3; int startX = center.x - markWidth; int endX = center.x + markWidth;
复制代码
(9) 要让水平值更易于读取,应该保证俯仰角刻度总是从当前值开始。下面的代码计算了天空和地面的接口在水平面上的位置:
java代码:
- double h = innerRadius*Math.cos(Math.toRadians(90-tiltDegree)); double justTiltY = center.y - h;
复制代码
(10) 找到表示每一个倾斜角的像素的数目。
java代码:
- float pxPerDegree = (innerBoundingBox.height()/2)/45f;
复制代码
(11) 现在遍历180度,以当前的倾斜值为中心,给出一个可能的俯仰角的滑动刻度。
java代码:
- for (int i = 90; i >= -90; i -= 10) {
- double ypos = justTiltY + i*pxPerDegree;
- // 只显示内表盘的刻度
- if ((ypos < (innerBoundingBox.top + textHeight)) || (ypos > innerBoundingBox.bottom - textHeight)) continue;
- // 为每一个刻度增加画一个直线和一个倾斜角
- canvas.drawLine(startX, (float)ypos, endX, (float)ypos, markerPaint);
- t displayPos = (int)(tiltDegree - i);
- String displayString = String.valueOf(displayPos);
- float stringSizeWidth = textPaint.measureText(displayString);
- canvas.drawText(displayString, (int)(center.x-stringSizeWidth/2), (int)(ypos)+1, textPaint);
- }
复制代码
(12) 现在,在大地/天空接口处绘制一条更粗的线。在画线之前,改变markerPaint对象的线条粗度(然后把它设置回以前的值)。
java代码:
- markerPaint.setStrokeWidth(2);
- canvas.drawLine(center.x - radius / 2, (float)justTiltY, center.x + radius / 2, (float)justTiltY, markerPaint);
- markerPaint.setStrokeWidth(1);
复制代码
(13) 要让用户能够更容易地读取精确的翻转值,应该画一个箭头,并显示一个文本字符串来表示精确值。
创建一个新的Path,并使用moveTo/lineTo方法构建一个开放的箭头,它指向直线的前方。然后绘制路径和一个文本字符串来展示当前的翻转。
java代码:
- // 绘制箭头
- Path rollArrow = new Path();
- rollArrow.moveTo(center.x - 3, (int)innerBoundingBox.top + 14);
- rollArrow.lineTo(center.x, (int)innerBoundingBox.top + 10);
- rollArrow.moveTo(center.x + 3, innerBoundingBox.top + 14);
- rollArrow.lineTo(center.x, innerBoundingBox.top + 10);
- canvas.drawPath(rollArrow, markerPaint);
- // 绘制字符串
- String rollText = String.valueOf(rollDegree);
- double rollTextWidth = textPaint.measureText(rollText);
- canvas.drawText(rollText, (float)(center.x - rollTextWidth / 2), innerBoundingBox.top + textHeight + 2, textPaint);
复制代码
(14) 将Canvas旋转到正上方,这样就可以绘制其他的盘面标记了。
java代码:
- canvas.restore();
复制代码
(15) 每次将Canvas旋转10度,然后画一个标记或者一个值,直到画完翻转值表盘为止。当完成表盘之后,把Canvas恢复为正上方的方向。
java代码:
- canvas.save();
- canvas.rotate(180, center.x, center.y);
- for (int i = -180; i < 180; i += 10) {
- // 每30度显示一个数字值
- if (i % 30 == 0) {
- String rollString = String.valueOf(i*-1);
- float rollStringWidth = textPaint.measureText(rollString);
- PointF rollStringCenter = new PointF(center.x-rollStringWidth / 2, innerBoundingBox.top+1+textHeight);
- canvas.drawText(rollString, rollStringCenter.x, rollStringCenter.y, textPaint);
- }
- // 否则,绘制一个标记直线
- else { canvas.drawLine(center.x, (int)innerBoundingBox.top, center.x, (int)innerBoundingBox.top + 5, markerPaint);
- }
- canvas.rotate(10, center.x, center.y);
- }
- canvas.restore();
Android 画布绘图的更多相关文章
- Android SurfaceView 绘图覆盖刷新及脏矩形刷新方法
http://www.cnblogs.com/SkyD/archive/2010/11/08/1871423.html Android SurfaceView 绘图覆盖刷新及脏矩形刷新方法 Surfa ...
- Android 2D绘图初步
Android是通过graphics类来显示2D图形的.其中graphics中包括了Canvas.Paint.Color.Bitmap等类.graphics具有绘制点.线.颜色.2D几何图形.图像处理 ...
- Android开发——绘图基础
前言: Android中绘图基本三个类,分别是Paint(画笔),Path(路径),Canvas(画布),这三个也是自定义View经常会使用到的类 个人理解,Canvas画布这个翻译其实不太好,这个类 ...
- 【转】Android Canvas绘图详解(图文)
转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html Android Canvas绘图详解(图文) 泡 ...
- Android Drawable绘图学习笔记(转)
如何获取 res 中的资源 数据包package:android.content.res 主要类:Resources Android SDK中的简介:Class for accessing an ap ...
- Android Canvas 绘图
画布(Canvas)是图形编程中一个很普通的概念,通常由三个基本的绘图组件组成: Canvas 提供了绘图方法,可以向底层的位图绘制基本图形. Paint 也称为" ...
- android xfermode绘图
1.fermode:是来自 transfer-modes,在画笔中设置xfermode后,画笔在使用的时候就会应用设置的像素转换模式.就类似于ps中的选取功能,留下的选取是从原选区中去除还是合并,取交 ...
- Android - View绘图原理总结
Android系统的视图结构的设计也采用了组合模式,即View作为所有图形的基类,Viewgroup对View继承扩展为视图容器类,由此就得到了视图部分的基本结构--树形结构 View定义了绘图的 ...
- Android使用绘图Path总结
Path作为Android中一种相对复杂的绘图方式,官方文档中的有些解释并不是很好理解,这里作一个相对全面一些的总结,供日后查看,也分享给大家,共同进步. 1.基本绘图方法 addArc(RectF ...
随机推荐
- R语言学习笔记:SQL操作
虽然R很强大,但如果对SQL非常熟悉,也不能浪费这项技能了,可以用上sqldf包,从example("sqldf")抄了几条用法放在这里,以后可能会用上. library(&quo ...
- AVL树详解
AVL树 参考了:http://www.cppblog.com/cxiaojia/archive/2012/08/20/187776.html 修改了其中的错误,代码实现并亲自验证过. 平衡二叉树(B ...
- GET/POST请求的使用《极客学院 --AFNetworking 2.x 网络解析详解--2》学习笔记
AFNetworking是开源代码排名第一的开源库. GET请求的请求正文 一般都是明文显示的,携带的数据量小. POST用于处理复杂的业务,并不用明文的请求,其实POST请求可以携带更多的参数,只 ...
- Swift语言与Objective-C语言混合编程
首先创建一个Swift的Single View工程 然后直接在工程中新建OC文件: 然后选择OC语言之后会问你是否自动创建OC和Swift的中间文件: 然后工程文件夹里就有了三个文件: 现在OC头文件 ...
- android 之 ExpandableListView列表中的列表
有时候,我们需要设计这样一个界面,外面有一个列表,当我们点击其中列表中的某个条目时,就会展开这个条目,出现一个新的列表.比如下图:(程序运行的效果图,在这里贴出来) 当我们点击第一项时,视图变为: - ...
- 关于ajax请求数据后,数据本身的js失效的一些想法
今天遇到一个头疼的问题.我做一个左右翻页效果(客户要求能够无限翻页),所以只能动态请求数据,进行局部刷新操作. 这时候问题就出来了,当我请求翻页的时候,数据通过js填充到div里面,但这些数据,自身带 ...
- sublime text 3 修改文件保存编码
File->Save With Encoding->UTF-8 with BOM
- 搭建openvpn 未完成。。。
轻松构建自己的OpenVPN家庭服务器(VMware+Amahi) http://os.51cto.com/art/201107/277146_all.htm 这是教程 不用安装第一步的,直接把下载 ...
- Xcode模拟器和真机生成的日志查看(转载)
在进行实际代码开发的过程中,我们会生成一些plist文件,但是如何在调试过程中查看这些plist文件是否被成功生成以及生成的内容是否正确? 如果查看模拟器生成的日志和真机生成的日志到底如何查看? DE ...
- 一个超复杂的间接递归——C语言初学者代码中的常见错误与瑕疵(6)
问题: 问题出处见 C语言初学者代码中的常见错误与瑕疵(5) . 在该文的最后,曾提到完成的代码还有进一步改进的余地.本文完成了这个改进.所以本文讨论的并不是初学者代码中的常见错误与瑕疵,而是对我自己 ...