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;
		}
	}
}

小球的控制系统类和实体类代码注释也很详细,就不具体说了,碰撞检测和弹簧的模拟的公式本人看的也不是很懂,这里也就不卖弄了,大家如果谁能很好地解释,希望不吝赐教。


源码下载

版权声明:本文为博主原创文章,未经博主允许不得转载。