文章的目的为了记录使用java 进行android app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 java android app 开发(一)开发环境的搭建-CSDN博客
开源 java android app 开发(二)工程文件结构-CSDN博客
开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客
开源 java android app 开发(四)GUI界面重要组件-CSDN博客
开源 java android app 开发(五)文件和数据库存储-CSDN博客
开源 java android app 开发(六)多媒体使用-CSDN博客
开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客
开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客
开源 java android app 开发(九)后台之线程和服务-CSDN博客
开源 java android app 开发(十)广播机制-CSDN博客
开源 java android app 开发(十一)调试、发布-CSDN博客
开源 java android app 开发(十二)封库.aar-CSDN博客
开源 java android app 开发(十三)自定义绘图控件--游戏摇杆
开源 java android app 开发(十四)自定义绘图控件--波形图
开源 java android app 开发(十五)自定义绘图控件--仪表盘
开源 java android app 开发(十六)自定义绘图控件--圆环
推荐链接:
开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客
开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客
开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-CSDN博客
开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-CSDN博客
开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客
本章节主要内容是:自定义的仪表盘控件的使用,随机指向速度。
1.代码分析
2.所有源码
3.效果图
一、代码分析
DashboardView 自定义仪表盘视图详细分析
-
类定义和常量声明
public class DashboardView extends View {
// 默认颜色值常量
private static final int DEFAULT_DIAL_COLOR = Color.parseColor("#3F51B5");
private static final int DEFAULT_NEEDLE_COLOR = Color.parseColor("#FF4081");
private static final int DEFAULT_TEXT_COLOR = Color.parseColor("#212121");
private static final int DEFAULT_BG_COLOR = Color.parseColor("#FFFFFF");
功能分析:
继承自View基类,实现自定义视图
定义4个默认颜色常量,使用Material Design配色方案
Color.parseColor()将十六进制颜色字符串转换为整型颜色值
- 成员变量定义
2.1 绘制对象
private Paint dialPaint; // 表盘圆弧画笔
private Paint needlePaint; // 指针画笔
private Paint textPaint; // 文字画笔
private Paint bgPaint; // 背景画笔
2.2 自定义属性
private String title = "仪表盘"; // 标题文本
private int dialColor; // 表盘颜色
private int needleColor; // 指针颜色
private int textColor; // 文字颜色
private int backgroundColor; // 背景颜色
2.3 数据范围
private float currentValue = 0; // 当前显示值
private float minValue = 0; // 最小值
private float maxValue = 100; // 最大值
private float targetValue = 0; // 动画目标值
2.4 动画控制
private boolean isAnimating = false; // 动画状态标志
private static final long ANIMATION_DURATION = 500; // 动画时长500ms
private long animationStartTime = 0; // 动画开始时间戳
- 构造函数
3.1 单参数构造函数
public DashboardView(Context context) {
super(context);
init(null); // 调用初始化方法,无属性集
}
功能: 用于代码动态创建视图时的构造函数
3.2 双参数构造函数
public DashboardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs); // 传入XML属性集
}
功能: 用于XML布局文件中引用的标准构造函数
3.3 三参数构造函数
public DashboardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs); // 包含默认样式属性
}
功能: 支持样式属性的构造函数
-
init() 初始化函数
private void init(AttributeSet attrs) {
// 获取自定义属性
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.DashboardView);
title = a.getString(R.styleable.DashboardView_title);
dialColor = a.getColor(R.styleable.DashboardView_dialColor, DEFAULT_DIAL_COLOR);
needleColor = a.getColor(R.styleable.DashboardView_needleColor, DEFAULT_NEEDLE_COLOR);
textColor = a.getColor(R.styleable.DashboardView_textColor, DEFAULT_TEXT_COLOR);
backgroundColor = a.getColor(R.styleable.DashboardView_backgroundColor, DEFAULT_BG_COLOR);
a.recycle(); // 必须回收TypedArray
} else {
// 使用默认值
dialColor = DEFAULT_DIAL_COLOR;
// ... 其他颜色初始化
}// 初始化表盘画笔 dialPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 启用抗锯齿 dialPaint.setColor(dialColor); dialPaint.setStyle(Paint.Style.STROKE); // 描边模式 dialPaint.setStrokeWidth(10f); // 线宽10像素 // 初始化指针画笔 needlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); needlePaint.setColor(needleColor); needlePaint.setStyle(Paint.Style.FILL_AND_STROKE); // 填充和描边 needlePaint.setStrokeWidth(5f); // 线宽5像素 // 初始化文字画笔 textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); textPaint.setColor(textColor); textPaint.setTextSize(40); // 文字大小40px textPaint.setTextAlign(Paint.Align.CENTER); // 文字居中对齐 // 初始化背景画笔 bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); bgPaint.setColor(backgroundColor); bgPaint.setStyle(Paint.Style.FILL); // 填充模式
}
功能分析:
属性解析: 使用TypedArray读取XML中定义的属性值
资源回收: 必须调用recycle()释放资源
画笔配置: 为不同绘制元素创建专用的Paint对象
抗锯齿: 所有画笔都启用抗锯齿以获得平滑效果5. onDraw() 核心绘制函数
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas); // 调用父类绘制
// 计算基本几何参数
int width = getWidth();
int height = getHeight();
int centerX = width / 2; // 中心点X坐标
int centerY = height / 2; // 中心点Y坐标
int radius = Math.min(width, height) / 2 - 20; // 表盘半径(留出边距)
// 1. 绘制背景圆形
canvas.drawCircle(centerX, centerY, radius + 20, bgPaint);
// 2. 绘制表盘圆弧
RectF oval = new RectF(centerX - radius, centerY - radius,
centerX + radius, centerY + radius);
canvas.drawArc(oval, 150, 240, false, dialPaint); // 从150°开始绘制240°圆弧
// 3. 绘制刻度线和刻度值
drawScale(canvas, centerX, centerY, radius);
// 4. 绘制指针
drawNeedle(canvas, centerX, centerY, radius);
// 5. 绘制标题文字
canvas.drawText(title, centerX, centerY + radius + 60, textPaint);
// 6. 绘制当前数值(加大字号)
textPaint.setTextSize(50); // 临时调整字号
canvas.drawText(String.format("%.1f", currentValue), centerX, centerY + 20, textPaint);
textPaint.setTextSize(40); // 恢复原字号
// 7. 动画处理逻辑
if (isAnimating) {
long currentTime = System.currentTimeMillis();
float elapsed = currentTime - animationStartTime; // 已过去的时间
if (elapsed < ANIMATION_DURATION) {
float progress = elapsed / ANIMATION_DURATION; // 动画进度(0-1)
// 线性插值计算当前值
currentValue = currentValue + (targetValue - currentValue) * progress;
invalidate(); // 请求重绘,实现动画帧
} else {
currentValue = targetValue; // 动画结束,直接跳到目标值
isAnimating = false; // 停止动画
}
}
}
-
drawScale() 刻度绘制函数
private void drawScale(Canvas canvas, int centerX, int centerY, int radius) {
Paint scalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
scalePaint.setColor(textColor);
scalePaint.setTextSize(30);
scalePaint.setTextAlign(Paint.Align.CENTER);// 绘制11个刻度(0-10,包含两端) for (int i = 0; i <= 10; i++) { // 计算刻度角度:起始150° + 每刻度24°(总240°范围) float angle = 150 + (i * 24); // 计算对应数值:最小值 + 等分比例 float value = minValue + (maxValue - minValue) * i / 10; // 将角度转换为弧度 double startAngleRad = Math.toRadians(angle); // 计算刻度线起点(向内偏移20px) int startX = centerX + (int) ((radius - 20) * Math.cos(startAngleRad)); int startY = centerY + (int) ((radius - 20) * Math.sin(startAngleRad)); // 计算刻度线终点(表盘边缘) int endX = centerX + (int) (radius * Math.cos(startAngleRad)); int endY = centerY + (int) (radius * Math.sin(startAngleRad)); // 绘制刻度线 canvas.drawLine(startX, startY, endX, endY, scalePaint); // 计算刻度值文字位置(向内偏移50px) int textX = centerX + (int) ((radius - 50) * Math.cos(startAngleRad)); int textY = centerY + (int) ((radius - 50) * Math.sin(startAngleRad)) + 10; // 绘制刻度数值(取整显示) canvas.drawText(String.valueOf((int)value), textX, textY, scalePaint); }
}
-
drawNeedle() 指针绘制函数
private void drawNeedle(Canvas canvas, int centerX, int centerY, int radius) {
// 计算指针角度:150°对应minValue,390°对应maxValue(实际是150°+240°)
float angle = 150 + (currentValue - minValue) / (maxValue - minValue) * 240;
double angleRad = Math.toRadians(angle); // 转换为弧度// 计算指针终点坐标(向内偏移30px) int endX = centerX + (int) ((radius - 30) * Math.cos(angleRad)); int endY = centerY + (int) ((radius - 30) * Math.sin(angleRad)); // 绘制指针线段(从中心到终点) canvas.drawLine(centerX, centerY, endX, endY, needlePaint); // 绘制中心圆点(半径10px) canvas.drawCircle(centerX, centerY, 10, needlePaint);
}
-
setValue() 数值设置函数(带动画)
public void setValue(float value) {
// 边界检查
if (value < minValue) value = minValue;
if (value > maxValue) value = maxValue;// 设置动画参数 this.targetValue = value; // 目标值 this.isAnimating = true; // 启动动画标志 this.animationStartTime = System.currentTimeMillis(); // 记录开始时间 invalidate(); // 触发重绘,启动动画
}
-
其他设置函数
// 设置数值范围
public void setRange(float min, float max) {
this.minValue = min;
this.maxValue = max;
invalidate(); // 重绘视图
}// 设置标题
public void setTitle(String title) {
this.title = title;
invalidate();
}// 颜色设置函数(都会更新对应画笔颜色并重绘)
public void setDialColor(int color) {
this.dialColor = color;
dialPaint.setColor(color);
invalidate();
}// 获取当前值
public float getCurrentValue() {
return currentValue;
} -
关键设计特点
10.1 动画实现机制
线性插值: 使用currentValue + (targetValue - currentValue) * progress计算过渡值
时间控制: 基于系统时间戳计算动画进度
连续重绘: 通过invalidate()在动画期间持续刷新视图
10.2 几何计算
角度映射: 将数值线性映射到150°-390°的角度范围
坐标转换: 使用三角函数计算圆周上的点坐标
自适应尺寸: 基于视图实际尺寸计算绘制参数
10.3 绘制层次
背景圆形
表盘圆弧
刻度线和数值
指针和中心点
标题和当前值文字
这个自定义视图实现了完整的仪表盘功能,具有良好的可定制性和平滑的动画效果
二、所有源码
文件结构,
DashboardView.java
MainActivity.java
activity_main.xml
attrs.xml
colors.xml

1.DashboardView.java源码
package com.example.dashboard;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
public class DashboardView extends View {
// 默认颜色值
private static final int DEFAULT_DIAL_COLOR = Color.parseColor("#3F51B5");
private static final int DEFAULT_NEEDLE_COLOR = Color.parseColor("#FF4081");
private static final int DEFAULT_TEXT_COLOR = Color.parseColor("#212121");
private static final int DEFAULT_BG_COLOR = Color.parseColor("#FFFFFF");
// 绘制相关对象
private Paint dialPaint;
private Paint needlePaint;
private Paint textPaint;
private Paint bgPaint;
// 自定义属性
private String title = "仪表盘";
private int dialColor;
private int needleColor;
private int textColor;
private int backgroundColor;
// 数据相关
private float currentValue = 0;
private float minValue = 0;
private float maxValue = 100;
private float targetValue = 0;
// 动画相关
private boolean isAnimating = false;
private static final long ANIMATION_DURATION = 500; // 动画持续时间(ms)
private long animationStartTime = 0;
public DashboardView(Context context) {
super(context);
init(null);
}
public DashboardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public DashboardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
// 获取自定义属性
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.DashboardView);
title = a.getString(R.styleable.DashboardView_title);
dialColor = a.getColor(R.styleable.DashboardView_dialColor, DEFAULT_DIAL_COLOR);
needleColor = a.getColor(R.styleable.DashboardView_needleColor, DEFAULT_NEEDLE_COLOR);
textColor = a.getColor(R.styleable.DashboardView_textColor, DEFAULT_TEXT_COLOR);
backgroundColor = a.getColor(R.styleable.DashboardView_backgroundColor, DEFAULT_BG_COLOR);
a.recycle();
} else {
dialColor = DEFAULT_DIAL_COLOR;
needleColor = DEFAULT_NEEDLE_COLOR;
textColor = DEFAULT_TEXT_COLOR;
backgroundColor = DEFAULT_BG_COLOR;
}
// 初始化表盘画笔
dialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dialPaint.setColor(dialColor);
dialPaint.setStyle(Paint.Style.STROKE);
dialPaint.setStrokeWidth(10f);
// 初始化指针画笔
needlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
needlePaint.setColor(needleColor);
needlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
needlePaint.setStrokeWidth(5f);
// 初始化文字画笔
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(textColor);
textPaint.setTextSize(40);
textPaint.setTextAlign(Paint.Align.CENTER);
// 初始化背景画笔
bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bgPaint.setColor(backgroundColor);
bgPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int centerX = width / 2;
int centerY = height / 2;
int radius = Math.min(width, height) / 2 - 20;
// 绘制背景
canvas.drawCircle(centerX, centerY, radius + 20, bgPaint);
// 绘制表盘
RectF oval = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
canvas.drawArc(oval, 150, 240, false, dialPaint);
// 绘制刻度
drawScale(canvas, centerX, centerY, radius);
// 绘制指针
drawNeedle(canvas, centerX, centerY, radius);
// 绘制标题
canvas.drawText(title, centerX, centerY + radius + 60, textPaint);
// 绘制当前值
textPaint.setTextSize(50);
canvas.drawText(String.format("%.1f", currentValue), centerX, centerY + 20, textPaint);
textPaint.setTextSize(40);
// 如果需要动画,继续重绘
if (isAnimating) {
long currentTime = System.currentTimeMillis();
float elapsed = currentTime - animationStartTime;
if (elapsed < ANIMATION_DURATION) {
float progress = elapsed / ANIMATION_DURATION;
currentValue = currentValue + (targetValue - currentValue) * progress;
invalidate();
} else {
currentValue = targetValue;
isAnimating = false;
}
}
}
private void drawScale(Canvas canvas, int centerX, int centerY, int radius) {
Paint scalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
scalePaint.setColor(textColor);
scalePaint.setTextSize(30);
scalePaint.setTextAlign(Paint.Align.CENTER);
for (int i = 0; i <= 10; i++) {
float angle = 150 + (i * 24);
float value = minValue + (maxValue - minValue) * i / 10;
// 计算刻度起点和终点
double startAngleRad = Math.toRadians(angle);
int startX = centerX + (int) ((radius - 20) * Math.cos(startAngleRad));
int startY = centerY + (int) ((radius - 20) * Math.sin(startAngleRad));
int endX = centerX + (int) (radius * Math.cos(startAngleRad));
int endY = centerY + (int) (radius * Math.sin(startAngleRad));
// 绘制刻度线
canvas.drawLine(startX, startY, endX, endY, scalePaint);
// 绘制刻度值
int textX = centerX + (int) ((radius - 50) * Math.cos(startAngleRad));
int textY = centerY + (int) ((radius - 50) * Math.sin(startAngleRad)) + 10;
canvas.drawText(String.valueOf((int)value), textX, textY, scalePaint);
}
}
private void drawNeedle(Canvas canvas, int centerX, int centerY, int radius) {
// 计算指针角度 (150°到390°对应minValue到maxValue)
float angle = 150 + (currentValue - minValue) / (maxValue - minValue) * 240;
double angleRad = Math.toRadians(angle);
// 计算指针终点
int endX = centerX + (int) ((radius - 30) * Math.cos(angleRad));
int endY = centerY + (int) ((radius - 30) * Math.sin(angleRad));
// 绘制指针
canvas.drawLine(centerX, centerY, endX, endY, needlePaint);
// 绘制中心圆点
canvas.drawCircle(centerX, centerY, 10, needlePaint);
}
// 设置当前值(带动画效果)
public void setValue(float value) {
if (value < minValue) value = minValue;
if (value > maxValue) value = maxValue;
this.targetValue = value;
this.isAnimating = true;
this.animationStartTime = System.currentTimeMillis();
invalidate();
}
// 设置数值范围
public void setRange(float min, float max) {
this.minValue = min;
this.maxValue = max;
invalidate();
}
// 设置标题
public void setTitle(String title) {
this.title = title;
invalidate();
}
// 设置表盘颜色
public void setDialColor(int color) {
this.dialColor = color;
dialPaint.setColor(color);
invalidate();
}
// 设置指针颜色
public void setNeedleColor(int color) {
this.needleColor = color;
needlePaint.setColor(color);
invalidate();
}
// 设置文字颜色
public void setTextColor(int color) {
this.textColor = color;
textPaint.setColor(color);
invalidate();
}
// 设置背景颜色
public void setBackgroundColor(int color) {
this.backgroundColor = color;
bgPaint.setColor(color);
invalidate();
}
public float getCurrentValue() {
return currentValue;
}
}
2.MainActivity.java源码
package com.example.dashboard;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private DashboardView dashboard;
private TextView valueText;
private Handler handler = new Handler();
private Random random = new Random();
private float minValue = 0;
private float maxValue = 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dashboard = findViewById(R.id.dashboard);
valueText = findViewById(R.id.valueText);
// 设置仪表盘属性
dashboard.setTitle("速度仪表盘");
dashboard.setDialColor(getResources().getColor(R.color.dialColor));
dashboard.setNeedleColor(getResources().getColor(R.color.needleColor));
dashboard.setTextColor(getResources().getColor(R.color.textColor));
dashboard.setBackgroundColor(getResources().getColor(R.color.backgroundColor));
dashboard.setRange(minValue, maxValue);
// 开始定时更新数据
startDataUpdates();
}
private void startDataUpdates() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
// 生成随机值
float value = minValue + random.nextFloat() * (maxValue - minValue);
// 更新仪表盘
dashboard.setValue(value);
// 更新文本显示
valueText.setText(String.format("当前值: %.1f", value));
// 1秒后再次更新
handler.postDelayed(this, 1000);
}
}, 1000);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 移除所有回调,防止内存泄漏
handler.removeCallbacksAndMessages(null);
}
}
3.activity_main.xml源码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp"
tools:context=".MainActivity">
<com.example.dashboard.DashboardView
android:id="@+id/dashboard"
android:layout_width="300dp"
android:layout_height="300dp"
app:title="速度仪表盘"
app:dialColor="#3F51B5"
app:needleColor="#FF4081"
app:textColor="#212121"
app:backgroundColor="#FFFFFF" />
<TextView
android:id="@+id/valueText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="当前值: 0.0"
android:textSize="18sp" />
</LinearLayout>
4.attrs.xml源码用于主活动设置控件的属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DashboardView">
<attr name="title" format="string" />
<attr name="dialColor" format="color" />
<attr name="needleColor" format="color" />
<attr name="textColor" format="color" />
<attr name="backgroundColor" format="color" />
</declare-styleable>
</resources>
5.colors.xml源码用途添加的各种颜色
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<!-- 仪表盘自定义颜色 -->
<color name="dialColor">#3F51B5</color>
<color name="needleColor">#FF4081</color>
<color name="textColor">#212121</color>
<color name="backgroundColor">#FFFFFF</color>
</resources>
三、效果演示
主活动随机写入数据,指针指向仪表盘的相应位置。
