Android SDK 自带的案例源码都很不错,想学习一下。本着天朝的大无畏分享精神,这里就把自己学习的东西跟大家分享下,共同进步。
AccelerometerPlay是Android自带例子中的加速度传感器的使用,同时也使用了自定义view。例子的运行效果由于网络不行,图片上传不了,感兴趣的朋友可以网上下载一个源码运行下看看,本博客也会附上源码的链接。
Android中自带的该例子把所有的类都写在了一起,本人觉得看的麻烦,容易让人思路不清(个人看法),就把他们都分开了。
主界面AccelerometerPlay类源码如下:
* This is an example of using the accelerometer to integrate the device's
* acceleration to a position using the Verlet method. This is illustrated with
* a very simple particle system comprised of a few iron balls freely moving on
* an inclined wooden table. The inclination of the virtual table is controlled
* by the device's accelerometer.
*
* @see SensorManager
* @see SensorEvent
* @see Sensor
*/
/**
* 参考资料 weakLock机制浅析:http://blog.sina.com.cn/s/blog_4ad7c2540101n2k2.html
* SensorManager:http://www.cnblogs.com/androidez/archive/2013/02/06/2901295.
* html
*
* 传感器的坐标系:http://www.cnblogs.com/mengdd/archive/2013/03/12/2954947.html
* @author LIUBO
*
*/
public class AccelerometerPlayActivity extends Activity {
private SimulationView mSimulationView;
/** 电源管理者 */
private PowerManager mPowerManager;
/** 唤醒锁 */
private WakeLock mWakeLock;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 获取一个电池管理者的实例
mPowerManager = (PowerManager) getSystemService(POWER_SERVICE);
// SCREEN_BRIGHT_WAKE_LOCK:保持CPU 运转,保持屏幕高亮显示,允许关闭键盘灯
// 保持屏幕的高亮
mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass().getName());
//自定义的view,该示例的核心
mSimulationView = new SimulationView(this);
setContentView(mSimulationView);
}
@Override
protected void onResume() {
super.onResume();
mWakeLock.acquire();
//注册加速度传感器
mSimulationView.startSimulation();
}
@Override
protected void onPause() {
super.onPause();
//解除加速度传感器的注册
mSimulationView.stopSimulation();
mWakeLock.release();
}
}上面的代码主要是传感器的注册和保持屏幕高亮显示。
该示例的核心VIew SImulationView继承View实现传感器的事件监听接口
<pre name="code" class="java">public class SimulationView extends View implements SensorEventListener {
// diameter of the balls in meters(以米为单位:小球的直径)
public static final float sBallDiameter = 0.004f;
/** 传感器管理者 */
private SensorManager mSensorManager;
/** 窗口管理者 */
private WindowManager mWindowManager;
private Display mDisplay;
private Sensor mAccelerometer;// 重力传感器
private float mXDpi;// x轴每英寸有多少像素
private float mYDpi;// y轴每英寸有多少像素
private float mMetersToPixelsX;// x轴每米有多少像素
private float mMetersToPixelsY;// y轴每米有多少像素
private Bitmap mBitmap;// 小球的图片
private Bitmap mWood;// 背景图片
private float mXOrigin;
private float mYOrigin;
private float mSensorX;// x轴的加速度
private float mSensorY;// y轴的加速度
private long mSensorTimeStamp;
private long mCpuTimeStamp;
private float mHorizontalBound;
private float mVerticalBound;
private final ParticleSystem mParticleSystem = new ParticleSystem();
public SimulationView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 获取屏幕的尺寸
mDisplay = mWindowManager.getDefaultDisplay();
// 获取重力传感器
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
// 获取屏幕尺寸参数
DisplayMetrics metrics = new DisplayMetrics();
mDisplay.getMetrics(metrics);
mXDpi = metrics.xdpi;
mYDpi = metrics.ydpi;
// 1.0英寸等于0.0254米
mMetersToPixelsX = mXDpi / 0.0254f;
mMetersToPixelsY = mYDpi / 0.0254f;
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);
}
public void startSimulation() {
// 传感器的注册
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
}
public void stopSimulation() {
// 传感器解除注册
mSensorManager.unregisterListener(this);
}
/**
* 布局改变时更改坐标系的中心和最大活动范围
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// 设置原点的坐标(小球粒子活动的最大范围的中心)
mXOrigin = (w - mBitmap.getWidth()) * 0.5f;
mYOrigin = (h - mBitmap.getHeight()) * 0.5f;
// 小球粒子活动的距离原点的最大x轴和y轴的最大距离
mHorizontalBound = ((w / mMetersToPixelsX - sBallDiameter) * 0.5f);
mVerticalBound = ((h / mMetersToPixelsY - sBallDiameter) * 0.5f);
}
@Override
public void onSensorChanged(SensorEvent event) {
// 传感器的类型不是加速度传感器的话就抛掉
if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
return;
/**
* 这里的坐标点(mSensorX,mSensorY)是在以水平向右为X轴的正方向, 垂直向上为Y轴的正方向的自定义的坐标系中
* 重力传感器的坐标系是始终不变的(不要被自定义的坐标系迷惑,这里是两套坐标系)
* 屏幕的旋转会改变自定义坐标系所以坐标点(mSensorX,mSensorY)在屏幕旋转时值会发生变化 (这里的屏幕旋转和手机旋转要区分开)
*/
switch (mDisplay.getRotation()) {// 判断屏幕的旋转角度
case Surface.ROTATION_0:// 正常的x-y坐标
mSensorX = event.values[0];
mSensorY = event.values[1];
break;
case Surface.ROTATION_90:// 旋转90度
mSensorX = -event.values[1];
mSensorY = event.values[0];
break;
case Surface.ROTATION_180:// 旋转180度
mSensorX = -event.values[0];
mSensorY = -event.values[1];
break;
case Surface.ROTATION_270:// 旋转270度
mSensorX = event.values[1];
mSensorY = -event.values[0];
break;
}
// 传感器发生改变的时间(单位:纳秒)
mSensorTimeStamp = event.timestamp;
// 当前的系统时间(单位:纳秒)
mCpuTimeStamp = System.nanoTime();
}
@Override
protected void onDraw(Canvas canvas) {
// 画背景
canvas.drawBitmap(mWood, 0, 0, null);
// 小球粒子运动的总时间
final long now = mSensorTimeStamp + (System.nanoTime() - mCpuTimeStamp);
/*
* 基于重力加速度传感器的数据和当前时间重新计算小球粒子的位置
*/
mParticleSystem.update(mSensorX, mSensorY, now, mHorizontalBound, mVerticalBound);
final Bitmap bitmap = mBitmap;
final int count = mParticleSystem.getParticleCount();
for (int i = 0; i < count; i++) {
/*
(改造画布的坐标系统(单位像素)使之适应重力传感器的坐标系统(单位米))
*/
// 这里的x,y的单位是像素
final float x = mXOrigin + mParticleSystem.getPosX(i) * mMetersToPixelsX;
final float y = mYOrigin - mParticleSystem.getPosY(i) * mMetersToPixelsY;
canvas.drawBitmap(bitmap, x, y, null);
}
invalidate();
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
<span style="background-color: rgb(255, 255, 255);">该View的主要功能:</span>
<span style="background-color: rgb(255, 255, 255);">1、将背景图片和小球资源加载进来,然后画到画布上,传感器的坐标系的单位是米,而自定义的坐标系的单位是像素,所以要对米与像素进行转换。</span>
<span style="background-color: rgb(255, 255, 255);">2、在onSizeChange方法中根据布局的改变重新设定自定义坐标系的原点和x轴和y轴上距离原点的最大值。</span></span>
<span style="background-color: rgb(255, 255, 255);">3、在onSensorChange方法中检测重力传感器的数据变化,并根据屏幕的旋转进行坐标的转换。(因为注册清单中设置了屏幕垂直所以屏幕不会进行旋转)</span></span>
<span style="background-color: rgb(255, 255, 255);">4、更新小球的位置,并在画布上画出小球。</span>
<span style="background-color: rgb(255, 255, 255);">小球的系统控制类ParticleSystem </span>
public class ParticleSystem {
// 小球粒子的外接正方形面积
private static float sBallDiameter2 = SimulationView.sBallDiameter * SimulationView.sBallDiameter;
static final int NUM_PARTICLES = 15;// 小球粒子数量
private Particle mBalls[] = new Particle[NUM_PARTICLES];// 小球粒子的数组
private long mLastT;// 小球上次更改位置的时间
private float mLastDeltaT;// 记录小球相邻两次的位置变化的间隔的时间
ParticleSystem() {
/*
* 初始化所有的小球粒子
*/
for (int i = 0; i < mBalls.length; i++) {
mBalls[i] = new Particle();
}
}
/*
* 更新小球粒子的位置
*/
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, float mHorizontalBound, float mVerticalBound) {
updatePositions(sx, sy, now);
// 设置最大的迭代次数
final 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;
// 小球的碰撞检测
if (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;// 两个小球粒子的中心之间的距离的二次方
// 两个小球之间的距离
final float d = (float) Math.sqrt(dd);
//不是很懂
final float c = (0.5f * (SimulationView.sBallDiameter - d)) / d;
curr.mPosX -= dx * c;
curr.mPosY -= dy * c;
ball.mPosX += dx * c;
ball.mPosY += dy * c;
more = true;
}
}
/*
* 小球粒子不和墙壁相交
*/
curr.resolveCollisionWithBounds(mHorizontalBound, mVerticalBound);
}
}
}
// 返回粒子的数量
public int getParticleCount() {
return mBalls.length;
}
// 返回小球所在的x坐标
public float getPosX(int i) {
return mBalls[i].mPosX;
}
// 返回小球所在的y坐标
public float getPosY(int i) {
return mBalls[i].mPosY;
}
}小球粒子的实体对象
Particle
/*
* 我们的每一个小球粒子保持它的当前和之前的位置,以及加速度。为增加真实性每个粒子都有自己的摩擦
*系数。
*/
public class Particle {
// (小球和桌子以及空气的摩擦参数)
private static final float sFriction = 0.1f;
//当前位置
float mPosX;
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) {
final float m = 1000.0f; // 我们虚拟物体的质量
final float gx = -sx * m;//重力在x轴向的分力
final float gy = -sy * m;//重力在y轴向的分力
final float invm = 1.0f / m;
final float ax = gx * invm;
final float ay = gy * invm;
final float dTdT = dT * dT;
//最新位置的坐标<span style="color:#ff9900;">(红色的代码公式不是很懂)</span>
final float x = mPosX + <span style="color:#ff0000;">mOneMinusFriction * dTC * (mPosX - mLastPosX)</span> + mAccelX * dTdT*0.5f;
final float y = mPosY + mOneMinusFriction * dTC * (mPosY - mLastPosY) + mAccelY * dTdT*0.5f;
mLastPosX = mPosX;
mLastPosY = mPosY;
mPosX = x;
mPosY = y;
mAccelX = ax;
mAccelY = ay;
}
/*
* 小球粒子不和墙壁相交
*/
public void resolveCollisionWithBounds(float mHorizontalBound,float mVerticalBound) {
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;
}
}
}
小球的控制系统类和实体类代码注释也很详细,就不具体说了,碰撞检测和弹簧的模拟的公式本人看的也不是很懂,这里也就不卖弄了,大家如果谁能很好地解释,希望不吝赐教。
本文分享了作者对Android SDK自带案例AccelerometerPlay的学习,该案例涉及加速度传感器的使用和自定义View。由于网络原因无法展示运行效果,但提供了源码下载链接,供读者自行体验。文章还提到了将原始代码拆分以提高可读性,并简单介绍了核心SimulationView类。

2552

被折叠的 条评论
为什么被折叠?



