Android: 创建旋转指针






4.94/5 (15投票s)
本文介绍在 Android 中使用 View 和 Graphics 创建简单指针的步骤。类似于速度计中的指针。
引言
有时会出现这种情况,Android 开发者需要创建仪表盘、指南针、速度计等。其中一个组件就是指针。在本教程中,我们将解释如何绘制一个简单的指针以及如何使其旋转。我们将使用 Android Graphics 和 View 类。
背景
当然,您需要对 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` 相同,但具有不同的值以及新的内容,例如 Shader 和 RadialGradient。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()` 方法中。 :)
感谢阅读。
请发送您的反馈/建议和改进意见。