267
技術社區[雲棲]
Android之指南針學習
5月12日更新到V5版:https://download.csdn.net/detail/weidi1989/5364243
今天,在小米的開源項目中下載了一個指南針源碼學習了一下,感覺不僅界麵做得很漂亮,代碼也是很精致,所以我在研究了之後,加了比較多的注釋,果斷跟大家分享一下,很精簡的幾段代碼,仔細品味可以學到很多東西,我稍微總結一下:
①.handler的靈活運用,每20秒後執行一次自己,用來檢測方向變化值,更新指南針旋轉。
②.傳感器和穀歌位置服務的使用。
③.自定義View,這裏麵是自定義一個ImageView,自己增加一個旋轉圖片的方法。
④.Android動畫Interpolator插入器:AccelerateInterpolator加速插入器的運用。順便說一下另外幾個插入器:
——AccelerateInterpolator:動畫從開始到結束,變化率是一個加速的過程。
——DecelerateInterpolator:動畫從開始到結束,變化率是一個減速的過程。
——CycleInterpolator:動畫從開始到結束,變化率是循環給定次數的正弦曲線。
——AccelerateDecelerateInterpolator:動畫從開始到結束,變化率是先加速後減速的過程。
——LinearInterpolator:動畫從開始到結束,變化率是線性變化。
AccelerateInterpolator有一個方法:getInterpolation(float input);
⑤.巧妙的數字替換成對應的數字圖片和根據本地語言使用對應的圖片資源(圖片資源國際化,哈哈)。還有一些其他的小知識,朋友們,自己下載去研究吧!
下麵看一下效果圖(我的是模擬器,木有傳感器也木有定位的):
下麵我們來看一下這個界麵的布局文件(main.xml):
- <?xml version="1.0" encoding="UTF-8"?>
- <FrameLayout xmlns:android="https://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" >
- <FrameLayout
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="@drawable/background" >
- <LinearLayout
- android:id="@+id/view_compass"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="@drawable/background_light"
- android:orientation="vertical" >
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- android:orientation="vertical" >
- <FrameLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/prompt" >
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_marginTop="70dip"
- android:orientation="horizontal" >
- <LinearLayout
- android:id="@+id/layout_direction"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:gravity="right"
- android:orientation="horizontal" >
- </LinearLayout>
- <ImageView
- android:layout_width="20dip"
- android:layout_height="fill_parent" >
- </ImageView>
- <LinearLayout
- android:id="@+id/layout_angle"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:gravity="left"
- android:orientation="horizontal" >
- </LinearLayout>
- </LinearLayout>
- </FrameLayout>
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- android:orientation="vertical" >
- <FrameLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center" >
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:src="@drawable/background_compass" />
- <net.micode.compass.CompassView
- android:id="@+id/compass_pointer"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:src="@drawable/compass" />
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:src="@drawable/miui_cover" />
- </FrameLayout>
- </LinearLayout>
- </LinearLayout>
- <FrameLayout
- android:id="@+id/location_layout"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/background_bottom"
- android:orientation="vertical" >
- </LinearLayout>
- <TextView
- android:id="@+id/textview_location"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:text="@string/getting_location"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="#7FFFFFFF" />
- </FrameLayout>
- </LinearLayout>
- </FrameLayout>
- </FrameLayout>
這其中用到了一個自定義view,其實就是中間那個可以旋轉的指南針,我們也來看看它的代碼(CompassView.java):
- /**
- * 自定義一個View繼承ImageView,增加一個通用的旋轉圖片資源的方法
- *
- * @author way
- *
- */
- public class CompassView extends ImageView {
- private float mDirection;// 方向旋轉浮點數
- private Drawable compass;// 圖片資源
- //三個構造器
- public CompassView(Context context) {
- super(context);
- mDirection = 0.0f;// 默認不旋轉
- compass = null;
- }
- public CompassView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mDirection = 0.0f;
- compass = null;
- }
- public CompassView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mDirection = 0.0f;
- compass = null;
- }
- @Override
- protected void onDraw(Canvas canvas) {
- if (compass == null) {
- compass = getDrawable();// 獲取當前view的圖片資源
- compass.setBounds(0, 0, getWidth(), getHeight());// 圖片資源在view的位置,此處相當於充滿view
- }
- canvas.save();
- canvas.rotate(mDirection, getWidth() / 2, getHeight() / 2);// 繞圖片中心點旋轉,
- compass.draw(canvas);// 把旋轉後的圖片畫在view上,即保持旋轉後的樣子
- canvas.restore();// 保存一下
- }
- /**
- * 自定義更新方向的方法
- *
- * @param direction
- * 傳入的方向
- */
- public void updateDirection(float direction) {
- mDirection = direction;
- invalidate();// 重新刷新一下,更新方向
- }
- }
接下來就隻剩下一個Activity了,其實總體結構還是很簡單的,CompassActivity.java:
- /**
- * MainActivity
- *
- * @author way
- *
- */
- public class CompassActivity extends Activity {
- private static final int EXIT_TIME = 2000;// 兩次按返回鍵的間隔判斷
- private final float MAX_ROATE_DEGREE = 1.0f;// 最多旋轉一周,即360°
- private SensorManager mSensorManager;// 傳感器管理對象
- private Sensor mOrientationSensor;// 傳感器對象
- private LocationManager mLocationManager;// 位置管理對象
- private String mLocationProvider;// 位置提供者名稱,GPS設備還是網絡
- private float mDirection;// 當前浮點方向
- private float mTargetDirection;// 目標浮點方向
- private AccelerateInterpolator mInterpolator;// 動畫從開始到結束,變化率是一個加速的過程,就是一個動畫速率
- protected final Handler mHandler = new Handler();
- private boolean mStopDrawing;// 是否停止指南針旋轉的標誌位
- private boolean mChinease;// 係統當前是否使用中文
- private long firstExitTime = 0L;// 用來保存第一次按返回鍵的時間
- View mCompassView;
- CompassView mPointer;// 指南針view
- TextView mLocationTextView;// 顯示位置的view
- LinearLayout mDirectionLayout;// 顯示方向(東南西北)的view
- LinearLayout mAngleLayout;// 顯示方向度數的view
- // 這個是更新指南針旋轉的線程,handler的靈活使用,每20毫秒檢測方向變化值,對應更新指南針旋轉
- protected Runnable mCompassViewUpdater = new Runnable() {
- @Override
- public void run() {
- if (mPointer != null && !mStopDrawing) {
- if (mDirection != mTargetDirection) {
- // calculate the short routine
- float to = mTargetDirection;
- if (to - mDirection > 180) {
- to -= 360;
- } else if (to - mDirection < -180) {
- to += 360;
- }
- // limit the max speed to MAX_ROTATE_DEGREE
- float distance = to - mDirection;
- if (Math.abs(distance) > MAX_ROATE_DEGREE) {
- distance = distance > 0 ? MAX_ROATE_DEGREE
- : (-1.0f * MAX_ROATE_DEGREE);
- }
- // need to slow down if the distance is short
- mDirection = normalizeDegree(mDirection
- + ((to - mDirection) * mInterpolator
- .getInterpolation(Math.abs(distance) > MAX_ROATE_DEGREE ? 0.4f
- : 0.3f)));// 用了一個加速動畫去旋轉圖片,很細致
- mPointer.updateDirection(mDirection);// 更新指南針旋轉
- }
- updateDirection();// 更新方向值
- mHandler.postDelayed(mCompassViewUpdater, 20);// 20毫米後重新執行自己,比定時器好
- }
- }
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- initResources();// 初始化view
- initServices();// 初始化傳感器和位置服務
- }
- @Override
- public void onBackPressed() {// 覆蓋返回鍵
- long curTime = System.currentTimeMillis();
- if (curTime - firstExitTime < EXIT_TIME) {// 兩次按返回鍵的時間小於2秒就退出應用
- finish();
- } else {
- Toast.makeText(this, R.string.exit_toast, Toast.LENGTH_SHORT)
- .show();
- firstExitTime = curTime;
- }
- }
- @Override
- protected void onResume() {// 在恢複的生命周期裏判斷、啟動位置更新服務和傳感器服務
- super.onResume();
- if (mLocationProvider != null) {
- updateLocation(mLocationManager
- .getLastKnownLocation(mLocationProvider));
- mLocationManager.requestLocationUpdates(mLocationProvider, 2000,
- 10, mLocationListener);// 2秒或者距離變化10米時更新一次地理位置
- } else {
- mLocationTextView.setText(R.string.cannot_get_location);
- }
- if (mOrientationSensor != null) {
- mSensorManager.registerListener(mOrientationSensorEventListener,
- mOrientationSensor, SensorManager.SENSOR_DELAY_GAME);
- } else {
- Toast.makeText(this, R.string.cannot_get_sensor, Toast.LENGTH_SHORT)
- .show();
- }
- mStopDrawing = false;
- mHandler.postDelayed(mCompassViewUpdater, 20);// 20毫秒執行一次更新指南針圖片旋轉
- }
- @Override
- protected void onPause() {// 在暫停的生命周期裏注銷傳感器服務和位置更新服務
- super.onPause();
- mStopDrawing = true;
- if (mOrientationSensor != null) {
- mSensorManager.unregisterListener(mOrientationSensorEventListener);
- }
- if (mLocationProvider != null) {
- mLocationManager.removeUpdates(mLocationListener);
- }
- }
- // 初始化view
- private void initResources() {
- mDirection = 0.0f;// 初始化起始方向
- mTargetDirection = 0.0f;// 初始化目標方向
- mInterpolator = new AccelerateInterpolator();// 實例化加速動畫對象
- mStopDrawing = true;
- mChinease = TextUtils.equals(Locale.getDefault().getLanguage(), "zh");// 判斷係統當前使用的語言是否為中文
- mCompassView = findViewById(R.id.view_compass);// 實際上是一個LinearLayout,裝指南針ImageView和位置TextView
- mPointer = (CompassView) findViewById(R.id.compass_pointer);// 自定義的指南針view
- mLocationTextView = (TextView) findViewById(R.id.textview_location);// 顯示位置信息的TextView
- mDirectionLayout = (LinearLayout) findViewById(R.id.layout_direction);// 頂部顯示方向名稱(東南西北)的LinearLayout
- mAngleLayout = (LinearLayout) findViewById(R.id.layout_angle);// 頂部顯示方向具體度數的LinearLayout
- mPointer.setImageResource(mChinease ? R.drawable.compass_cn
- : R.drawable.compass);// 如果係統使用中文,就用中文的指南針圖片
- }
- // 初始化傳感器和位置服務
- private void initServices() {
- // sensor manager
- mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
- mOrientationSensor = mSensorManager
- .getDefaultSensor(Sensor.TYPE_ORIENTATION);
- // location manager
- mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
- Criteria criteria = new Criteria();// 條件對象,即指定條件過濾獲得LocationProvider
- criteria.setAccuracy(Criteria.ACCURACY_FINE);// 較高精度
- criteria.setAltitudeRequired(false);// 是否需要高度信息
- criteria.setBearingRequired(false);// 是否需要方向信息
- criteria.setCostAllowed(true);// 是否產生費用
- criteria.setPowerRequirement(Criteria.POWER_LOW);// 設置低電耗
- mLocationProvider = mLocationManager.getBestProvider(criteria, true);// 獲取條件最好的Provider
- }
- // 更新頂部方向顯示的方法
- private void updateDirection() {
- LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- // 先移除layout中所有的view
- mDirectionLayout.removeAllViews();
- mAngleLayout.removeAllViews();
- // 下麵是根據mTargetDirection,作方向名稱圖片的處理
- ImageView east = null;
- ImageView west = null;
- ImageView south = null;
- ImageView north = null;
- float direction = normalizeDegree(mTargetDirection * -1.0f);
- if (direction > 22.5f && direction < 157.5f) {
- // east
- east = new ImageView(this);
- east.setImageResource(mChinease ? R.drawable.e_cn : R.drawable.e);
- east.setLayoutParams(lp);
- } else if (direction > 202.5f && direction < 337.5f) {
- // west
- west = new ImageView(this);
- west.setImageResource(mChinease ? R.drawable.w_cn : R.drawable.w);
- west.setLayoutParams(lp);
- }
- if (direction > 112.5f && direction < 247.5f) {
- // south
- south = new ImageView(this);
- south.setImageResource(mChinease ? R.drawable.s_cn : R.drawable.s);
- south.setLayoutParams(lp);
- } else if (direction < 67.5 || direction > 292.5f) {
- // north
- north = new ImageView(this);
- north.setImageResource(mChinease ? R.drawable.n_cn : R.drawable.n);
- north.setLayoutParams(lp);
- }
- // 下麵是根據係統使用語言,更換對應的語言圖片資源
- if (mChinease) {
- // east/west should be before north/south
- if (east != null) {
- mDirectionLayout.addView(east);
- }
- if (west != null) {
- mDirectionLayout.addView(west);
- }
- if (south != null) {
- mDirectionLayout.addView(south);
- }
- if (north != null) {
- mDirectionLayout.addView(north);
- }
- } else {
- // north/south should be before east/west
- if (south != null) {
- mDirectionLayout.addView(south);
- }
- if (north != null) {
- mDirectionLayout.addView(north);
- }
- if (east != null) {
- mDirectionLayout.addView(east);
- }
- if (west != null) {
- mDirectionLayout.addView(west);
- }
- }
- // 下麵是根據方向度數顯示度數圖片數字
- int direction2 = (int) direction;
- boolean show = false;
- if (direction2 >= 100) {
- mAngleLayout.addView(getNumberImage(direction2 / 100));
- direction2 %= 100;
- show = true;
- }
- if (direction2 >= 10 || show) {
- mAngleLayout.addView(getNumberImage(direction2 / 10));
- direction2 %= 10;
- }
- mAngleLayout.addView(getNumberImage(direction2));
- // 下麵是增加一個°的圖片
- ImageView degreeImageView = new ImageView(this);
- degreeImageView.setImageResource(R.drawable.degree);
- degreeImageView.setLayoutParams(lp);
- mAngleLayout.addView(degreeImageView);
- }
- // 獲取方向度數對應的圖片,返回ImageView
- private ImageView getNumberImage(int number) {
- ImageView image = new ImageView(this);
- LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- switch (number) {
- case 0:
- image.setImageResource(R.drawable.number_0);
- break;
- case 1:
- image.setImageResource(R.drawable.number_1);
- break;
- case 2:
- image.setImageResource(R.drawable.number_2);
- break;
- case 3:
- image.setImageResource(R.drawable.number_3);
- break;
- case 4:
- image.setImageResource(R.drawable.number_4);
- break;
- case 5:
- image.setImageResource(R.drawable.number_5);
- break;
- case 6:
- image.setImageResource(R.drawable.number_6);
- break;
- case 7:
- image.setImageResource(R.drawable.number_7);
- break;
- case 8:
- image.setImageResource(R.drawable.number_8);
- break;
- case 9:
- image.setImageResource(R.drawable.number_9);
- break;
- }
- image.setLayoutParams(lp);
- return image;
- }
- // 更新位置顯示
- private void updateLocation(Location location) {
- if (location == null) {
- mLocationTextView.setText(R.string.getting_location);
- } else {
- StringBuilder sb = new StringBuilder();
- double latitude = location.getLatitude();
- double longitude = location.getLongitude();
- if (latitude >= 0.0f) {
- sb.append(getString(R.string.location_north,
- getLocationString(latitude)));
- } else {
- sb.append(getString(R.string.location_south,
- getLocationString(-1.0 * latitude)));
- }
- sb.append(" ");
- if (longitude >= 0.0f) {
- sb.append(getString(R.string.location_east,
- getLocationString(longitude)));
- } else {
- sb.append(getString(R.string.location_west,
- getLocationString(-1.0 * longitude)));
- }
- mLocationTextView.setText(sb.toString());// 顯示經緯度,其實還可以作反向編譯,顯示具體地址
- }
- }
- // 把經緯度轉換成度分秒顯示
- private String getLocationString(double input) {
- int du = (int) input;
- int fen = (((int) ((input - du) * 3600))) / 60;
- int miao = (((int) ((input - du) * 3600))) % 60;
- return String.valueOf(du) + "°" + String.valueOf(fen) + "′"
- + String.valueOf(miao) + "″";
- }
- // 方向傳感器變化監聽
- private SensorEventListener mOrientationSensorEventListener = new SensorEventListener() {
- @Override
- public void onSensorChanged(SensorEvent event) {
- float direction = event.values[0] * -1.0f;
- mTargetDirection = normalizeDegree(direction);// 賦值給全局變量,讓指南針旋轉
- }
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- }
- };
- // 調整方向傳感器獲取的值
- private float normalizeDegree(float degree) {
- return (degree + 720) % 360;
- }
- // 位置信息更新監聽
- LocationListener mLocationListener = new LocationListener() {
- @Override
- public void onStatusChanged(String provider, int status, Bundle extras) {
- if (status != LocationProvider.OUT_OF_SERVICE) {
- updateLocation(mLocationManager
- .getLastKnownLocation(mLocationProvider));
- } else {
- mLocationTextView.setText(R.string.cannot_get_location);
- }
- }
- @Override