点我下载源码
5月12日更新到V5版:/detail/weidi1989/5364243
今天,在小米的开源项目中下载了一个指南针源码学习了一下,感觉不仅界面做得很漂亮,代码也是很精致,所以我在研究了之后,加了比较多的注释,果断跟大家分享一下,很精简的几段代码,仔细品味可以学到很多东西,我稍微总结一下:
①.handler的灵活运用,每20秒后执行一次自己,用来检测方向变化值,更新指南针旋转。
②.传感器和谷歌位置服务的使用。
③.自定义View,这里面是自定义一个ImageView,自己增加一个旋转图片的方法。
④.Android动画Interpolator插入器:AccelerateInterpolator加速插入器的运用。顺便说一下另外几个插入器:
——AccelerateInterpolator:动画从开始到结束,变化率是一个加速的过程。
——DecelerateInterpolator:动画从开始到结束,变化率是一个减速的过程。
——CycleInterpolator:动画从开始到结束,变化率是循环给定次数的正弦曲线。
——AccelerateDecelerateInterpolator:动画从开始到结束,变化率是先加速后减速的过程。
——LinearInterpolator:动画从开始到结束,变化率是线性变化。
AccelerateInterpolator有一个方法:getInterpolation(float input);
⑤.巧妙的数字替换成对应的数字图片和根据本地语言使用对应的图片资源(图片资源国际化,哈哈)。还有一些其他的小知识,朋友们,自己下载去研究吧!
下面看一下效果图(我的是模拟器,木有传感器也木有定位的):
下面我们来看一下这个界面的布局文件(main.xml):
[html]view plaincopy <?xmlversion="1.0"encoding="UTF-8"?><FrameLayoutxmlns:android="/apk/res/android"android:layout_width="fill_parent"android:layout_height="fill_parent"><FrameLayoutandroid:layout_width="fill_parent"android:layout_height="fill_parent"android:background="@drawable/background"><LinearLayoutandroid:id="@+id/view_compass"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="@drawable/background_light"android:orientation="vertical"><LinearLayoutandroid:layout_width="fill_parent"android:layout_height="0dip"android:layout_weight="1"android:orientation="vertical"><FrameLayoutandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:background="@drawable/prompt"><LinearLayoutandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginTop="70dip"android:orientation="horizontal"><LinearLayoutandroid:id="@+id/layout_direction"android:layout_width="0dip"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="right"android:orientation="horizontal"></LinearLayout><ImageViewandroid:layout_width="20dip"android:layout_height="fill_parent"></ImageView><LinearLayoutandroid: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><LinearLayoutandroid:layout_width="fill_parent"android:layout_height="0dip"android:layout_weight="1"android:orientation="vertical"><FrameLayoutandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_gravity="center"><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:src="@drawable/background_compass"/><passViewandroid:id="@+id/compass_pointer"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:src="@drawable/compass"/><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:src="@drawable/miui_cover"/></FrameLayout></LinearLayout></LinearLayout><FrameLayoutandroid:id="@+id/location_layout"android:layout_width="fill_parent"android:layout_height="wrap_content"><LinearLayoutandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:background="@drawable/background_bottom"android:orientation="vertical"></LinearLayout><TextViewandroid: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):
[java]view plaincopy /***自定义一个View继承ImageView,增加一个通用的旋转图片资源的方法**@authorway**/publicclassCompassViewextendsImageView{privatefloatmDirection;//方向旋转浮点数privateDrawablecompass;//图片资源//三个构造器publicCompassView(Contextcontext){super(context);mDirection=0.0f;//默认不旋转compass=null;}publicCompassView(Contextcontext,AttributeSetattrs){super(context,attrs);mDirection=0.0f;compass=null;}publicCompassView(Contextcontext,AttributeSetattrs,intdefStyle){super(context,attrs,defStyle);mDirection=0.0f;compass=null;}@OverrideprotectedvoidonDraw(Canvascanvas){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();//保存一下}/***自定义更新方向的方法**@paramdirection*传入的方向*/publicvoidupdateDirection(floatdirection){mDirection=direction;invalidate();//重新刷新一下,更新方向}}
接下来就只剩下一个Activity了,其实总体结构还是很简单的,CompassActivity.java:
[java]view plaincopy /***MainActivity**@authorway**/publicclassCompassActivityextendsActivity{privatestaticfinalintEXIT_TIME=2000;//两次按返回键的间隔判断privatefinalfloatMAX_ROATE_DEGREE=1.0f;//最多旋转一周,即360°privateSensorManagermSensorManager;//传感器管理对象privateSensormOrientationSensor;//传感器对象privateLocationManagermLocationManager;//位置管理对象privateStringmLocationProvider;//位置提供者名称,GPS设备还是网络privatefloatmDirection;//当前浮点方向privatefloatmTargetDirection;//目标浮点方向privateAccelerateInterpolatormInterpolator;//动画从开始到结束,变化率是一个加速的过程,就是一个动画速率protectedfinalHandlermHandler=newHandler();privatebooleanmStopDrawing;//是否停止指南针旋转的标志位privatebooleanmChinease;//系统当前是否使用中文privatelongfirstExitTime=0L;//用来保存第一次按返回键的时间ViewmCompassView;CompassViewmPointer;//指南针viewTextViewmLocationTextView;//显示位置的viewLinearLayoutmDirectionLayout;//显示方向(东南西北)的viewLinearLayoutmAngleLayout;//显示方向度数的view//这个是更新指南针旋转的线程,handler的灵活使用,每20毫秒检测方向变化值,对应更新指南针旋转protectedRunnablemCompassViewUpdater=newRunnable(){@Overridepublicvoidrun(){if(mPointer!=null&&!mStopDrawing){if(mDirection!=mTargetDirection){//calculatetheshortroutinefloatto=mTargetDirection;if(to-mDirection>180){to-=360;}elseif(to-mDirection<-180){to+=360;}//limitthemaxspeedtoMAX_ROTATE_DEGREEfloatdistance=to-mDirection;if(Math.abs(distance)>MAX_ROATE_DEGREE){distance=distance>0?MAX_ROATE_DEGREE:(-1.0f*MAX_ROATE_DEGREE);}//needtoslowdownifthedistanceisshortmDirection=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毫米后重新执行自己,比定时器好}}};@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);initResources();//初始化viewinitServices();//初始化传感器和位置服务}@OverridepublicvoidonBackPressed(){//覆盖返回键longcurTime=System.currentTimeMillis();if(curTime-firstExitTime<EXIT_TIME){//两次按返回键的时间小于2秒就退出应用finish();}else{Toast.makeText(this,R.string.exit_toast,Toast.LENGTH_SHORT).show();firstExitTime=curTime;}}@OverrideprotectedvoidonResume(){//在恢复的生命周期里判断、启动位置更新服务和传感器服务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毫秒执行一次更新指南针图片旋转}@OverrideprotectedvoidonPause(){//在暂停的生命周期里注销传感器服务和位置更新服务super.onPause();mStopDrawing=true;if(mOrientationSensor!=null){mSensorManager.unregisterListener(mOrientationSensorEventListener);}if(mLocationProvider!=null){mLocationManager.removeUpdates(mLocationListener);}}//初始化viewprivatevoidinitResources(){mDirection=0.0f;//初始化起始方向mTargetDirection=0.0f;//初始化目标方向mInterpolator=newAccelerateInterpolator();//实例化加速动画对象mStopDrawing=true;mChinease=TextUtils.equals(Locale.getDefault().getLanguage(),"zh");//判断系统当前使用的语言是否为中文mCompassView=findViewById(R.id.view_compass);//实际上是一个LinearLayout,装指南针ImageView和位置TextViewmPointer=(CompassView)findViewById(pass_pointer);//自定义的指南针viewmLocationTextView=(TextView)findViewById(R.id.textview_location);//显示位置信息的TextViewmDirectionLayout=(LinearLayout)findViewById(R.id.layout_direction);//顶部显示方向名称(东南西北)的LinearLayoutmAngleLayout=(LinearLayout)findViewById(R.id.layout_angle);//顶部显示方向具体度数的LinearLayoutmPointer.setImageResource(mChinease?pass_cn:pass);//如果系统使用中文,就用中文的指南针图片}//初始化传感器和位置服务privatevoidinitServices(){//sensormanagermSensorManager=(SensorManager)getSystemService(Context.SENSOR_SERVICE);mOrientationSensor=mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);//locationmanagermLocationManager=(LocationManager)getSystemService(Context.LOCATION_SERVICE);Criteriacriteria=newCriteria();//条件对象,即指定条件过滤获得LocationProvidercriteria.setAccuracy(Criteria.ACCURACY_FINE);//较高精度criteria.setAltitudeRequired(false);//是否需要高度信息criteria.setBearingRequired(false);//是否需要方向信息criteria.setCostAllowed(true);//是否产生费用criteria.setPowerRequirement(Criteria.POWER_LOW);//设置低电耗mLocationProvider=mLocationManager.getBestProvider(criteria,true);//获取条件最好的Provider}//更新顶部方向显示的方法privatevoidupdateDirection(){LayoutParamslp=newLayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);//先移除layout中所有的viewmDirectionLayout.removeAllViews();mAngleLayout.removeAllViews();//下面是根据mTargetDirection,作方向名称图片的处理ImageVieweast=null;ImageViewwest=null;ImageViewsouth=null;ImageViewnorth=null;floatdirection=normalizeDegree(mTargetDirection*-1.0f);if(direction>22.5f&&direction<157.5f){//easteast=newImageView(this);east.setImageResource(mChinease?R.drawable.e_cn:R.drawable.e);east.setLayoutParams(lp);}elseif(direction>202.5f&&direction<337.5f){//westwest=newImageView(this);west.setImageResource(mChinease?R.drawable.w_cn:R.drawable.w);west.setLayoutParams(lp);}if(direction>112.5f&&direction<247.5f){//southsouth=newImageView(this);south.setImageResource(mChinease?R.drawable.s_cn:R.drawable.s);south.setLayoutParams(lp);}elseif(direction<67.5||direction>292.5f){//northnorth=newImageView(this);north.setImageResource(mChinease?R.drawable.n_cn:R.drawable.n);north.setLayoutParams(lp);}//下面是根据系统使用语言,更换对应的语言图片资源if(mChinease){//east/westshouldbebeforenorth/southif(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/southshouldbebeforeeast/westif(south!=null){mDirectionLayout.addView(south);}if(north!=null){mDirectionLayout.addView(north);}if(east!=null){mDirectionLayout.addView(east);}if(west!=null){mDirectionLayout.addView(west);}}//下面是根据方向度数显示度数图片数字intdirection2=(int)direction;booleanshow=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));//下面是增加一个°的图片ImageViewdegreeImageView=newImageView(this);degreeImageView.setImageResource(R.drawable.degree);degreeImageView.setLayoutParams(lp);mAngleLayout.addView(degreeImageView);}//获取方向度数对应的图片,返回ImageViewprivateImageViewgetNumberImage(intnumber){ImageViewimage=newImageView(this);LayoutParamslp=newLayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);switch(number){case0:image.setImageResource(R.drawable.number_0);break;case1:image.setImageResource(R.drawable.number_1);break;case2:image.setImageResource(R.drawable.number_2);break;case3:image.setImageResource(R.drawable.number_3);break;case4:image.setImageResource(R.drawable.number_4);break;case5:image.setImageResource(R.drawable.number_5);break;case6:image.setImageResource(R.drawable.number_6);break;case7:image.setImageResource(R.drawable.number_7);break;case8:image.setImageResource(R.drawable.number_8);break;case9:image.setImageResource(R.drawable.number_9);break;}image.setLayoutParams(lp);returnimage;}//更新位置显示privatevoidupdateLocation(Locationlocation){if(location==null){mLocationTextView.setText(R.string.getting_location);}else{StringBuildersb=newStringBuilder();doublelatitude=location.getLatitude();doublelongitude=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());//显示经纬度,其实还可以作反向编译,显示具体地址}}//把经纬度转换成度分秒显示privateStringgetLocationString(doubleinput){intdu=(int)input;intfen=(((int)((input-du)*3600)))/60;intmiao=(((int)((input-du)*3600)))%60;returnString.valueOf(du)+"°"+String.valueOf(fen)+"′"+String.valueOf(miao)+"″";}//方向传感器变化监听privateSensorEventListenermOrientationSensorEventListener=newSensorEventListener(){@OverridepublicvoidonSensorChanged(SensorEventevent){floatdirection=event.values[0]*-1.0f;mTargetDirection=normalizeDegree(direction);//赋值给全局变量,让指南针旋转}@OverridepublicvoidonAccuracyChanged(Sensorsensor,intaccuracy){}};//调整方向传感器获取的值privatefloatnormalizeDegree(floatdegree){return(degree+720)%360;}//位置信息更新监听LocationListenermLocationListener=newLocationListener(){@OverridepublicvoidonStatusChanged(Stringprovider,intstatus,Bundleextras){if(status!=LocationProvider.OUT_OF_SERVICE){updateLocation(mLocationManager.getLastKnownLocation(mLocationProvider));}else{mLocationTextView.setText(R.string.cannot_get_location);}}@OverridepublicvoidonProviderEnabled(Stringprovider){}@OverridepublicvoidonProviderDisabled(Stringprovider){}@OverridepublicvoidonLocationChanged(Locationlocation){updateLocation(location);//更新位置}};}
好了,核心代码就这些了,其实思路还是很简单,最后,感谢各位看到文章最后,祝愿各位程序猿们好好学习,天天向上!