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) {
        }
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
List of Sample Apps The list below provides a summary of the sample applications that are available with the Android SDK. Using the links on this page, you can view the source files of the sample applications in your browser. You can also download the source of these samples into your SDK, then modify and reuse it as you need. For more information, see Getting the Samples. API Demos A variety of small applications that demonstrate an extensive collection of framework topics. Backup and Restore A simple example that illustrates a few different ways for an application to implement support for the Android data backup and restore mechanism. Bluetooth Chat An application for two-way text messaging over Bluetooth. BusinessCard An application that demonstrates how to launch the built-in contact picker from within an activity. This sample also uses reflection to ensure that the correct version of the contacts API is used, depending on which API level the application is running under. Contact Manager An application that demonstrates how to query the system contacts provider using the ContactsContract API, as well as insert contacts into a specific account. Home A home screen replacement application. JetBoy A game that demonstrates the SONiVOX JET interactive music technology, with JetPlayer. Live Wallpaper An application that demonstrates how to create a live wallpaper and bundle it in an application that users can install on their devices. Lunar Lander A classic Lunar Lander game. Multiple Resolutions A sample application that shows how to use resource directory qualifiers to provide different resources for different screen configurations. Note Pad An application for saving notes. Similar (but not identical) to the Notepad tutorial. SampleSyncAdapter Demonstrates how an application can communicate with a cloud-based service and synchronize its data with data stored locally in a content provider. The sample uses two related parts of the Android framework — the account manager and the synchronization manager (through a sync adapter). Searchable Dictionary A sample application that demonstrates Android's search framework, including how to provide search suggestions for Quick Search Box. Snake An implementation of the classic game "Snake." Soft Keyboard An example of writing an input method for a software keyboard. Spinner A simple application that serves as an application-under-test for the SpinnerTest sample application. SpinnerTest An example test application that contains test cases run against the Spinner sample application. To learn more about the application and how to run it, please read the Activity Testing tutorial. TicTacToeLib An example of an Android library project that provides a game-play Activity to any dependent application project. For an example of how an application can use the code and resources in an Android library project, see the TicTacToeMain sample application. TicTacToeMain An example of an Android application that makes use of code and resources provided in an Android library project. Specifically, this application uses code and resources provided in the TicTacToeLib library project. Wiktionary An example of creating interactive widgets for display on the Android home screen. Wiktionary (Simplified) A simple Android home screen widgets example.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值