Android坡度計

藝術來源於生活,對我來講,編程也是一門藝術。今天發佈這篇技術博客,就是我跟朋友在一次登山過程當中的爭論,他跟我說那座山至少45度,我說沒有,最多30度。咱們彼此爭論不休,因而我就想,爲啥不寫個手機程序來實際測量一下?因而,個人工做就開始了。android

硬件基礎

由於我暫時沒有筆記本,因此就用了個人ZTE-U807N做爲編寫平臺,它雖然配置較低,但有加速度傳感器就夠了。算法

編程工具

我選擇移動端開發工具,使用AIDE做爲集成開發環境(表示它真的很強大)。編程

算法思想

加速度傳感器能夠測量三座標份量,即X, Y, Z座標。XY平面是手機平面,Z座標垂直手機平面,手機觸屏朝上Z值爲正。經過簡單的數學推導能夠得出坡度的餘弦值等於|Z|比上X、Y、Z的矢量和的模。寫成公式就是以下:
g = √(X²+Y²+Z²)
cosθ = |z|/g
因而,我就可以經過Z和g得出坡度θ的值。
因爲加速度傳感器採樣速率很快,並且測量值不許,老是會變,因此我採樣滑動平均算法,將各測量份量進行積累,而後求取平均值來消除偏差,同時也能夠消除手機靜止時坡度數值跳變的問題。數據結構

數據結構

使用隊列存儲各份量數據,等數據積累完畢之後每來一個數據就出一個老數據。框架

框架編寫

我須要2個界面,一個主界面,就是現地坡度;另外一個是關於界面,就是軟件說明、做者等等。具體如圖所示:


ide

首先在MainActivity放置一個TextView用於顯示坡度。而後我給它加上一個SeekBar用於靈敏度調節,代碼以下:函數

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="20dp">
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:id="@+id/mainTextView1"
        android:textSize="80sp"/>
    <LinearLayout
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:orientation="horizontal">
        <TextView
            android:layout_height="match_parent"
            android:text="靈敏度調節"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:layout_width="wrap_content"
            android:paddingRight="5dp"
            android:gravity="center_vertical"/>
        <SeekBar
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:id="@+id/mainSeekBar1"/>
    </LinearLayout>
</LinearLayout>

其次就是關於About界面,就是做者說明、版本啊等等,而後有個按鈕,按下可以返回MainActivity界面。代碼以下:工具

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:gravity="center">
    <TextView
        android:layout_height="wrap_content"
        android:text="@string/version"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:layout_width="wrap_content"/>
    <TextView
        android:layout_height="wrap_content"
        android:text="@string/copy_right"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_width="wrap_content"/>
    <TextView
        android:layout_height="wrap_content"
        android:text="@string/description"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_width="wrap_content"/>
    <Button
        android:layout_height="wrap_content"
        android:text="肯定"
        android:layout_width="wrap_content"
        android:id="@+id/aboutButton1"/>
</LinearLayout>

而後我要有一個設置菜單,在MainActivity類裏面加入onCreateOptionsMenu函數,代碼以下:開發工具

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    menu.add(Menu.FIRST, Menu.FIRST, Menu.FIRST, "關於").setIcon(android.R.drawable.ic_dialog_info);
    menu.add(Menu.FIRST+1, Menu.FIRST+1, Menu.FIRST+1, "退出").setIcon(android.R.drawable.ic_lock_power_off);
    return true;
}

固然要給菜單添加單擊處理:this

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == Menu.FIRST) {
        /*關於界面*/
        Intent intent = new Intent();
        intent.setClass(MainActivity.this, About.class);
        startActivity(intent);
    }
    else if(item.getItemId() == Menu.FIRST + 1) {
                /*退出*/
        android.os.Process.killProcess(android.os.Process.myPid());
        finish();
    }
    return super.onOptionsItemSelected(item);
}

還有大多數軟件有的連續按兩次返回鍵退出程序:

/*long curent = 0;*/
@Override
public void onBackPressed() {
    if (System.currentTimeMillis() - current > 2000) { //若是兩次按鍵時間大於2秒
        current = System.currentTimeMillis();
        Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show();
    }
    else {
        android.os.Process.killProcess(android.os.Process.myPid());
        finish();
    }
}

最後就是在關於界面按鍵可以返回到主界面的功能:

@Override
    public void onClick(View p1) {
        super.onBackPressed(); //實現按鍵返回上一活動
}

我偷了懶,調用父類的onBackPressed函數,就至關於按下返回鍵。

核心算法

首先是數據定義與初始化:

private long current;
private TextView tv = null;
private SeekBar sb = null;
private SeekListener seekl = null;  //自定義類
private SensorManager sm = null;
private Sensor as = null;
private SenListener sl = null;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

    current = 0;
    tv = (TextView)findViewById(R.id.mainTextView1);
    sb = (SeekBar)findViewById(R.id.mainSeekBar1);
    sb.setMax(3); //能夠控制4個級別
    sb.setOnSeekBarChangeListener(seekl = new SeekListener());
    sm = (SensorManager)getSystemService(Service.SENSOR_SERVICE); //獲取傳感器管理器
    as = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); //獲取加速度傳感器
    sm.registerListener(sl = new SenListener(), as, SensorManager.SENSOR_DELAY_FASTEST); //註冊傳感器
    Toast.makeText(MainActivity.this, "請使手機平行於地面", Toast.LENGTH_LONG).show();
}

而後是傳感器監聽器的實現:

/**核心算法以及傳感器監聽器**/
private class SenListener implements SensorEventListener {
    private int sizeLimit; //緩衝區大小
    private Queue<Float> queX, queY, queZ; //各份量緩衝區
    private float sumX, sumY, sumZ; //和
    private float aveX, aveY, aveZ; //平均
    private float g; //矢量和
    private float gradient; //坡度
    
    SenListener() {
        sizeLimit = 100;
        queX = new LinkedList<>();
        queY = new LinkedList<>();
        queZ = new LinkedList<>();
        sumX = sumY = sumZ = 0;
    }
    
    void setLimit(int grade) {
        sizeLimit = 100 * (grade + 1); //SeekBar選擇來控制緩衝區大小,據此能夠調節靈敏度
    }
    
    private double grad2Deg (double grad) {
        return grad * 180 / Math.PI;
    }
    
    @Override
    public void onSensorChanged(SensorEvent p1) {
        float x, y, z;
        x = p1.values[0];
        y = p1.values[1];
        z = p1.values[2];
        
        /*滑動平均算法核心部分*/
        sumX += x;
        sumY += y;
        sumZ += z;
        queX.offer(x);
        queY.offer(y);
        queZ.offer(z);
        while (queX.size() > sizeLimit) {
            sumX -= queX.poll();
            sumY -= queY.poll();
            sumZ -= queZ.poll();
        }
        aveX = sumX / queX.size();
        aveY = sumY / queX.size();
        aveZ = sumZ / queX.size();
        g = (float)Math.sqrt(aveX * aveX + aveY * aveY + aveZ * aveZ);
        gradient = (float)Math.acos(Math.abs(aveZ) / g);
        
        tv.setText(Math.round(grad2Deg(gradient)) + "º"); //弧度轉爲度
    }
    @Override
    public void onAccuracyChanged(Sensor p1, int p2) {
        // TODO: Implement this method
    }

    @Override
    protected void finalize() throws Throwable {
        // TODO: Implement this method
        queX = queY = queZ = null;
        super.finalize();
    }
}

結語

我最終編寫出了坡度計,解決了我跟朋友的問題。我將我所想的與作的分享出來,寫的也有些亂,若是有不明白的地方歡迎提問。另外,我將整個工程放到了個人百度網盤裏,有興趣的話歡迎下載,雖然是手機上建的,但它兼容Eclipse。http://pan.baidu.com/share/link?shareid=3657265547&uk=2315273780

相關文章
相關標籤/搜索