Android移動開發中調用方向傳感器與OpenGL開發簡易水平儀的實現方法

調用方向傳感器與OpenGL開發簡易水平儀的原理其實很簡單的:就是在一個透明的圓盤中充滿某種液體,液體中留有一個氣泡,當一端翹起時,該氣泡將會浮向翹起的一端。
方向傳感器會反向三個值,其中第二個角度值代表底部翹起的角度(當頂部翹起時為負值);第三個角度值代表右側翹起的角度(當左側翹起時為負值);根據這兩個角度就可以開發出水平儀瞭。
該程序Demo定義一個View,該自定義View很簡單,就是繪制透明圓盤和氣泡——其中氣泡的位置會動態改變。該自定義View的代碼如下。

MyView.java邏輯代碼如下:

package com.fukaimei.gradienter;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;


public class MyView extends View {

    // 定義水平儀儀表盤圖片
    Bitmap back;
    // 定義水平儀中的氣泡圖標
    Bitmap bubble;
    // 定義水平儀中氣泡的X、Y坐標
    int bubbleX, bubbleY;

    public MyView(Context context, AttributeSet attrs) {

        super(context, attrs);
        // 獲取窗口管理器
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        // 獲取屏幕的寬度和高度
        Display display = wm.getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        display.getMetrics(metrics);
        int screenWidth = metrics.widthPixels;
        // 創建位圖
        back = Bitmap.createBitmap(screenWidth, screenWidth
                , Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(back);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        // 設置繪制風格:僅填充
        paint.setStyle(Paint.Style.FILL);
        // 創建一個線性漸變來繪制線性漸變
        Shader shader = new LinearGradient(0, screenWidth, screenWidth * 0.8f, screenWidth * 0.2f,
                Color.YELLOW, Color.WHITE, Shader.TileMode.MIRROR);
        paint.setShader(shader);
        // 繪制圓形
        canvas.drawCircle(screenWidth / 2, screenWidth / 2, screenWidth / 2, paint);
        Paint paint2 = new Paint();
        paint2.setAntiAlias(true);
        // 設置繪制風格:僅繪制邊框
        paint2.setStyle(Paint.Style.STROKE);
        // 設置畫筆寬度
        paint2.setStrokeWidth(5);
        // 設置畫筆顏色
        paint2.setColor(Color.BLACK);
        // 繪制圓形邊框
        canvas.drawCircle(screenWidth / 2, screenWidth / 2, screenWidth / 2, paint2);
        // 繪制水平橫線
        canvas.drawLine(0, screenWidth / 2, screenWidth, screenWidth / 2, paint2);
        // 繪制垂直橫線
        canvas.drawLine(screenWidth / 2, 0, screenWidth / 2, screenWidth, paint2);
        // 設置畫筆寬度
        paint2.setStrokeWidth(10);
        // 設置畫筆顏色
        paint2.setColor(Color.RED);
        // 繪制中心的紅色“十”字
        canvas.drawLine(screenWidth / 2 - 30, screenWidth / 2
                , screenWidth / 2 + 30, screenWidth / 2, paint2);
        canvas.drawLine(screenWidth / 2, screenWidth / 2 - 30
                , screenWidth / 2, screenWidth / 2 + 30, paint2);
        // 加載氣泡圖片
        bubble = BitmapFactory
                .decodeResource(getResources(), R.drawable.bubble);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 繪制水平儀表盤圖片
        canvas.drawBitmap(back, 0, 0, null);
        // 根據氣泡坐標繪制氣泡
        canvas.drawBitmap(bubble, bubbleX, bubbleY, null);
    }
}

上面自定義的View會根據bubbleX、bubbleY動態地繪制氣泡的位置,而這個bubbleX、bubbleY就需要根據方向傳感器返回的第三個角度、第二個角度來動態計算。

layout/activity_main.xml界面佈局代碼如下:


<framelayout android:background="#fff" android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="https://schemas.android.com/apk/res/android">

    

</framelayout>

MainActivity.java邏輯代碼如下:

package com.fukaimei.gradienter;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;


public class MainActivity extends AppCompatActivity implements SensorEventListener {

    // 定義水平儀的儀表盤
    MyView show;
    // 定義水平儀能處理的最大傾斜角,超過該角度,氣泡將直接位於邊界
    int MAX_ANGLE = 30;
    // 定義Sensor管理器
    SensorManager mSensorManager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 獲取水平儀的主組件
        show = (MyView) findViewById(R.id.show);
        // 獲取傳感器管理服務
        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
    }

    @Override
    public void onResume() {
        super.onResume();
        // 為系統的方向傳感器註冊監聽器
        mSensorManager.registerListener(this,
                mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),
                SensorManager.SENSOR_DELAY_GAME);
    }

    @Override
    protected void onPause() {
        // 取消註冊
        mSensorManager.unregisterListener(this);
        super.onPause();
    }

    @Override
    protected void onStop() {
        // 取消註冊
        mSensorManager.unregisterListener(this);
        super.onStop();
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        float[] values = event.values;
        // 獲取觸發event的傳感器類型
        int sensorType = event.sensor.getType();
        if (sensorType == Sensor.TYPE_ORIENTATION) {
            // 獲取與Y軸的夾角
            float yAngle = values[1];
            // 獲取與Z軸的夾角
            float zAngle = values[2];
            // 氣泡位於中間時(水平儀完全水平),氣泡的X、Y坐標
            int x = (show.back.getWidth() - show.bubble.getWidth()) / 2;
            int y = (show.back.getHeight() - show.bubble.getHeight()) / 2;
            // 如果與Z軸的傾斜角還在最大角度之內
            if (Math.abs(zAngle) <= MAX_ANGLE) {
                // 根據與Z軸的傾斜角度計算X坐標的變化值
                // (傾斜角度越大,X坐標變化越大)
                int deltaX = (int) ((show.back.getWidth() - show.bubble
                        .getWidth()) / 2 * zAngle / MAX_ANGLE);
                x += deltaX;
            }
            // 如果與Z軸的傾斜角已經大於MAX_ANGLE,氣泡應到最左邊
            else if (zAngle > MAX_ANGLE) {
                x = 0;
            }
            // 如果與Z軸的傾斜角已經小於負的MAX_ANGLE,氣泡應到最右邊
            else {
                x = show.back.getWidth() - show.bubble.getWidth();
            }
            // 如果與Y軸的傾斜角還在最大角度之內
            if (Math.abs(yAngle) <= MAX_ANGLE) {
                // 根據與Y軸的傾斜角度計算Y坐標的變化值
                // (傾斜角度越大,Y坐標變化越大)
                int deltaY = (int) ((show.back.getHeight() - show.bubble
                        .getHeight()) / 2 * yAngle / MAX_ANGLE);
                y += deltaY;
            }
            // 如果與Y軸的傾斜角已經大於MAX_ANGLE,氣泡應到最下邊
            else if (yAngle > MAX_ANGLE) {
                y = show.back.getHeight() - show.bubble.getHeight();
            }
            // 如果與Y軸的傾斜角已經小於負的MAX_ANGLE,氣泡應到最右邊
            else {
                y = 0;
            }
            // 如果計算出來的X、Y坐標還位於水平儀的儀表盤內,更新水平儀的氣泡坐標
            if (isContain(x, y)) {
                show.bubbleX = x;
                show.bubbleY = y;
            }
            // 通知系統重回MyView組件
            show.postInvalidate();
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

    // 計算x、y點的氣泡是否處於水平儀的儀表盤內
    private boolean isContain(int x, int y) {

        // 計算氣泡的圓心坐標X、Y
        int bubbleCx = x + show.bubble.getWidth() / 2;
        int bubbleCy = y + show.bubble.getWidth() / 2;
        // 計算水平儀儀表盤的圓心坐標X、Y
        int backCx = show.back.getWidth() / 2;
        int backCy = show.back.getWidth() / 2;
        // 計算氣泡的圓心與水平儀儀表盤的圓心之間的距離
        double distance = Math.sqrt((bubbleCx - backCx) * (bubbleCx - backCx)
                + (bubbleCy - backCy) * (bubbleCy - backCy));
        // 若兩個圓心的距離小於它們的半徑差,即可認為處於該點的氣泡依然位於儀表盤內
        if (distance < (show.back.getWidth() - show.bubble.getWidth()) / 2) {
            return true;
        } else {
            return false;
        }
    }
}

註意:該應用必須在有方向傳感器的真機中安裝運行才能看到效果。

Demo程序運行效果界面截圖如下:

這裡寫圖片描述這裡寫圖片描述這裡寫圖片描述這裡寫圖片描述


發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *