Android-17的案例学习1:AccelerometerPlayActivity
Android-17的案例学习一:AccelerometerPlayActivity
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; } } }
小球的控制系统类和实体类代码注释也很详细,就不具体说了,碰撞检测和弹簧的模拟的公式本人看的也不是很懂,这里也就不卖弄了,大家如果谁能很好地解释,希望不吝赐教。
源码下载
版权声明:本文为博主原创文章,未经博主允许不得转载。