27、传感器数据可视化:使用Canvas或MPAndroidChart绘制实时传感器数据波形图。
做Android传感器开发,最头疼的是什么?
我个人觉得,是调试阶段看不到数据变化。你写了个距离传感器监听,log里打印出一堆数字,比如5.0、3.2、0.0……光看这些数字,你很难直观感受到传感器在“想什么”。
所以,数据可视化就变得特别重要。说白了,就是把数字变成波形图,一眼就能看出趋势。
这一章,我们就来聊聊怎么在手机上画出实时波形图。我会介绍两种方式:Canvas自绘和MPAndroidChart库。两种我都用过,各有各的适用场景。
27.1 为什么需要实时波形图?
你想想看,距离传感器的数据是连续变化的。手靠近,数值从5降到0;手远离,数值又从0升到5。如果只看log,你只能看到一个个孤立的点。
但波形图不一样。它能展示出整个变化过程。比如,你可以看到数据是否有毛刺、是否有延迟、是否稳定。
我在项目中遇到过一个问题:某款手机的距离传感器在快速靠近时,数据会跳变。光看log根本发现不了,但波形图上一眼就能看到那个“尖刺”。
所以,学会画波形图,是传感器调试的必备技能。
27.2 方案一:使用Canvas自绘波形图
Canvas是Android原生的绘图API。它的好处是轻量、灵活、没有额外依赖。如果你只需要画一个简单的波形,用Canvas就够了。
27.2.1 核心思路
我们需要一个自定义View,在onDraw()方法里不断重绘。数据存储在一个队列里,每次新数据进来,就把旧数据往左推。
嗯,这里要注意:队列长度决定了波形图的宽度。我一般用屏幕宽度除以每个数据点的间距,比如每个点占2像素,屏幕宽1080,那就存540个点。
27.2.2 代码实现
下面是一个简单的实现。我把它封装成了一个WaveformView。
public class WaveformView extends View {
private Paint linePaint;
private Paint gridPaint;
private float[] dataPoints;
private int maxDataPoints;
private int currentIndex = 0;
private float maxValue = 10.0f; // 距离传感器最大距离
public WaveformView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
linePaint = new Paint();
linePaint.setColor(Color.parseColor("#2196F3"));
linePaint.setStrokeWidth(3f);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setAntiAlias(true);
gridPaint = new Paint();
gridPaint.setColor(Color.parseColor("#E0E0E0"));
gridPaint.setStrokeWidth(1f);
// 假设每个数据点占2像素,屏幕宽度为1080
maxDataPoints = 540;
dataPoints = new float[maxDataPoints];
// 初始化为无效值
Arrays.fill(dataPoints, -1f);
}
public void addDataPoint(float value) {
dataPoints[currentIndex] = value;
currentIndex = (currentIndex + 1) % maxDataPoints;
postInvalidate(); // 触发重绘
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawGrid(canvas);
drawWaveform(canvas);
}
private void drawGrid(Canvas canvas) {
float height = getHeight();
float width = getWidth();
// 画水平网格线
for (int i = 0; i <= 4; i++) {
float y = height * i / 4;
canvas.drawLine(0, y, width, y, gridPaint);
}
}
private void drawWaveform(Canvas canvas) {
float height = getHeight();
float width = getWidth();
float stepX = width / maxDataPoints;
Path path = new Path();
boolean firstPoint = true;
for (int i = 0; i < maxDataPoints; i++) {
int index = (currentIndex + i) % maxDataPoints;
float value = dataPoints[index];
if (value < 0) continue; // 跳过无效数据
float x = i * stepX;
float y = height - (value / maxValue) * height;
if (firstPoint) {
path.moveTo(x, y);
firstPoint = false;
} else {
path.lineTo(x, y);
}
}
canvas.drawPath(path, linePaint);
}
}
使用起来也很简单:
WaveformView waveformView = findViewById(R.id.waveform_view);
// 在传感器回调中调用
waveformView.addDataPoint(distance);
postInvalidate()而不是invalidate()。前者可以在非UI线程调用,避免卡顿。
27.3 方案二:使用MPAndroidChart库
如果你需要更专业的图表效果——比如缩放、拖拽、坐标轴标签、动画——那我建议直接用MPAndroidChart。这是一个非常成熟的开源库。
27.3.1 添加依赖
在build.gradle里加上:
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
27.3.2 布局文件
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/lineChart"
android:layout_width="match_parent"
android:layout_height="300dp" />
27.3.3 实时更新数据
核心逻辑和Canvas类似,但MPAndroidChart帮我们处理了坐标轴、标签、动画等细节。
public class ChartHelper {
private LineChart lineChart;
private LineDataSet dataSet;
private LineData lineData;
private int maxVisiblePoints = 100; // 最多显示100个点
public ChartHelper(LineChart chart) {
this.lineChart = chart;
setupChart();
}
private void setupChart() {
// 初始化数据集
dataSet = new LineDataSet(new ArrayList<Entry>(), "距离传感器");
dataSet.setColor(Color.parseColor("#FF5722"));
dataSet.setLineWidth(2f);
dataSet.setDrawCircles(false);
dataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER); // 平滑曲线
lineData = new LineData(dataSet);
lineChart.setData(lineData);
lineChart.getDescription().setEnabled(false);
// 限制可见范围
lineChart.setVisibleXRangeMaximum(maxVisiblePoints);
lineChart.setAutoScaleMinMaxEnabled(true);
}
public void addEntry(float value) {
LineData data = lineChart.getData();
if (data != null) {
ILineDataSet set = data.getDataSetByIndex(0);
if (set == null) {
set = createDataSet();
data.addDataSet(set);
}
// 添加新数据点
data.addEntry(new Entry(set.getEntryCount(), value), 0);
// 通知数据更新
data.notifyDataChanged();
lineChart.notifyDataSetChanged();
// 移动到最新位置
lineChart.setVisibleXRangeMaximum(maxVisiblePoints);
lineChart.moveViewToX(data.getEntryCount() - maxVisiblePoints);
}
}
private ILineDataSet createDataSet() {
LineDataSet set = new LineDataSet(new ArrayList<Entry>(), "距离传感器");
set.setColor(Color.parseColor("#FF5722"));
set.setLineWidth(2f);
set.setDrawCircles(false);
set.setMode(LineDataSet.Mode.CUBIC_BEZIER);
return set;
}
}
在传感器回调里调用:
chartHelper.addEntry(distance);
setVisibleXRangeMaximum()限制可见范围,并且定期清理旧数据。
27.4 两种方案的对比
| 特性 | Canvas自绘 | MPAndroidChart |
|---|---|---|
| 依赖 | 无 | 需引入第三方库 |
| 灵活性 | 极高,可定制任何效果 | 较高,但受限于库的API |
| 功能丰富度 | 低,需自己实现坐标轴、标签等 | 高,自带缩放、拖拽、动画 |
| 性能 | 好,适合高频更新 | 一般,数据量大时需优化 |
| 学习成本 | 需要了解Canvas API | 需要了解库的API |
27.5 核心逻辑流程图
下面这张图展示了实时波形图的核心数据流。从传感器监听,到数据存储,再到UI绘制,每一步都很关键。
27.6 性能优化建议
实时绘制波形图,最怕的就是卡顿。我总结了几条经验:
- 控制数据点数量:不要无限存储。设置一个最大长度,比如500个点。旧数据该丢就丢。
- 减少重绘频率:如果传感器每秒更新100次,你没必要每秒画100次。可以每50ms重绘一次,或者用
postInvalidateOnAnimation()。 - 使用硬件加速:在自定义View的构造器里调用
setLayerType(LAYER_TYPE_HARDWARE, null),能显著提升Canvas绘制性能。 - 避免在onDraw里创建对象:比如Path、Paint这些,最好在初始化时创建好,复用。
好了,这一章的内容就到这里。Canvas和MPAndroidChart两种方式,你都可以试试。我个人习惯是:简单调试用Canvas,正式产品用MPAndroidChart。毕竟,少写代码就是少出bug嘛。
公众号:蓝海资料掘金营,微信deep3321