понедельник, 14 мая 2012 г.

Ориентируемся в пространстве

Сегодня мы научимся обрабатывать показания акселерометра. Поскольку за изменением трех чисел наблюдать как то не весело, сделаем это красиво.

Ориентируемся в пространстве


Поскольку мы решили делать красиво, для начала нам понадобиться View для наглядного отображения вертикали. Изобразим этакую матрешку-неваляшку из двух вложенных друг в друга кругляшков:

package com.WhiteRabbit.Sensors;

...
public class MainView extends View  {

    private int maxX = 10000;
    private int maxY = 10000;
    private int sz;
    private int x0;
    private int y0;
    private double alfa = Math.PI / 2;

    public MainView(Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if ((MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) ||
            (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED)) {
            setMeasuredDimension(maxX, maxY);
        } else {
            int szX = MeasureSpec.getSize(widthMeasureSpec);
            int szY = MeasureSpec.getSize(heightMeasureSpec);
            if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
                if (szX > maxX) {
                    szX = maxX;
                }
            }
            if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
                if (szY > maxY) {
                    szY = maxY;
                }
            }
            setMeasuredDimension(szX, szY);
            x0 = szX / 2;
            y0 = szY / 2;
            if (szX < szY) {
                sz = szX / 3;
            } else {
                sz = szY / 3;
            }
        }
    }

    @Override
    protected void onDraw(Canvas c) {
        c.drawColor(Color.BLACK);
        Paint p = new Paint();
        p.setColor(Color.WHITE);
        p.setAntiAlias(true);
        c.drawCircle(x0, y0, sz, p);
        p.setColor(Color.BLACK);
        double x1 = x0 - (sz /2) * Math.cos(alfa);
        double y1 = y0 - (sz /2) * Math.sin(alfa);
        c.drawCircle((float)x1, (float)y1, sz / 2, p);
    }   
}


В этом коде, в переменной sz сохраняется радиус большого кругляшка, в x0 и y0 его координаты, а в alfa угол (в радианах), в направлении которого будет изображен маленький кругяшок. Добавим созданное View в Activity:

package com.WhiteRabbit.Sensors;

...
public class SensorsActivity extends Activity {

    MainView view;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        LinearLayout.LayoutParams containerParams =
                new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT, 0.0F);
        LinearLayout root = new LinearLayout(this);
        root.setOrientation(LinearLayout.VERTICAL);
        root.setBackgroundColor(Color.LTGRAY);
        root.setLayoutParams(containerParams);
        view = new MainView(this);
        root.addView(view);
        setContentView(root);
    }
}


и, запустив на выполнение, пронаблюдаем результат:



Далее, нам нужен какой-то инструмент, для изменения угла alfa при отображении маленького кружочка. От акселерометра мы будем получать три числовых значения, одно из которых (z) будем игнорировать. Судорожно вспоминаем тригонометрию и понимаем, что alfa = arctg(y/x), а также то, что его аргумент будет претерпевать особенности при x = 0. К счастью, он нас позаботились разработчики Java, определив в Math замечательную функцию atan2:

package com.WhiteRabbit.Sensors;

...
public class MainView extends View  {

    ...
    private double eps = 0.1;

    ...
    public void setXY(float x, float y) {
        if (Math.sqrt(x*x + y*y) < eps) {
            alfa = Math.PI / 2;
        } else {
            alfa = -Math.atan2(y, x);
        }
        invalidate();
    }
}


Константу eps определяем на случай получения очень уж маленьких значений x и y. После этого, проводим на эмуляторе небольшое тестирование, передавая в setXY различные значения для проверки корректности расчета alfa. Убедившись, что все считается правильно, переходим к самой интересной части, работе с акселерометром.
На самом деле, нам требуется всего лишь получить акселерометр из списка предоставляемых устройством сенсоров, написать обработчики его событий и добавить код подписки/отписки на события акселерометра. Попутно зафиксируем ориентацию экрана, чтобы они не вертелся и не мешал нам эстетически наслаждаться:

package com.WhiteRabbit.Sensors;

public class SensorsActivity extends Activity implements SensorEventListener {

    ...
    SensorManager mSensorManager = null;
    Sensor mSensor = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {

    ...
        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        if (mSensorManager != null) {
            List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
            if(sensors.size() > 0) {
                for (Sensor sensor : sensors) {
                    switch(sensor.getType()) {
                        case Sensor.TYPE_ACCELEROMETER:
                            if(mSensor == null) mSensor = sensor;
                            break;
                        default:
                            break;
                    }
                }       
            }
        }
    }

    @Override
    public void onAccuracyChanged(Sensor event, int value) {}

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (view == null) return;
        switch(event.sensor.getType()) {
            case Sensor.TYPE_ACCELEROMETER:
                view.setXY(event.values[0], event.values[1]);
                break;
        }
    }   
   
    @Override
    public void onStart() {
        super.onStart();
        if (mSensor != null) {
            mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_GAME);
        }
    }
   
    @Override
    public void onPause() {
        super.onPause();
        if (mSensor != null) {
            mSensorManager.unregisterListener(this);
        }
    }   
}


Осталось добавить в манифест uses-feature, чтобы никто не запустил наше приложение при отсутствии акселерометра:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.WhiteRabbit.Sensors"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="15" />
    <uses-feature android:name="android.hardware.sensor.accelerometer"
              android:required="true" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".SensorsActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


и можно экспортировать apk-ку.

Комментариев нет:

Отправить комментарий