You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Kotlin实现手机传感器实时折线图:数据重合X轴的解决方法

问题分析与解决方案

核心问题原因

你的代码存在两个关键错误,导致数据集中在同一X轴:

  1. second变量作用域错误:每次onSensorChanged回调时,你都重新定义局部变量second并初始化为0,同时重复启动timer。这意味着所有数据点的X值始终无法正确递增,全部集中在初始值附近。
  2. 数据集创建方式错误:每次传感器回调都新建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) {
        // 无需处理
    }
}

关键修改点说明

  1. 将second改为类成员变量:确保计时在整个Activity生命周期中持续递增,不会每次回调重置。
  2. 初始化固定数据集:为X/Y/Z轴分别创建独立的LineDataSet,避免重复添加数据集导致的图例混乱。
  3. 定时任务移至onResume:在页面可见时启动计时,onPause中停止,避免资源浪费和内存泄漏。
  4. 动态调整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,提问作者이건웅

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 11:37:00