您的位置:首页 > 移动开发 > Android开发

Android-17的案例学习一:AccelerometerPlayActivity

2015-07-06 22:10 585 查看
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;}}}
小球的控制系统类和实体类代码注释也很详细,就不具体说了,碰撞检测和弹簧的模拟的公式本人看的也不是很懂,这里也就不卖弄了,大家如果谁能很好地解释,希望不吝赐教。源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: