這裡介紹的水平儀,指的是比較傳統的氣泡水平儀,在一個透明圓盤內充滿液體,液體中留有一個氣泡,當一端翹起時,該氣泡就會浮向翹起的一端。 在上文中,利用方向感應器返回的第一個參數,實現了一個指南針小應用。接下來,我們利用返回的第二、三個參數實現該水平儀。因為第二個參數,反映底部(或頂部)翹起的角度,第三個參數可以反映右側(或左側)翹起的角度。根據這兩個角度就可以開發水平儀,實現手機哪端翹起,氣泡就浮向哪端,這也是水平儀的實現思想。代碼如下: Activity:
package com.home.activity; import android.app.Activity; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import com.home.R; import com.home.view.MyView; public class MainActivity extends Activity implements SensorEventListener { // 定義水平儀的儀錶盤 private MyView view; // 定義水平儀能處理的最大傾斜角,超過該角度,氣泡將直接位於邊界 private final int MAX_ANGLE = 30; // 定義真機的Sensor管理器 private SensorManager mSensorManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 擷取水平儀的組件 view = (MyView) findViewById(R.id.main_myview); // 擷取真機的感應器管理服務 mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); } @Override protected 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 public void onAccuracyChanged(Sensor sensor, int accuracy) { } @Override public void onSensorChanged(SensorEvent event) { float[] values = event.values; // 真機上擷取觸發的感應器類型 int sensorType = event.sensor.getType(); switch (sensorType) { case Sensor.TYPE_ORIENTATION: // 擷取與Y軸的夾角 float yAngle = values[1]; // 擷取與Z軸的夾角 float zAngle = values[2]; // 氣泡位於中間時(水平儀完全水平),氣泡的X、Y座標 int x = (view.back.getWidth() - view.bubble.getWidth()) / 2; int y = (view.back.getHeight() - view.bubble.getHeight()) / 2; // 如果與z軸的傾斜角還在最大角度之內 if (Math.abs(zAngle) <= MAX_ANGLE) { // 根據與z軸的傾斜角計算x座標的變化值(傾斜角度越大,x座標變化越大) int deltaX = (int) ((view.back.getWidth() - view.bubble .getWidth()) / 2 * zAngle / MAX_ANGLE); x += deltaX; } // 如果與z軸的傾斜角已經大於MAX_ANGLE,氣泡應到最左邊 else if (zAngle > MAX_ANGLE) { x = 0; } // 如果與Z軸的傾斜角已經小於負的MAX_ANGLE,氣泡應到最右邊 else { x = view.back.getWidth() - view.bubble.getWidth(); } // 如果與Y軸的傾斜角還在最大角度之內 if (Math.abs(yAngle) <= MAX_ANGLE) { // 根據與Y軸的傾斜角計算Y座標的變化值(傾斜角度越大,Y座標變化越大) int deltaY = (int) ((view.back.getHeight() - view.bubble .getHeight()) / 2 * zAngle / MAX_ANGLE); y += deltaY; } // 如果與Y軸的傾斜角已經大於MAX_ANGLE,氣泡應到最下邊 else if (yAngle > MAX_ANGLE) { y = view.back.getHeight() - view.bubble.getHeight(); } // 如果與Y軸的傾斜角已經小於負的MAX_ANGLE,氣泡應到最右邊 else { y = 0; } // 如果計算出來的X、Y座標還位於水平儀的儀錶盤內,更新水平儀的氣泡座標 if (isContain(x, y)) { view.bubbleX = x; view.bubbleY = y; } // 通知系統重繪MyView組件 view.postInvalidate(); break; } } // 計算X、Y點的氣泡是否處於水平儀的儀錶盤內 private boolean isContain(int x, int y) { // 計算氣泡的圓心座標X、Y int bubbleCx = x + view.bubble.getWidth() / 2; int bubbleCy = y + view.bubble.getHeight() / 2; // 計算水平儀儀錶盤的圓心座標X、Y int backCx = view.back.getWidth() / 2; int backCy = view.back.getHeight() / 2; // 計算氣泡的圓心與水平儀儀錶盤的圓心之間的距離 double distance = Math.sqrt((bubbleCx - backCx) * (bubbleCx - backCx) + (bubbleCy - backCy) * (bubbleCy - backCy)); // 若兩個圓心的距離小於它們的半徑差,即可認為處於該店的氣泡依然位於儀錶盤內 if (distance < (view.back.getWidth() - view.bubble.getWidth()) / 2) { return true; } else { return false; } } } package com.home.activity;import android.app.Activity;import android.hardware.Sensor;import android.hardware.SensorEvent;import android.hardware.SensorEventListener;import android.hardware.SensorManager;import android.os.Bundle;import com.home.R;import com.home.view.MyView;public class MainActivity extends Activity implements SensorEventListener {// 定義水平儀的儀錶盤private MyView view;// 定義水平儀能處理的最大傾斜角,超過該角度,氣泡將直接位於邊界private final int MAX_ANGLE = 30;// 定義真機的Sensor管理器private SensorManager mSensorManager;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);// 擷取水平儀的組件view = (MyView) findViewById(R.id.main_myview);// 擷取真機的感應器管理服務mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);}@Overrideprotected void onResume() {super.onResume();// 為系統的方向感應器註冊監聽器mSensorManager.registerListener(this,mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),SensorManager.SENSOR_DELAY_GAME);}@Overrideprotected void onPause() {// 取消註冊mSensorManager.unregisterListener(this);super.onPause();}@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {}@Overridepublic void onSensorChanged(SensorEvent event) {float[] values = event.values;// 真機上擷取觸發的感應器類型int sensorType = event.sensor.getType();switch (sensorType) {case Sensor.TYPE_ORIENTATION:// 擷取與Y軸的夾角float yAngle = values[1];// 擷取與Z軸的夾角float zAngle = values[2];// 氣泡位於中間時(水平儀完全水平),氣泡的X、Y座標int x = (view.back.getWidth() - view.bubble.getWidth()) / 2;int y = (view.back.getHeight() - view.bubble.getHeight()) / 2;// 如果與z軸的傾斜角還在最大角度之內if (Math.abs(zAngle) <= MAX_ANGLE) {// 根據與z軸的傾斜角計算x座標的變化值(傾斜角度越大,x座標變化越大)int deltaX = (int) ((view.back.getWidth() - view.bubble.getWidth()) / 2 * zAngle / MAX_ANGLE);x += deltaX;}// 如果與z軸的傾斜角已經大於MAX_ANGLE,氣泡應到最左邊else if (zAngle > MAX_ANGLE) {x = 0;}// 如果與Z軸的傾斜角已經小於負的MAX_ANGLE,氣泡應到最右邊else {x = view.back.getWidth() - view.bubble.getWidth();}// 如果與Y軸的傾斜角還在最大角度之內if (Math.abs(yAngle) <= MAX_ANGLE) {// 根據與Y軸的傾斜角計算Y座標的變化值(傾斜角度越大,Y座標變化越大)int deltaY = (int) ((view.back.getHeight() - view.bubble.getHeight()) / 2 * zAngle / MAX_ANGLE);y += deltaY;}// 如果與Y軸的傾斜角已經大於MAX_ANGLE,氣泡應到最下邊else if (yAngle > MAX_ANGLE) {y = view.back.getHeight() - view.bubble.getHeight();}// 如果與Y軸的傾斜角已經小於負的MAX_ANGLE,氣泡應到最右邊else {y = 0;}// 如果計算出來的X、Y座標還位於水平儀的儀錶盤內,更新水平儀的氣泡座標if (isContain(x, y)) {view.bubbleX = x;view.bubbleY = y;}// 通知系統重繪MyView組件view.postInvalidate();break;}}// 計算X、Y點的氣泡是否處於水平儀的儀錶盤內private boolean isContain(int x, int y) {// 計算氣泡的圓心座標X、Yint bubbleCx = x + view.bubble.getWidth() / 2;int bubbleCy = y + view.bubble.getHeight() / 2;// 計算水平儀儀錶盤的圓心座標X、Yint backCx = view.back.getWidth() / 2;int backCy = view.back.getHeight() / 2;// 計算氣泡的圓心與水平儀儀錶盤的圓心之間的距離double distance = Math.sqrt((bubbleCx - backCx) * (bubbleCx - backCx)+ (bubbleCy - backCy) * (bubbleCy - backCy));// 若兩個圓心的距離小於它們的半徑差,即可認為處於該店的氣泡依然位於儀錶盤內if (distance < (view.back.getWidth() - view.bubble.getWidth()) / 2) {return true;} else {return false;}}}
自訂群組件類(MyView):
package com.home.view; import com.home.R; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; public class MyView extends View { // 定義水平儀盤圖片 public Bitmap back; // 定義水平儀中的泡泡圖標 public Bitmap bubble; // 定義水平儀中氣泡的X、Y座標 public int bubbleX, bubbleY; public MyView(Context context, AttributeSet attrs) { super(context, attrs); // 載入水平儀圖片和泡泡圖片 back = BitmapFactory.decodeResource(getResources(), R.drawable.back); 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); } } package com.home.view;import com.home.R;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.util.AttributeSet;import android.view.View;public class MyView extends View {// 定義水平儀盤圖片public Bitmap back;// 定義水平儀中的泡泡圖標public Bitmap bubble;// 定義水平儀中氣泡的X、Y座標public int bubbleX, bubbleY;public MyView(Context context, AttributeSet attrs) {super(context, attrs);// 載入水平儀圖片和泡泡圖片back = BitmapFactory.decodeResource(getResources(), R.drawable.back);bubble = BitmapFactory.decodeResource(getResources(),R.drawable.bubble);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 繪製水平儀圖片canvas.drawBitmap(back, 0, 0, null);// 根據氣泡座標繪製氣泡canvas.drawBitmap(bubble, bubbleX, bubbleY, null);}}
布局XML:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <com.home.view.MyView android:id="@+id/main_myview" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <com.home.view.MyView android:id="@+id/main_myview" android:layout_width="match_parent" android:layout_height="match_parent" /></LinearLayout>