用 chatGPT 绘制一个 G 值(加速度)动画「不改一行代码」

1,894 阅读11分钟

最近项目需要绘制一个感知 G 值的动画,因为没这方面的经验,如果是以前免不了搜索引擎一顿搜。不过现在跟以前不一样了,我们可以去问 AI(本文指 chatGPT) 了呀,看看 AI 能不能帮我们解决问题。这不问不知道,一问就感受到了 chatGPT 的强大。闲话少说,我们直接复盘这一轮与 chatGPT 的对话,Let's Go!

我:Android平台 我需要绘制一个感知G值变化的视图,需要能感知加速度的方向

在Android平台上绘制一个能够感知加速度方向并显示G值变化的视图,可以通过使用传感器API来实现。以下是一个示例代码,可以帮助你实现这个功能:

1. 配置权限和传感器

首先,在你的AndroidManifest.xml文件中添加使用传感器的权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

2. 创建一个自定义视图来绘制G值变化

创建一个自定义View类,例如GForceView,在其中绘制加速度数据。

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class GForceView extends View {
    private Paint paint;
    private float gX = 0;
    private float gY = 0;
    private float gZ = 0;

    public GForceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(5);
        paint.setTextSize(50);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float centerX = getWidth() / 2;
        float centerY = getHeight() / 2;

        // Draw the g-force lines
        canvas.drawLine(centerX, centerY, centerX + gX * 100, centerY - gY * 100, paint);
        
        // Draw the g-force values
        canvas.drawText("Gx: " + String.format("%.2f", gX), 50, 100, paint);
        canvas.drawText("Gy: " + String.format("%.2f", gY), 50, 200, paint);
        canvas.drawText("Gz: " + String.format("%.2f", gZ), 50, 300, paint);
    }

    public void updateGForce(float x, float y, float z) {
        gX = x;
        gY = y;
        gZ = z;
        invalidate();
    }
}

3. 在Activity中使用传感器并更新视图

在你的Activity中,注册加速度传感器,并在传感器数据变化时更新自定义视图。

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity implements SensorEventListener {
    private SensorManager sensorManager;
    private Sensor accelerometer;
    private GForceView gForceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        gForceView = findViewById(R.id.gForceView);

        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        if (sensorManager != null) {
            accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (accelerometer != null) {
            sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        sensorManager.unregisterListener(this);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            float gX = event.values[0];
            float gY = event.values[1];
            float gZ = event.values[2];
            gForceView.updateGForce(gX, gY, gZ);
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Do something here if sensor accuracy changes
    }
}

4. 设置布局文件

在你的activity_main.xml布局文件中添加自定义视图。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.yourpackage.GForceView
        android:id="@+id/gForceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

这个示例代码实现了一个简单的加速度方向感知和G值变化的视图显示。你可以根据需要进一步调整视图的外观和功能。

chatGPT 回答得很完整,从权限到自定义视图再到如何监听传感器的代码都全部展示,我相信哪怕一个刚接触 Android 的开发者都能正确运行这段代码。我们来看一下运行效果。

p1.gif

嗯嗯,还行,转动手机时能有明确的加速度变化,G值越大线条越长。这就是 chatGPT 自己设计感知加速度的效果,但并不是我们想要的,我们希望是一个球在屏幕上运动,所以已让它调整一下。

我:我希望视图用一个圆表示而不是线条

为了用一个圆表示G值变化的视图,我们需要在自定义视图中绘制一个圆,并根据加速度数据调整圆的位置。以下是更新后的示例代码:

1. 更新自定义视图类

GForceView类中绘制一个圆并根据加速度数据调整其位置。

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class GForceView extends View {
    private Paint paint;
    private float gX = 0;
    private float gY = 0;
    private float gZ = 0;
    private float radius = 50;

    public GForceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float centerX = getWidth() / 2;
        float centerY = getHeight() / 2;

        // Adjust the circle's position based on the g-force values
        float circleX = centerX + gX * 100;
        float circleY = centerY - gY * 100;

        // Draw the circle representing the g-force
        canvas.drawCircle(circleX, circleY, radius, paint);
        
        // Draw the g-force values
        paint.setColor(Color.BLACK);
        paint.setTextSize(50);
        canvas.drawText("Gx: " + String.format("%.2f", gX), 50, 100, paint);
        canvas.drawText("Gy: " + String.format("%.2f", gY), 50, 200, paint);
        canvas.drawText("Gz: " + String.format("%.2f", gZ), 50, 300, paint);
    }

    public void updateGForce(float x, float y, float z) {
        gX = x;
        gY = y;
        gZ = z;
        invalidate();
    }
}

2. 在Activity中使用传感器并更新视图

这个部分和之前相同,保持不变。

3. 设置布局文件

布局文件同样保持不变。

通过上述修改,你将能够在屏幕上用一个圆来表示加速度方向和G值变化。圆的位置根据G值变化而移动,这样就能直观地展示加速度的变化方向。

这个回答仅对GForceView做了调整,我们来看看调整后的效果:

p2.gif

诶,是不是有那味了,圆形小球根据 G 值的变化作出相应的位移。但仔细观察会发现,小球位置便会依赖的应该是重力,因为当我们固定手机时,小球的位置也会固定。

image.png

如上图,手机固定时 G 值并没有归 0 ,而加速度在手机没有速度变化时的值应该是 0 ,所以这边传感器取到的值应该不对,我们继续追问。

我:这个试图看起来是感知的重力,而不是加速度,我需要感知加速度

你提到的现象是因为重力在加速度传感器中的影响。为了感知实际的加速度,我们需要移除重力分量。可以通过使用高通滤波器来实现这一点。下面是一个更新的代码示例:

1. 更新自定义视图类

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class GForceView extends View {
    private Paint paint;
    private float ax = 0;
    private float ay = 0;
    private float az = 0;
    private float radius = 50;

    public GForceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float centerX = getWidth() / 2;
        float centerY = getHeight() / 2;

        // Adjust the circle's position based on the acceleration values
        float circleX = centerX + ax * 100;
        float circleY = centerY - ay * 100;

        // Draw the circle representing the acceleration
        canvas.drawCircle(circleX, circleY, radius, paint);
        
        // Draw the acceleration values
        paint.setColor(Color.BLACK);
        paint.setTextSize(50);
        canvas.drawText("Ax: " + String.format("%.2f", ax), 50, 100, paint);
        canvas.drawText("Ay: " + String.format("%.2f", ay), 50, 200, paint);
        canvas.drawText("Az: " + String.format("%.2f", az), 50, 300, paint);
    }

    public void updateAcceleration(float x, float y, float z) {
        ax = x;
        ay = y;
        az = z;
        invalidate();
    }
}

2. 在Activity中使用传感器并更新视图

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity implements SensorEventListener {
    private SensorManager sensorManager;
    private Sensor accelerometer;
    private GForceView gForceView;

    // Variables for gravity calculation
    private float[] gravity = new float[3];
    private float[] linear_acceleration = new float[3];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        gForceView = findViewById(R.id.gForceView);

        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        if (sensorManager != null) {
            accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (accelerometer != null) {
            sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        sensorManager.unregisterListener(this);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            final float alpha = 0.8f;

            // Isolate the force of gravity with the low-pass filter.
            gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
            gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
            gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];

            // Remove the gravity contribution with the high-pass filter.
            linear_acceleration[0] = event.values[0] - gravity[0];
            linear_acceleration[1] = event.values[1] - gravity[1];
            linear_acceleration[2] = event.values[2] - gravity[2];

            gForceView.updateAcceleration(linear_acceleration[0], linear_acceleration[1], linear_acceleration[2]);
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Do something here if sensor accuracy changes
    }
}

这样修改后,视图将能够感知实际的加速度变化,而不仅仅是重力。通过移除重力分量,你将获得更准确的加速度数据并在视图中表示。

原来是这样,传感器返回的原始数据包含重力和加速度,如果我们只需要加速度则需要将重力因素过滤掉。主要变化体现在onSensorChanged()这个回调,移除了重力分量。 好,我们再看看效果。

p3.gif

emmm,这就是我们想要的效果,在手机不动时 G 值应该为 0。

到这边其实就基本达到我的期望了,剩下的就是一些细微调整。比如说现在移动的幅度比较大,且在数据细微变化时会有抖动,我们也希望能调整下

我:现在视图移动的幅度有点大,能不能降低移动的幅度并且抑制抖动

当然,我们可以通过降低视图移动的幅度和抑制抖动来改善用户体验。为此,我们可以引入以下改进:

  1. 降低移动幅度:通过乘以一个缩放因子来减少视图移动的幅度。
  2. 抑制抖动:引入一个低通滤波器对线性加速度数据进行平滑处理,以减少噪音。

以下是更新后的Kotlin代码示例:

1. 更新自定义视图类

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View

class GForceView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private val paint = Paint().apply {
        color = Color.BLUE
        style = Paint.Style.FILL
    }
    private var ax = 0f
    private var ay = 0f
    private var az = 0f
    private val radius = 75f
    private val scaleFactor = 50f  // 缩放因子,用于减少移动幅度

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val centerX = width / 2
        val centerY = height / 2

        // Adjust the circle's position based on the acceleration values
        val circleX = centerX + ax * scaleFactor
        val circleY = centerY - ay * scaleFactor

        // Draw the circle representing the acceleration
        canvas.drawCircle(circleX, circleY, radius, paint)

        // Draw the acceleration values
        paint.color = Color.BLACK
        paint.textSize = 50f
        canvas.drawText("Ax: %.2f".format(ax), 50f, 100f, paint)
        canvas.drawText("Ay: %.2f".format(ay), 50f, 200f, paint)
        canvas.drawText("Az: %.2f".format(az), 50f, 300f, paint)
    }

    fun updateAcceleration(x: Float, y: Float, z: Float) {
        ax = x
        ay = y
        az = z
        invalidate()
    }
}

2. 在Activity中使用传感器并更新视图

import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity(), SensorEventListener {
    private lateinit var sensorManager: SensorManager
    private var accelerometer: Sensor? = null
    private lateinit var gForceView: GForceView

    // Variables for gravity calculation and low-pass filter
    private val gravity = FloatArray(3)
    private val linearAcceleration = FloatArray(3)
    private val smoothedAcceleration = FloatArray(3)
    private val alpha = 0.8f  // 低通滤波器系数

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        gForceView = findViewById(R.id.gForceView)

        sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
        accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
    }

    override fun onResume() {
        super.onResume()
        accelerometer?.also { accel ->
            sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_UI)
        }
    }

    override fun onPause() {
        super.onPause()
        sensorManager.unregisterListener(this)
    }

    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
            // Isolate the force of gravity with the low-pass filter.
            gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]
            gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]
            gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]

            // Remove the gravity contribution with the high-pass filter.
            linearAcceleration[0] = event.values[0] - gravity[0]
            linearAcceleration[1] = event.values[1] - gravity[1]
            linearAcceleration[2] = event.values[2] - gravity[2]

            // Apply a low-pass filter to smooth the acceleration values
            smoothedAcceleration[0] = alpha * smoothedAcceleration[0] + (1 - alpha) * linearAcceleration[0]
            smoothedAcceleration[1] = alpha * smoothedAcceleration[1] + (1 - alpha) * linearAcceleration[1]
            smoothedAcceleration[2] = alpha * smoothedAcceleration[2] + (1 - alpha) * linearAcceleration[2]

            gForceView.updateAcceleration(smoothedAcceleration[0], smoothedAcceleration[1], smoothedAcceleration[2])
        }
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        // Do something here if sensor accuracy changes
    }
}

通过上述修改,视图的移动幅度会减小,并且通过低通滤波器的平滑处理,可以有效抑制加速度数据中的抖动,使显示更加稳定。

这样确实没有抖动了,移动幅度也没那么大,但是现在的移动是线性的,效果没那么好,需要再优化下

我:现在的移动是线性的,我想要非线性的变化,在g值越大时变化越小

为了实现非线性的变化,使得在G值较大时变化较小,可以使用对数缩放或其他非线性缩放函数来调整加速度值。这种方法可以将较大范围的加速度值压缩到较小的变化范围内。以下是一个使用对数缩放的示例代码:

1. 更新自定义视图类

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import kotlin.math.sign

class GForceView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private val paint = Paint().apply {
        color = Color.BLUE
        style = Paint.Style.FILL
    }
    private var ax = 0f
    private var ay = 0f
    private var az = 0f
    private val radius = 50f
    private val scaleFactor = 50f  // 调整缩放因子

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val centerX = width / 2
        val centerY = height / 2

        // Apply non-linear transformation
        val nonLinearX = nonLinearTransform(ax)
        val nonLinearY = nonLinearTransform(ay)

        // Adjust the circle's position based on the transformed acceleration values
        val circleX = centerX + nonLinearX * scaleFactor
        val circleY = centerY - nonLinearY * scaleFactor

        // Draw the circle representing the acceleration
        canvas.drawCircle(circleX, circleY, radius, paint)

        // Draw the acceleration values
        paint.color = Color.BLACK
        paint.textSize = 50f
        canvas.drawText("Ax: %.2f".format(ax), 50f, 100f, paint)
        canvas.drawText("Ay: %.2f".format(ay), 50f, 200f, paint)
        canvas.drawText("Az: %.2f".format(az), 50f, 300f, paint)
    }

    fun updateAcceleration(x: Float, y: Float, z: Float) {
        ax = x
        ay = y
        az = z
        invalidate()
    }

    private fun nonLinearTransform(value: Float): Float {
        // Apply a non-linear transformation (e.g., logarithmic scaling)
        val sign = sign(value)
        return sign * kotlin.math.log10(1 + kotlin.math.abs(value))
    }
}

2. 在Activity中使用传感器并更新视图

import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity(), SensorEventListener {
    private lateinit var sensorManager: SensorManager
    private var accelerometer: Sensor? = null
    private lateinit var gForceView: GForceView

    // Variables for gravity calculation and low-pass filter
    private val gravity = FloatArray(3)
    private val linearAcceleration = FloatArray(3)
    private val smoothedAcceleration = FloatArray(3)
    private val alpha = 0.8f  // 调整低通滤波器系数以加快响应速度

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        gForceView = findViewById(R.id.gForceView)

        sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
        accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
    }

    override fun onResume() {
        super.onResume()
        accelerometer?.also { accel ->
            sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_UI)
        }
    }

    override fun onPause() {
        super.onPause()
        sensorManager.unregisterListener(this)
    }

    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
            // Isolate the force of gravity with the low-pass filter.
            gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]
            gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]
            gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]

            // Remove the gravity contribution with the high-pass filter.
            linearAcceleration[0] = event.values[0] - gravity[0]
            linearAcceleration[1] = event.values[1] - gravity[1]
            linearAcceleration[2] = event.values[2] - gravity[2]

            // Apply a low-pass filter to smooth the acceleration values
            smoothedAcceleration[0] = alpha * smoothedAcceleration[0] + (1 - alpha) * linearAcceleration[0]
            smoothedAcceleration[1] = alpha * smoothedAcceleration[1] + (1 - alpha) * linearAcceleration[1]
            smoothedAcceleration[2] = alpha * smoothedAcceleration[2] + (1 - alpha) * linearAcceleration[2]

            gForceView.updateAcceleration(smoothedAcceleration[0], smoothedAcceleration[1], smoothedAcceleration[2])
        }
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        // Do something here if sensor accuracy changes
    }
}

通过上述修改,视图的移动将采用非线性变化。当G值较大时,变化幅度将相对减小,从而实现更符合预期的效果。

我们来看看最终的效果:

p4.gif

这差不多我们最终期望的一个效果了。这个效果要直接在项目中使用肯定不可能,所以我们标题里说的「不改一行代码」实际上说的是 demo 工程。不过哪怕是这样,这次对话着实远远超出我的期望。我完全可以基于此 demo 的基准去实现我实际的目标需求,切切实实提高了工作效率。

以前总想当然得让 chatGPT 搜索模版代码可以,让它创造应该不太能。今天看来自己的认知有待提高,GPT 提高多数人的工作效率应该没啥问题。

再发展几年,程序员的工作会不会真的被 AI 取代?