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");

复制代码
            或者,也可以使用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);

复制代码

            或者,你还可以定义更复杂的按照设定比例进行分布的颜色序列,如下面的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);

复制代码

        每一种渐变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);

复制代码


       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>



  16.         
复制代码

(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)));

复制代码

        (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);

复制代码

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

java代码:
  1.     int markWidth = radius / 3; int startX = center.x - markWidth; int endX = center.x + markWidth;

复制代码

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

java代码:
  1.     double h = innerRadius*Math.cos(Math.toRadians(90-tiltDegree)); double justTiltY = center.y - h;

复制代码

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

java代码:
  1.     float pxPerDegree = (innerBoundingBox.height()/2)/45f;

复制代码

        (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) 现在,在大地/天空接口处绘制一条更粗的线。在画线之前,改变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);

复制代码

        (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);

复制代码

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

java代码:
  1.     canvas.restore();

复制代码

            (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();
复制代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/59547.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android——(仿微信聊天界面布局实例)

今天看郭霖《第一行代码》书上写了一个聊天窗体的小例子&#xff0c;自己就练习学了一下。加上一些自己的理解整理了一下。 1.第一步首先是制作9.patch图片&#xff0c;这个在android sdk 目录下tools文件&#xff0c;找到draw9patch.bat文件双击打开。这是一个专门用来处理安…

Android MPAndroidChart超漂亮图表框架

Android MPAndroidChart超漂亮图表框架 简介配置示例总结 Blog如有不对&#xff0c;敬请斧正 喜欢Android的可以关注我&#xff0c;日常更新Android干货简介 MPAndroidChart是GitHub中大名鼎鼎的一个开源框架&#xff0c;可以快速制作开发中需要的图表&#xff0c;如:折线图、…

【安卓基础】03 实现简单聊天界面

编写一个简单的聊天界面&#xff0c;例子来自《第一行代码》。 1. 编写主界面 聊天界面使用 RecyclerView&#xff0c;简单来说是谷歌新增的控件&#xff0c;与 ListView 类似&#xff0c;但是功能更加强大。比如 ListView 只能纵向华东&#xff0c;但是 RecyclerView 可以设…

安卓 类微信界面实现

目录 1.引言 2.实现 1.写出顶部和底部的layout 2.主页面 3.写四个fragment用于显示不同的内容 4.写MainActivity中的代码 3.总结 1.引言 这是我第一次接触安卓的开发&#xff0c;因为之前学过c#和java&#xff0c;感觉使用AS开发就像使用java在写winform&#xff0c;同样…

安卓开发之设计微信界面

一、功能需求 完成一个类似微信页面的布局&#xff0c;要求&#xff1a; 页面最上方是标题居中页面中间界面显示内容&#xff0c;内容随下方栏的选择而切换页面最下方有四个按钮点击按钮后&#xff0c;按钮图标会变换颜色&#xff0c;且显示框变换内容 项目大致框架如下&…

干货!几招教你降低论文重复率!!

相信大家已经开始为毕业论文奋战了&#xff0c;甚至有的学校已经答辩结束了。今天&#xff0c;小编就说说史上最奇葩智能系统——知网论文检测系统&#xff0c;并且告诉大家躲避查重的几个大招。 各个学校对所谓论文原创度的标准要求不同&#xff0c;碰到要求重复率在30%或20%以…

最简单的 6 种防止数据重复提交的方法!(干货)

有位朋友&#xff0c;某天突然问磊哥&#xff1a;在 Java 中&#xff0c;防止重复提交最简单的方案是什么&#xff1f; 这句话中包含了两个关键信息&#xff0c;第一&#xff1a;防止重复提交&#xff1b;第二&#xff1a;最简单。 于是磊哥问他&#xff0c;是单机环境还是分布…

如何降重计算机SCI论文的重复率? - 易智编译EaseEditing

介绍几种降重方法&#xff1a; 1“中英中”互换法 用翻译软件先翻译成英文&#xff0c; 再翻译回中文&#xff0c;之后手工修改润色一下&#xff0c; 这样换了一种表达句式&#xff0c;但愿文意思没有改变。 值得一提的是&#xff0c;现在中英文互译软件已经非常强大了&…

对文本文件中出现的词进行次数统计

主要涉及读取文本文件、统计词出现的数目、排序、隐藏函数lambda。 随便从网页复制一篇文章保存成“train.txt”&#xff0c;采用以下代码读取txt文件&#xff1a; def load_stop_words(file "stopwords.txt"):with open(file,"r",encoding"utf-8&…

最简单的6种防止数据重复提交的方法!(干货)

有位朋友&#xff0c;某天突然问磊哥&#xff1a;在 Java 中&#xff0c;防止重复提交最简单的方案是什么&#xff1f; 这句话中包含了两个关键信息&#xff0c;第一&#xff1a;防止重复提交&#xff1b;第二&#xff1a;最简单。 于是磊哥问他&#xff0c;是单机环境还是分布…

马斯克成立人工智能公司X.AI:对抗ChatGPT 已买1万个GPU

雷递网 雷建平 4月15日 根据内华达州的一份文件&#xff0c;特斯拉CEO埃隆马斯克 (Elon Musk) 已经成立了一家名为X.AI Corp的新人工智能公司。马斯克为X.AI Corp的唯一董事&#xff0c;而贾里德伯查尔&#xff0c;马斯克家族办公室的董事则是其秘书。 X.AI已允许出售这家私人持…

十大网络安全上市公司分析,让我们重点聊聊F5

网络安全上市厂商业务广泛分布于网络安全硬件、软件&#xff0c;网络安全服务等板块&#xff0c;总体来看&#xff0c;十大网络安全上市公司的竞争可谓是如火如荼。今天让我们把目光集中在F5&#xff0c;这个能为我们所有人创造更安全的数字世界的企业&#xff0c;在应用及API交…

基于blinker的 microPython 小爱同学

官方没有基于esp8266 esp32的microPython 的SDK 翻了 c源码 照葫芦画瓢画出了 这个 100行左右 里面有个设置设备类型的链接 需要手动设置一次 然后就可以进米家绑定其他设备同步到小爱同学啦 比官方的几百K缩小了很多 不过在官方源码翻协议倒是翻了一晚上。 https://download…

集美大学及集美大学诚毅学院的课表导入小爱同学

引言 由于学校教务系统在使用上存在一定的不便&#xff0c;无法在手机上简单地查看课程表&#xff0c;往往都需要通过截图的形式在手机上保存以供上课过程中查阅。本教程将介绍一种将集美大学及集美大学诚毅学院的课程表导入到小爱同学中的方法。 选择小爱课程表的理由是觉得市…

基于微信小程序的网上订餐系统 报告+任务书+开题报告+文献综述+中期PPT+外文翻译及原文+PPT+项目源码及数据库文件

摘要 随着微信小程序的飞速发展&#xff0c;很多系统随之兴起&#xff0c;微信已经是我们生活中的一部分&#xff0c;可不单单是人们用于沟通聊天的工具。还有很多公告平台、小程序也随之发展。大部分公众平台都只起到了一个信息消息的推送或者浏览的作用&#xff0c;而小程序的…

蚌埠学院教务系统自动导入课程表到小米/Redmi手机小爱同学课程表使用说明

文章目录 蚌埠学院教务系统自动导入课程表到小米/Redmi手机小爱同学课程表详细教学视频演示一、自我介绍二、使用说明1.使用条件2.读入数据 总结and已知问题 蚌埠学院教务系统自动导入课程表到小米/Redmi手机小爱同学课程表 蚌埠学院教务系统自动导入课程表到小米/Redmi手机小…

基于Javamail的邮件收发系统(系统+论文+开题报告+任务书+外文翻译+文献综述+答辩PPT)

毕业设计&#xff08;论文&#xff09; &#xff08; 20 届&#xff09; 论文&#xff08;设计&#xff09;题目 基于Javamail的邮件收发系统 作 者 二级学院、专业 班 级 指导教师&#xff08;职称&#xff09; 论 文 字 数 论文完成时间 20年月日 基于JavaMail的邮件…

电脑打不开网页,能ping通,能上QQ,解决办法。

方法一 修改网络配置 因为网络配置不正确无法访问外网的情况。 解决办法&#xff1a; WINR —>在运行里面输入cmd 输入 ipconfig 查看网络配置是否正确 如果不正确&#xff0c;右击网络–>属性–>更改适配器设置–>右击你现在连接的外网的–>属性–>双击…

计算机微信接收excel打不开怎么回事,电脑端微信打不开怎么解决

电脑端微信大家相信大家都用过了&#xff0c;但是有时候出现打不开情况怎么解决呢。下面由学习啦小编为你整理了电脑端微信网页版打不开怎么办的相关方法&#xff0c;希望对你有帮助! 电脑端微信网页版打不开解决方法如下 打开浏览器&#xff0c;点“工具”→“管理加载项”那里…

微信粤语语音转文字 讯飞输入法更懂粤语直出文字

微信语音转文字功能在一定程度上缓解了语音消息的压力。但是&#xff0c;如果对方讲的是方言怎么办&#xff1f;日前&#xff0c;腾讯微信团队发微博称&#xff0c;广东地区用户支持粤语语音转文字功能。然而广东以外的广东人怎么办&#xff1f;网友们表示&#xff1a;用讯飞输…