(本系列基于Jelly Bean,Android OS4.2,API 17版本)
accelerometerplay是一个使用加速度传感器的范例。加速度加速度是游戏开发中经常使用的传感器,在流行的神庙逃亡等等都有使用。在本例中模拟了由十几个小铁球组成的粒子系统在一个桌子上的运动。其中小球求位置和速度用到了verlet算法,verlet算法是一种求解牛顿运动方程的数值方法,被广泛应用于分子动力学模拟和视频游戏中,由于涉及物理学和积分,不做展开介绍(我也不懂)。
首先说一下传感器开发的基本步骤:
1.第一步,获取SensorManager对象
SensorManager mSensorManager=(SensorManger)getSystemService(SENSOR_SERVICE);
2.第二步,通过调取getDefaultSensor方法获取某种具体类型的传感器对象
Sensor mAccelerometer=mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
3.第三步,开发传感器监听器,即实现SensorEventListener接口,这个接口就是两个方法onAccurayChanged和onSensorChanged。第一方法在传感器精度发生变化时回调,第二个在传感器测量的物理量发生变化时回调(本例为加速度)
4第四步,注册和注销监听器
注册一般在Activity中的onResume方法实现。registerLisner(SensorEventListener listener, Sensor sensor, int rateUs)
注销一般在onPause方法中实现。unregisterListener(SensorListener listener).
其次还用到了唤醒锁。唤醒锁的作用是即使按下电源键,依然使程序运行。下面显示了可供使用的唤醒锁类型:
唤醒锁类型 | CPU | 屏幕 | 物理键盘 |
PARTIAL_WAKE_LOCK | 开启 | 关闭 | 关闭 |
SCREEN_DIM_WAKE_LOCK | 开启 | 变暗 | 关闭 |
SCREEN_BRIGHT_WAKE_LOCK | 开启 | 明亮 | 关闭 |
FULL_WAKE_LOCK | 开启 | 明亮 | 明亮 |
从表格中可以看出,有两种主要的唤醒锁类型:一种是部分唤醒锁,允许屏幕关闭,但确保应用仍然在运行;另一种是完全唤醒锁,即便电源键按下,也不会关闭屏幕和键盘。部分唤醒锁有两种子类型,可提供对屏幕更细粒度的控制。本范例中就的就是子类型。使用唤醒锁时需要在AndroidManifest中添加许可
<user-permission android:name="android.permission.WAKE_LOCK"/>
这个例子比较简单,只有一个类AccelerometerPlayActivity。下面我们就来分析:
import android.app.Activity;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.BitmapFactory.Options;import android.hardware.Sensor;import android.hardware.SensorEvent;import android.hardware.SensorEventListener;import android.hardware.SensorManager;import android.os.Bundle;import android.os.PowerManager;import android.os.PowerManager.WakeLock;import android.util.DisplayMetrics;import android.view.Display;import android.view.Surface;import android.view.View;import android.view.WindowManager;public class AccelerometerPlayActivity extends Activity { private SimulationView mSimulationView; private SensorManager mSensorManager; private PowerManager mPowerManager; private WindowManager mWindowManager; private Display mDisplay; private WakeLock mWakeLock;public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //获取传感器管理器的一个实例mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); // 要获取一个唤醒锁,需要访问PowerManager类。该类是一个系统服务,通过调用Context.getSystemService(Context.Power_Service)访问到它。mPowerManager = (PowerManager) getSystemService(POWER_SERVICE); //获取窗口管理器的一个实例mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); mDisplay = mWindowManager.getDefaultDisplay(); // 创建一个亮屏唤醒锁mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass() .getName()); // 实例化simulation view并将其内容显示mSimulationView = new SimulationView(this); setContentView(mSimulationView); } @Overrideprotected void onResume() { super.onResume(); /* * 因为玩家一般不希望在玩这个应用时还要操作键盘来保持屏幕始终明亮,所以Activity继续时,我们需要唤醒锁保持屏幕始终明亮。 * 激活唤醒锁的方法就是调用mWakeLock.acquire()函数。 */mWakeLock.acquire(); // Start the simulationmSimulationView.startSimulation(); } @Overrideprotected void onPause() { super.onPause();
//当Activity中止时确保停止Simulation和wakelock。mSimulationView.stopSimulation(); // 不需要唤醒锁时,应显式的将其释放。释放后,我们的设备就可以想通常那样在关闭屏幕后转入休眠了。mWakeLock.release(); }//下面的SimulationView类用来绘制视图,此类实现了SencorEventListener接口 class SimulationView extends View implements SensorEventListener { // diameter of the balls in metersprivate static final float sBallDiameter = 0.004f; private static final float sBallDiameter2 = sBallDiameter * sBallDiameter; // 定义桌子和空气的总摩擦力private static final float sFriction = 0.1f; private Sensor mAccelerometer; private long mLastT; private float mLastDeltaT; private float mXDpi; private float mYDpi; private float mMetersToPixelsX; private float mMetersToPixelsY; private Bitmap mBitmap; private Bitmap mWood; private float mXOrigin; private float mYOrigin; private float mSensorX; private float mSensorY; private long mSensorTimeStamp; private long mCpuTimeStamp; private float mHorizontalBound; private float mVerticalBound; private final ParticleSystem mParticleSystem = new ParticleSystem(); /* * 粒子类。 */class Particle { private float mPosX; private float mPosY; private float mAccelX; private float mAccelY; private float mLastPosX; private float mLastPosY; private float mOneMinusFriction; Particle() { // 利用一个随机数使粒子的摩擦系数有点差异final float r = ((float) Math.random() - 0.5f) * 0.2f; mOneMinusFriction = 1.0f - sFriction + r; } public void computePhysics(float sx, float sy, float dT, float dTC) { // Force of gravity applied to our virtual objectfinal float m = 1000.0f; // 物体重量final float gx = -sx * m; final float gy = -sy * m; /* * 加速度公式 */final float invm = 1.0f / m; final float ax = gx * invm; final float ay = gy * invm; /*Verlet方法 */final float dTdT = dT * dT; final float x = mPosX + mOneMinusFriction * dTC * (mPosX - mLastPosX) + mAccelX* dTdT; final float y = mPosY + mOneMinusFriction * dTC * (mPosY - mLastPosY) + mAccelY* dTdT; mLastPosX = mPosX; mLastPosY = mPosY; mPosX = x; mPosY = y; mAccelX = ax; mAccelY = ay; } /* * 用verlet方法添加约束获取粒子发生碰撞时的位置 */public void resolveCollisionWithBounds() { final float xmax = mHorizontalBound; final float ymax = mVerticalBound; final float x = mPosX; final float y = mPosY; if (x > xmax) { mPosX = xmax; } else if (x < -xmax) { mPosX = -xmax; } if (y > ymax) { mPosY = ymax; } else if (y < -ymax) { mPosY = -ymax; } } } /* * 粒子系统类,用来模拟多个小球在一起时的运动关系。 */class ParticleSystem { static final int NUM_PARTICLES = 15; private Particle mBalls[] = new Particle[NUM_PARTICLES]; ParticleSystem() { /* * 初始化每个小球,初始速度和加速度均为0。 */for (int i = 0; i < mBalls.length; i++) { mBalls[i] = new Particle(); } } /* * 通过verlet积分更新系统中每个粒子的位置 */private void updatePositions(float sx, float sy, long timestamp) { final long t = timestamp; if (mLastT != 0) { final float dT = (float) (t - mLastT) * (1.0f / 1000000000.0f); if (mLastDeltaT != 0) { final float dTC = dT / mLastDeltaT; final int count = mBalls.length; for (int i = 0; i < count; i++) { Particle ball = mBalls[i]; ball.computePhysics(sx, sy, dT, dTC); } } mLastDeltaT = dT; } mLastT = t; } /* * 迭代执行模拟 */public void update(float sx, float sy, long now) { // update the system's positionsupdatePositions(sx, sy, now); // We do no more than a limited number of iterationsfinal int NUM_MAX_ITERATIONS = 10; /* * 遍历粒子集合,修改每个粒子碰撞后的位置。 */boolean more = true; final int count = mBalls.length; for (int k = 0; k < NUM_MAX_ITERATIONS && more; k++) { more = false; for (int i = 0; i < count; i++) { Particle curr = mBalls[i]; for (int j = i + 1; j < count; j++) { Particle ball = mBalls[j]; float dx = ball.mPosX - curr.mPosX; float dy = ball.mPosY - curr.mPosY; float dd = dx * dx + dy * dy; // Check for collisionsif (dd <= sBallDiameter2) { /* * add a little bit of entropy, after nothing is * perfect in the universe. */dx += ((float) Math.random() - 0.5f) * 0.0001f; dy += ((float) Math.random() - 0.5f) * 0.0001f; dd = dx * dx + dy * dy; // simulate the springfinal float d = (float) Math.sqrt(dd); final float c = (0.5f * (sBallDiameter - d)) / d; curr.mPosX -= dx * c; curr.mPosY -= dy * c; ball.mPosX += dx * c; ball.mPosY += dy * c; more = true; } } /* * 确保小球没撞进“墙”里面。 */curr.resolveCollisionWithBounds(); } } } public int getParticleCount() { return mBalls.length; } public float getPosX(int i) { return mBalls[i].mPosX; } public float getPosY(int i) { return mBalls[i].mPosY; } } public void startSimulation() { /* * 注册监听器。传感器采样频率有四种,一般不设置要太高,设置为SENSOR_DELAY_UI就可以,这样可以节省CPU和电池。 */mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI); } public void stopSimulation() {
/* * 注销监听器。
mSensorManager.unregisterListener(this);
}
public SimulationView(Context context) {
super(context);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
mXDpi = metrics.xdpi;
mYDpi = metrics.ydpi;
mMetersToPixelsX = mXDpi / 0.0254f;
mMetersToPixelsY = mYDpi / 0.0254f;
// rescale the ball so it's about 0.5 cm on screen
Bitmap ball = BitmapFactory.decodeResource(getResources(), R.drawable.ball);
final int dstWidth = (int) (sBallDiameter * mMetersToPixelsX + 0.5f);
final int dstHeight = (int) (sBallDiameter * mMetersToPixelsY + 0.5f);
mBitmap = Bitmap.createScaledBitmap(ball, dstWidth, dstHeight, true);
Options opts = new Options();
opts.inDither = true;
opts.inPreferredConfig = Bitmap.Config.RGB_565;
mWood = BitmapFactory.decodeResource(getResources(), R.drawable.wood, opts);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// compute the origin of the screen relative to the origin of
// the bitmap
mXOrigin = (w - mBitmap.getWidth()) * 0.5f;
mYOrigin = (h - mBitmap.getHeight()) * 0.5f;
mHorizontalBound = ((w / mMetersToPixelsX - sBallDiameter) * 0.5f);
mVerticalBound = ((h / mMetersToPixelsY - sBallDiameter) * 0.5f);
}
@实现SensorEventListener接口的onSensorChanged方法。传入的参数为传感器事件的引用,获取当前测量值的数组values
public void onSensorChanged(SensorEvent event) {
//不是加速度类型的传感器变化不做处理 if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) return; /*加速度传感器对应的values数组长度为3,其中3个元素分别代表当前加速度在x,y,z上的分量。
同时考虑屏幕的旋转方向。 */switch (mDisplay.getRotation()) { case Surface.ROTATION_0: mSensorX = event.values[0]; mSensorY = event.values[1]; break; case Surface.ROTATION_90: mSensorX = -event.values[1]; mSensorY = event.values[0]; break; case Surface.ROTATION_180: mSensorX = -event.values[0]; mSensorY = -event.values[1]; break; case Surface.ROTATION_270: mSensorX = event.values[1]; mSensorY = -event.values[0]; break; } mSensorTimeStamp = event.timestamp; mCpuTimeStamp = System.nanoTime(); } @Overrideprotected void onDraw(Canvas canvas) {canvas.drawBitmap(mWood, 0, 0, null);final ParticleSystem particleSystem = mParticleSystem; final long now = mSensorTimeStamp + (System.nanoTime() - mCpuTimeStamp); final float sx = mSensorX; final float sy = mSensorY; particleSystem.update(sx, sy, now); final float xc = mXOrigin; final float yc = mYOrigin; final float xs = mMetersToPixelsX; final float ys = mMetersToPixelsY; final Bitmap bitmap = mBitmap; final int count = particleSystem.getParticleCount(); for (int i = 0; i < count; i++) {final float x = xc + particleSystem.getPosX(i) * xs; final float y = yc - particleSystem.getPosY(i) * ys; canvas.drawBitmap(bitmap, x, y, null); }invalidate(); } @Override
//实现SensorEventListener接口的onAccuracyChanged方法,本例中为更改精度,故不做更改。public void onAccuracyChanged(Sensor sensor, int accuracy) { } }}
版权声明:本文为博主原创文章,未经博主允许不得转载。