Android SDK 实例代码分析---Accelerometer Play(三)

三.资源说明

3.1 图片资源

1.高分辨率图片

ball.png------震动传感器使用的球体图片。

icon------本应用程序的图标(在高分辨率屏幕上使用)。

wood.jpg------应用程序主窗口背景。

2. 中分辨率图片

icon.png------本应用程序的图标(在中分辨率屏幕上使用)。

3. 低分辨率图片

icon.png------应用程序在低分辨率屏幕上使用的图标。

3.2 布局资源(main.xml)

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    >

</LinearLayout>

<LinearLayout>:用单列或单行的方式来排列布局中的子元素。

xmlns:android:固定设置,始终是http://schemas.android.com/apk/res/android

android:orientation=”vertical”:指示用一列的方式排列布局中的子元素。

android:layout_width=”fill_parent”:定义当前View在屏幕上可以占据的宽度,fill_parent即横行完全填充

android:layout_height=”fill_parent”:定义当前View在屏幕上可以占据的高度,fill_parent即纵向完全填充

3.3 字符资源(strings.xml)

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <string name="app_name">AccelerometerPlay</string>

</resources>

定义应用程序在应用启动器中显示的名称。

四.代码分析

package com.example.android.accelerometerplay;

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 {

 //定制的View对象,继承View类,并实现SensorEventListener接口。
    private SimulationView mSimulationView;
    //Android系统传感器管理器,通过调用getSystemService()方法来获取管理器的一个实例
    private SensorManager mSensorManager;
    //Android系统的电源管理器,本例中使用电源管理器获取系统的唤醒锁
    private PowerManager mPowerManager;
    //Android系统的窗口管理器,提供了与应用会话的接口,本例使用窗口管理器获取当前默认的显示对象
    private WindowManager mWindowManager;
    //Android系统用于提供显示对象的尺寸、分辨、角度等信息,本例用于获取设备的旋转角度
    private Display mDisplay;
    //Android系统的唤醒锁,本例使用唤醒锁,锁定屏幕的显示,使程序在运行期间,屏幕始终高亮显示。
    private WakeLock mWakeLock;

    /** Activity被首次创建时,系统会调用这个方法,而且只在创建时调用一次,因此在本方法中完成一些
     *  短时的、全局的初始化操作 */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 获取传感器管理器的一个实例
        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

        // 获取电源管理器的一个实例
        mPowerManager = (PowerManager) getSystemService(POWER_SERVICE);

        // 获取窗口管理器的一个实例
        mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        // 获取系统当前默认的显示对象
        mDisplay = mWindowManager.getDefaultDisplay();

        // 创建一个亮度唤醒锁
        mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass()
                .getName());

        // 实例化我们定制的模拟View对象,并把它设置为Activity的显示内容。
        mSimulationView = new SimulationView(this);
        setContentView(mSimulationView);
    }

    @Override
    protected void onResume() {
        super.onResume();
        /*
         * 当Activity恢复显示时,我们申请了一个唤醒锁,让屏幕在程序运行期间保持高亮,
         * 因为用户不会喜欢在程序运行期间频繁的摆弄屏幕或按钮。
         */
        mWakeLock.acquire();

        // 启动模拟窗口
        mSimulationView.startSimulation();
    }

    @Override
    protected void onPause() {
        super.onPause();
        /*
         * 当Activity被挂起时,我们要确保终止模拟窗口,并且要释放传感器资源和唤醒锁。
         */

        // 终止模拟窗口
        mSimulationView.stopSimulation();

        // 释放唤醒锁
        mWakeLock.release();
    }
    /*
     * 内部类,震动器模拟窗口,继承View对象,并实现SensorEventListener接口的以下方法:
     * onSensorChanged()
     * onAccuracyChanged()
     */

    class SimulationView extends View implements SensorEventListener {
        // 以米为单位定义球的直径
        private 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;
        //传感器的X轴位置
        private float mSensorX;
        //传感器的Y轴位置
        private float mSensorY;
        private long mSensorTimeStamp;
        private long mCpuTimeStamp;
        private float mHorizontalBound;
        private float mVerticalBound;
        private final ParticleSystem mParticleSystem = new ParticleSystem();

        /*
         * View对象的内部类,定义在View对象内运动的震动粒子。
         * 每个粒子都有它的加速度的前一个位置和当前的位置。对于每个
         * 实际添加的粒子,都有它们自己的摩擦系数。
         */
        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;
            }

            /*
             * 计算粒子的物理特性,把物理特性强制应用到我们的虚拟对象向粒子上
             * float sx:传感器X轴方向加速度
             * float sy:传感器Y轴方向加速度
             * float dT: 传感器每次响应时间差
             * float dTC:本次时间差与上次时间差的比值
             */
            public void computePhysics(float sx, float sy, float dT, float dTC) {
                // 虚拟对象的质量
                final float m = 1000.0f;
                // 虚拟对象X轴方向的重力
                final float gx = -sx * m;
                // 虚拟对象Y轴方向的重力
                final float gy = -sy * m;

                /*
                 * 把公式 F=mA 转换成 A=F/m,我们能够通过完全消除所有公式中的“m”(质量)来简化代码
                 * 但是它也从这段示例代码中隐藏了概念
                 * 合力=质量乘以加速度
                 */
                final float invm = 1.0f / m;
                // 虚拟对象X轴方向的加速度
                final float ax = gx * invm;
                // 虚拟对象Y轴方向的加速度
                final float ay = gy * invm;

                /*
                 * 此处公式不做解释
                 */
                final float dTdT = dT * dT;
                // 虚拟对象在X轴方向位移后的位置
                final float x = mPosX + mOneMinusFriction * dTC * (mPosX - mLastPosX) + mAccelX
                        * dTdT;
                // 虚拟对象在Y轴方向位移后的位置
                final float y = mPosY + mOneMinusFriction * dTC * (mPosY - mLastPosY) + mAccelY
                        * dTdT;
                // 保存前次X轴位移后的位置
                mLastPosX = mPosX;
                // 保存前次Y轴位移后的位置
                mLastPosY = mPosY;
                // 保存当前X轴位移后的位置
                mPosX = x;
                // 保存当前Y轴位移后的位置
                mPosY = y;
                // 保存X轴加速度
                mAccelX = ax;
                // 保存Y轴加速度
                mAccelY = ay;
            }

            /*
             * 用Verlet积分器能很简单的解决限制和碰撞的问题,
             * 我们只需要用这种方法来移动正在碰撞的或受到限制的粒子
             */
            public void resolveCollisionWithBounds() {
             // X轴最大边界
                final float xmax = mHorizontalBound;
                // Y轴最大边界
                final float ymax = mVerticalBound;
                // X轴当前位置
                final float x = mPosX;
                // Y轴当前位置
                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;
                }
            }
        }

        /*
         * 内部类,一个View对象内粒子的集合
         */
        class ParticleSystem {
         // 定义当前集合内的粒子数
            static final int NUM_PARTICLES = 15;
            private Particle mBalls[] = new Particle[NUM_PARTICLES];

            ParticleSystem() {
                /*
                 * 初始化没有速度和加速度的粒子
                 */
                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;
            }

            /*
             * 执行一次传感器模拟的迭代。
             * 首先更新所有的粒子的位置,并解决限制和碰撞的问题
             * float sx: X轴加速度
             * float sy: Y轴加速度
             * long now: 传感器当前响应的时间
             */
            public void update(float sx, float sy, long now) {
                // 更新粒子系统中粒子的位置
                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;
                            // Check for collisions
                            if (dd <= sBallDiameter2) {
                                /*
                                 * 添加一个熵值
                                 */
                                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 * (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;

            // 调整粒子球的大小
            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);
        }

        /*
         * 重写View类的onSizeChanged方法
         */
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            // 计算相对图片原点的屏幕原点
            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方法
         * @see android.hardware.SensorEventListener#onSensorChanged(android.hardware.SensorEvent)
         */
        public void onSensorChanged(SensorEvent event) {
            if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
                return;
            /*
             * 记录加速传感器的数据、事件发生的时间以及当前时间。
             * 我们需要后者来计算渲染期间的展现时间。在这个应用程序中,
             * 我们需要考虑屏幕是怎样跟传感器一起旋转的(让它返回的数据
             * 跟屏幕的原始坐标空间相对应)
             */

            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();
        }
        /*
         * 覆写View类的onDraw方法
         * @see android.view.View#onDraw(android.graphics.Canvas)
         */
        @Override
        protected 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++) {
                /*
                 * We transform the canvas so that the coordinate system matches
                 * the sensors coordinate system with the origin in the center
                 * of the screen and the unit is the meter.
                 */

             /*
              *  变换坐标,让画布的坐标系统与传感器的坐标系统匹配,
              *  以屏幕中央为原点,并且单位是“米”
              */
             
                final float x = xc + particleSystem.getPosX(i) * xs;
                final float y = yc - particleSystem.getPosY(i) * ys;
                canvas.drawBitmap(bitmap, x, y, null);
            }

            // 确保画布重绘
            invalidate();
        }

        /*
         * 实现SensorEventListener接口的onAccuracyChanged方法
         * @see android.hardware.SensorEventListener#onAccuracyChanged(android.hardware.Sensor, int)
         */
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }
    }
}

 

你可能感兴趣的:(android,service,layout,float,encoding,代码分析)