65.9K
CodeProject 正在变化。 阅读更多。
Home

Android: 创建旋转指针

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (15投票s)

2014 年 9 月 19 日

CPOL

6分钟阅读

viewsIcon

38261

本文介绍在 Android 中使用 View 和 Graphics 创建简单指针的步骤。类似于速度计中的指针。

引言

有时会出现这种情况,Android 开发者需要创建仪表盘、指南针、速度计等。其中一个组件就是指针。在本教程中,我们将解释如何绘制一个简单的指针以及如何使其旋转。我们将使用 Android GraphicsView 类。 

背景

当然,您需要对 Android Graphics、其组件/功能/方法以及 View 类有所了解。尽管我会解释每一步,以便您在阅读本教程/文章时无需频繁查阅 Android 文档/Google。您还应该熟悉如何创建 Android 项目:)

使用代码

本文包含创建指针的步骤。在某些步骤中,您可能会发现更好的方法或一些硬编码的值,但这只是为了方便理解。当然,您可以编写比我更好的代码。非常欢迎您的反馈/改进。让我们开始吧。

步骤 1

使用默认设置创建一个新的 Android 应用程序项目。创建一个您选择名称的独立包。我在我的项目中将其命名为 com.needle.views。现在,在 com.needle.views 中创建一个类,并为其命名。我将其命名为 Needle。因此,我有一个类 Needle.java

步骤 2: 

当您在 Android 中创建一个空类时,默认情况下它继承自父类 Object。您需要将其扩展自 View

public class Needle extends View {

请注意,View 类位于 `android.view.View` 包中。因此,请在代码中导入此包。Intellisense 会为您完成此操作。如果不行,请按 Ctrl+Shift+O

import android.view.View;

public class Needle extends View {

这会在类名上显示错误,强制您编写构造函数。需要至少一个构造函数。我已经编写了所有三个构造函数。

public Needle(Context context) {
        super(context);
    }

public Needle(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

public Needle(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

步骤 3

当您编写自己的控件或在 View 中处理任何图形时,需要重写 `onDraw(Canvas canvas)` 方法。根据 Android 文档,当您想要渲染您的 View 时,必须重写此方法。您必须为自己的绘图实现此方法。因此,请重写此方法。 

  • 在代码编辑器中的任何位置右键单击,然后选择 Source -> Override/Implement Methods...
  • 将打开一个弹出窗口。找到 `onDraw(Canvas)` 方法。选择它,然后单击 OK

它将为您放置代码片段。

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

Canvas 是您绘制不同形状的地方。它提供了大量绘制不同形状的方法。我们将使用它的 path 方法来绘制路径。 

步骤 4

现在我们将绘制线条,使其形成速度计指针的形状。我们需要两个主要组件来完成这项工作。首先,一个 Path,它需要点来连接线条;其次,一个 Paint,它将用于着色、样式等。 

 

Paint 类包含有关如何绘制几何图形、文本和位图的样式和颜色信息。

因此,让我们实例化两个 `Path` 和 `Paint` 类型的对象。

private Paint linePaint;
private Paint needleScrewPaint;
private Path linePath;

初始化这些对象。我建议您编写一个 `init()` 方法,并在其中完成所有初始化。并在 构造函数 中调用 `init()` 方法。

public Needle(Context context) {
        super(context);
        init(); // Also add call to this method in other overloaded constructors.
    }
private void init() {
        linePaint = new Paint();

        linePath = new Path();

        needleScrewPaint = Paint();  
    }

步骤 5

首先,设置颜色和样式信息。`linePaint` 对象保存了我们指针的颜色和样式信息。 

linePaint = new Paint();
linePaint.setColor(Color.RED); // Set the color
linePaint.setStyle(Paint.Style.STROKE); // We will change it at the end.
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth(5.0f); // width of the border
linePaint.setShadowLayer(8.0f, 0.1f, 0.1f, Color.GRAY); // Shadow of the needle

您可以更改这些值并进行尝试。我建议您更改这些值并查看结果,并观察如何根据您的需求调整这些值。 

步骤 6

现在初始化 `linePath`。我们将绘制线条,最终形成指针形状。

linePath = new Path(); 
linePath.moveTo(50.0f, 50.0f); // line starting point

`moveTo(float x, float y)` 方法是线条的起点。请注意,为了简单起见和更好地理解,我硬编码了 **x,y** 坐标。您可以根据屏幕大小调整这些值。只需尝试一下即可。 

现在我们需要定义下一个点。一旦定义了下一个点,我们就能在设备上看到线条。`lineTo(float x, float y)` 方法将完成这项工作。请注意,这里的 x,y 是终点。线条将从起点绘制到该点。

linePath.moveTo(50.0f, 50.0f);
linePath.lineTo(130.0f, 40.0f); // next end point
linePath.lineTo(600.0f, 50.0f); // next end point
linePath.lineTo(130.0f, 60.0f); // .....
linePath.lineTo(50.0f, 50.0f); // ...... 
linePath.close();

请注意,我调用了相同的 4 次方法。每次调用此方法时,终点都不同,路径会继续绘制。您可以在设备/模拟器上进行检查。在测试之前,您需要做两件事。在 onDraw(Canvas) 方法中,调用 `canvas.drawPath(Path, Paint)` 方法,因为 **onDraw(Canvas)** 会将其渲染到设备屏幕上。

@Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // draw path   
        canvas.drawPath(linePath, linePaint);
    }

在 Activity 的 xml 布局中,添加此控件:注意 包名 和类名。

<com.needle.views.Needle
        android:id="@+id/myNeedle"
        android:layout_marginTop="10dp"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"/>

当您在模拟器/设备上运行时,输出将如下所示:

步骤 7

现在我们将添加一个圆。这个圆将使它看起来更优雅。要填充此指针形状,请将 `linePaint.setStyle(Paint.Style.STROKE)` 更改为 `linePaint.setStyle(Paint.Style.FILL_AND_STROKE)`。 `linePath` 对象包含一个 `addCircle(x, y, radius, direction)` 方法,该方法将圆添加到线条路径中。该方法非常简单。但请在 `close();` 方法之前添加它。

.....
linePath.addCircle(130.0f, 50.0f, 20.0f, Path.Direction.CW); 
linePath.close();

它将看起来像这样:

测试: 尝试设置 **linePaint.setStyle(Paint.Style.STROKE)** 并查看指针的形状。

我们差不多完成了。只需要修改这个圆。实际上,我们将添加一个新的圆,其半径小于当前圆,并且中心坐标相同,然后应用另一个画笔属性。这就是我们使用画笔对象 `lineScrewPaint` 的原因。

在 `init()` 方法中,初始化 `lineScrewPaint` 并设置其值。

needleScrewPaint = new Paint();
needleScrewPaint.setColor(Color.BLACK);
needleScrewPaint.setAntiAlias(true);
needleScrewPaint.setShader(new RadialGradient(130.0f, 50.0f, 10.0f,
                Color.DKGRAY, Color.BLACK, Shader.TileMode.CLAMP));

它与 `linePaint` 相同,但具有不同的值以及新的内容,例如 ShaderRadialGradient。Shader 用于在视图上设置渐变颜色。尝试玩这些值,您会喜欢的。现在,在 onDraw() 方法中,在 `canvas.drawPath(linePath, linePaint);` 之后添加以下方法调用:

canvas.drawCircle(130.0f, 50.0f, 16.0f, needleScrewPaint);

它将如下所示

所以,这就是我们的指针,准备使用。它非常简单。没有涉及到什么高超的技术。 

步骤 8

动画帮助来自 Stackoverflow

现在我们要让它旋转。使用 Matrix 和三个变量

private Matrix matrix;
private int framePerSeconds = 100;
private long animationDuration = 10000;
private long startTime;

在 `init()` 或构造函数中初始化它们。 

public Line(Context context) {
        super(context);

        matrix = new Matrix();
        this.startTime = System.currentTimeMillis();
        this.postInvalidate();
        init();
}

现在,如下修改 onDraw(Canvas) 方法:

long elapsedTime = System.currentTimeMillis() - startTime;

matrix.postRotate(1.0f, 130.0f, 50.0f); // rotate 1o every second
canvas.concat(matrix);
        
canvas.drawPath(linePath, linePaint);
        
canvas.drawCircle(130.0f, 50.0f, 16.0f, 

if(elapsedTime < animationDuration){
            this.postInvalidateDelayed(10000 / framePerSeconds);
        }
        
//this.postInvalidateOnAnimation();
invalidate();

请注意,在 `matrix.postRotate(... , 130.0f, 50.0f)` 方法中,我们有三个参数。第一个是旋转的角度,其余两个参数是轴。这两个参数必须与您在向线条添加圆时设置的圆心值相同。例如:`linePath.addCircle(130.0f, 50.0f, ...)`。

`canvas.concat(matrix)` 方法将矩阵附加到 canvas。 

最后,`invalidate()`,此方法是必需的。

这是 **Needle.java** 类的完整代码。

package com.needle.views;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;

public class Needle extends View {

    private Paint linePaint;
    private Path linePath;
    private Paint needleScrewPaint;
    
    private Matrix matrix;
    private int framePerSeconds = 100;
    private long animationDuration = 10000;
    private long startTime;
    
    public Needle(Context context) {
        super(context);
        matrix = new Matrix();
        this.startTime = System.currentTimeMillis();
        this.postInvalidate();
        init();
    }
    
    public Needle(Context context, AttributeSet attrs) {
        super(context, attrs);
        matrix = new Matrix();
        this.startTime = System.currentTimeMillis();
        this.postInvalidate();
        init();
    }

    public Needle(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        matrix = new Matrix();
        this.startTime = System.currentTimeMillis();
        this.postInvalidate();
        init();
    }

    private void init(){
        
        linePaint = new Paint();
        linePaint.setColor(Color.RED); // Set the color
        linePaint.setStyle(Paint.Style.FILL_AND_STROKE); // set the border and fills the inside of needle
        linePaint.setAntiAlias(true);
        linePaint.setStrokeWidth(5.0f); // width of the border
        linePaint.setShadowLayer(8.0f, 0.1f, 0.1f, Color.GRAY); // Shadow of the needle
        
        linePath = new Path();
        linePath.moveTo(50.0f, 50.0f);
        linePath.lineTo(130.0f, 40.0f);
        linePath.lineTo(600.0f, 50.0f);
        linePath.lineTo(130.0f, 60.0f);
        linePath.lineTo(50.0f, 50.0f);
        linePath.addCircle(130.0f, 50.0f, 20.0f, Path.Direction.CW);
        linePath.close();
        
        needleScrewPaint = new Paint();
        needleScrewPaint.setColor(Color.BLACK);
        needleScrewPaint.setAntiAlias(true);
        needleScrewPaint.setShader(new RadialGradient(130.0f, 50.0f, 10.0f,
                Color.DKGRAY, Color.BLACK, Shader.TileMode.CLAMP));
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        
        
        long elapsedTime = System.currentTimeMillis() - startTime;

        matrix.postRotate(1.0f, 130.0f, 50.0f); // rotate 10 degree every second
        canvas.concat(matrix);
        
        canvas.drawPath(linePath, linePaint);
        
        canvas.drawCircle(130.0f, 50.0f, 16.0f, needleScrewPaint);
        
        if(elapsedTime < animationDuration){
            this.postInvalidateDelayed(10000 / framePerSeconds);
        }
        
        //this.postInvalidateOnAnimation();
        invalidate();
    }

}

关注点

正如我们所见,从简单和从头开始绘制内容有多么容易。这使得学习速度更快。在本教程中,我使用了硬编码的值来创建指针。您应该应用动态值。您还应该考虑 `onSizeChanged(int, int, int, int)` 方法。您可能会在构造函数中发现重复的代码,请忽略它,并将其放在 `init()` 方法中。 :)

感谢阅读。

请发送您的反馈/建议和改进意见。 

© . All rights reserved.