使用Kotlin实现手机传感器实时折线图:数据重合X轴的解决方法
问题分析与解决方案
核心问题原因
你的代码存在两个关键错误,导致数据集中在同一X轴:
second变量作用域错误:每次onSensorChanged回调时,你都重新定义局部变量second并初始化为0,同时重复启动timer。这意味着所有数据点的X值始终无法正确递增,全部集中在初始值附近。- 数据集创建方式错误:每次传感器回调都新建
LineDataSet并添加到LineData,不仅会导致图例混乱,还会让所有新数据点都绑定到同一个无效的X值上。
修复后的完整代码
package com.example.ex_linear_acc_graph import android.content.Context import android.graphics.Color import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.Handler import android.os.Looper import android.widget.TextView import com.github.mikephil.charting.charts.LineChart import com.github.mikephil.charting.components.Legend import com.github.mikephil.charting.components.XAxis import com.github.mikephil.charting.components.YAxis import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.data.LineDataSet class linearActivity : AppCompatActivity(), SensorEventListener { private val mSensorManager by lazy { getSystemService(Context.SENSOR_SERVICE) as SensorManager } private lateinit var chart: LineChart private lateinit var data: LineData private var second: Float = 0f private lateinit var xDataSet: LineDataSet private lateinit var yDataSet: LineDataSet private lateinit var zDataSet: LineDataSet private var updateHandler: Handler? = null private var updateRunnable: Runnable? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_linear) chart = findViewById(R.id.chart_line) chart.description.isEnabled = false chart.setTouchEnabled(true) chart.isDragEnabled = true chart.setScaleEnabled(true) chart.setPinchZoom(false) chart.legend.form = Legend.LegendForm.LINE chart.xAxis.position = XAxis.XAxisPosition.BOTTOM chart.xAxis.setDrawLabels(true) chart.xAxis.axisMaximum = 10f // 初始显示10秒范围 chart.xAxis.axisMinimum = 0f chart.xAxis.setDrawAxisLine(true) chart.xAxis.setDrawGridLines(false) chart.axisRight.isEnabled = false chart.legend.textColor = Color.WHITE chart.animateXY(2000, 2000) // 初始化三个轴的数据集,分别对应X/Y/Z xDataSet = LineDataSet(null, "Mag X").apply { color = Color.RED lineWidth = 2f setDrawCircles(false) } yDataSet = LineDataSet(null, "Mag Y").apply { color = Color.GREEN lineWidth = 2f setDrawCircles(false) } zDataSet = LineDataSet(null, "Mag Z").apply { color = Color.BLUE lineWidth = 2f setDrawCircles(false) } data = LineData(xDataSet, yDataSet, zDataSet) chart.data = data } override fun onResume() { super.onResume() // 注册磁场传感器 mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)?.also { magneticField -> mSensorManager.registerListener( this, magneticField, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI ) } // 启动定时任务更新X轴时间值,运行在主线程 updateHandler = Handler(Looper.getMainLooper()) updateRunnable = object : Runnable { override fun run() { second++ // 动态调整X轴范围,保持显示最近10秒的数据 if (second > chart.xAxis.axisMaximum) { chart.xAxis.axisMaximum = second chart.xAxis.axisMinimum = second - 10 } updateHandler?.postDelayed(this, 1000) } } updateHandler?.postDelayed(updateRunnable!!, 1000) } override fun onPause() { super.onPause() mSensorManager.unregisterListener(this) // 停止定时任务,避免内存泄漏 updateHandler?.removeCallbacks(updateRunnable!!) updateHandler = null updateRunnable = null } override fun onSensorChanged(event: SensorEvent) { if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) { val mag_x = event.values[0] val mag_y = event.values[1] val mag_z = event.values[2] val tv_mag: TextView = findViewById(R.id.tv_linear_acc) tv_mag.text = "mag_x : ${mag_x}\nmag_y : ${mag_y}\nmag_z : ${mag_z}" // 向对应数据集添加新数据点 xDataSet.addEntry(Entry(second, mag_x)) yDataSet.addEntry(Entry(second, mag_y)) zDataSet.addEntry(Entry(second, mag_z)) // 通知图表更新 data.notifyDataChanged() chart.notifyDataSetChanged() chart.invalidate() } } override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { // 无需处理 } }
关键修改点说明
- 将
second改为类成员变量:确保计时在整个Activity生命周期中持续递增,不会每次回调重置。 - 初始化固定数据集:为X/Y/Z轴分别创建独立的
LineDataSet,避免重复添加数据集导致的图例混乱。 - 定时任务移至
onResume:在页面可见时启动计时,onPause中停止,避免资源浪费和内存泄漏。 - 动态调整X轴范围:保持图表始终显示最近10秒的数据,提升可读性。
线程相关说明
- 传感器回调默认运行在主线程(因为在Activity主线程注册),所以更新UI(TextView和图表)可以直接进行,无需额外线程。
- 如果需要处理复杂的数据计算(比如滤波、转换),可以在
onSensorChanged中启动协程,在IO线程处理数据后,切换回主线程更新UI,示例如下:
override fun onSensorChanged(event: SensorEvent) { if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) { val mag_x = event.values[0] val mag_y = event.values[1] val mag_z = event.values[2] // 协程处理复杂计算 kotlinx.coroutines.GlobalScope.launch(kotlinx.coroutines.Dispatchers.IO) { // 这里执行耗时计算,比如数据滤波 val processedX = mag_x * 0.8f + ... // 切换回主线程更新UI launch(kotlinx.coroutines.Dispatchers.Main) { xDataSet.addEntry(Entry(second, processedX)) // 其他UI更新操作 } } } }
内容的提问来源于stack exchange,提问作者이건웅
相关产品推荐
相关产品推荐

