Android之指南针(电子罗盘)学习
5月12日更新到V5版:http://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="http://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
- public void onProviderEnabled(String provider) {
- }
- @Override
- public void onProviderDisabled(String provider) {
- }
- @Override
- public void onLocationChanged(Location location) {
- updateLocation(location);// 更新位置
- }
- };
- }
好了,核心代码就这些了,其实思路还是很简单,最后,感谢各位看到文章最后,祝愿各位程序猿们好好学习,天天向上!
Android之指南针(电子罗盘)学习的更多相关文章
- Android 开源项目及其学习
Android 系统研究:http://blog.csdn.net/luoshengyang/article/details/8923485 Android 腾讯技术人员博客 http://hukai ...
- Android自动化测试之Monkeyrunner学习笔记(一)
Android自动化测试之Monkeyrunner学习笔记(一) 因项目需要,开始研究Android自动化测试方法,对其中的一些工具.方法和框架做了一些简单的整理,其中包括Monkey.Monkeyr ...
- Android(java)学习笔记267:Android线程池形态
1. 线程池简介 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项任务所需时间为:T1 创建线程时间, ...
- Android(java)学习笔记207:开源项目使用之gif view
1. 由于android没有自带的gif动画,我在Android(java)学习笔记198:Android下的帧动画(Drawable Animation) 播客中提到可以使用AnimationVie ...
- Android(java)学习笔记71:生产者和消费者之等待唤醒机制
1. 首先我们根据梳理我们之前Android(java)学习笔记70中关于生产者和消费者程序思路: 2. 下面我们就要重点介绍这个等待唤醒机制: (1)第一步:还是先通过代码体现出等待唤醒机制 pac ...
- Android(java)学习笔记167:Java中操作文件的类介绍(File + IO流)
1.File类:对硬盘上的文件和目录进行操作的类. File类是文件和目录路径名抽象表现形式 构造函数: 1) File(String pathname) Creat ...
- Android(java)学习笔记160:Framework运行环境之 Android进程产生过程
1.前面Android(java)学习笔记159提到Dalvik虚拟机启动初始化过程,就下来就是启动zygote进程: zygote进程是所有APK应用进程的父进程:每当执行一个Android应用程序 ...
- Android(java)学习笔记179:BroadcastReceiver之 有序广播和无序广播(BroadcastReceiver优先级)
之前我们在Android(java)学习笔记178中自定义的广播是无序广播,下面我们要了解一下有序广播: 1. 我们首先了解一下有序广播和无序广播区别和联系? (1) 有序广播> 接受者 ...
- Android(java)学习笔记206:利用开源SmartImageView优化网易新闻RSS客户端
1.我们自己编写的SmartImageView会有很多漏洞,但是我们幸运的可以在网上利用开源项目的,开源项目中有很多成熟的代码,比如SmartImageView都编写的很成熟的 国内我们经常用到htt ...
- Android(java)学习笔记205:网易新闻RSS客户端应用编写逻辑过程
1.我们的项目需求是编写一个新闻RSS浏览器,RSS(Really Simple Syndication)是一种描述和同步网站内容的格式,是使用最广泛的XML应用.RSS目前广泛用于网上新闻频道,bl ...
随机推荐
- CSS文件和Javascript文件的压缩
像JQuery一样来压缩我们的CSS和JS 我们都知道一般JQuery新版本发布的时候往往会有几个不同类型文件,比如原始版本文件.最小文件以及其他配合IDE智能提示的各种版本文件,前期我们使用JQue ...
- 【leetcode】Word Break (middle)
Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separa ...
- http://www.cnblogs.com/doit8791/p/4093808.html
http://www.cnblogs.com/doit8791/p/4093808.html
- Ubuntu Server 中resolv.conf重启时被覆盖的问题
/etc/resolv.conf中设置dns之后每次重启Ubuntu Server时该文件会被覆盖,针对这种情况找了一些个解决方法 防止/etc/resolv.conf被覆盖的方法 方法一 1.需要创 ...
- PHP开篇之环境的搭建
PHP开篇之环境的搭建 Wamp软件下载:http://www.wampserver.com/ 此时是2.5版本 下载下来一键安装. 安装有个主意 这里先不用管 或者smtp@qq.com 13643 ...
- C++遍历目录,并把目录里超过7天的文件删除(跨平台windows&linux)
C++遍历目录,并把目录里超过7天的文件删除,适用于项目里删除过期的日志,或者视频文件. 在windows和linux下测试通过. windows测试结果: linux测试结果: 源码: #inclu ...
- 在服务器端使用 Git 创建源代码仓库
下面简单讲述在服务器搭建 Git 仓库的过程. 安装 Git 程序 Git 是分布式的,即程序不区分服务端和客户端,大部分 Linux 发行版的官方源里都有它,比如在 Archlinux 里安装 Gi ...
- Android ScrollView+ViewPager+PullToRefreshListView
想达到此界面的风格 然后ViewPage里面第一个Fragment是一个瀑布流 这个瀑布流要有加载跟多 在ScrollView中,嵌套ViewPager,在ViewPager的每页使用Fragment ...
- 【一】 sched.h
第一个数据结构体是 task_struct ,这个数据结构被内核用来表示进程,包含其所有信息. 定义于文件 include/linux/sched.h 中,先看看其完整定义 struct task_s ...
- minitools
1.android 2.linux 3.luoji 4.windows CE ----