艺术来源于生活,对我来说,编程也是一门艺术。今天发布这篇技术博客,就是我跟朋友在一次爬山过程中的争论,他跟我说那座山至少45度,我说没有,最多30度。我们彼此争论不休,于是我就想,为啥不写个手机程序来实际测量一下?于是,我的工作就开始了。

硬件基础

因为我暂时没有笔记本,所以就用了我的ZTE-U807N作为编写平台,它虽然配置较低,但有加速度传感器就够了。

编程工具

我选择移动端开发工具,使用AIDE作为集成开发环境(表示它真的很强大)。

算法思想

加速度传感器可以测量三坐标分量,即X, Y, Z坐标。XY平面是手机平面,Z坐标垂直手机平面,手机触屏朝上Z值为正。通过简单的数学推导可以得出坡度的余弦值等于|Z|比上X、Y、Z的矢量和的模。写成公式就是如下:

g = √(X²+Y²+Z²)

cosθ = |z|/g

于是,我就能够通过Z和g得出坡度θ的值。

由于加速度传感器采样速率很快,而且测量值不准,总是会变,所以我采样滑动平均算法,将各测量分量进行积累,然后求取平均值来消除误差,同时也可以消除手机静止时坡度数值跳变的问题。

数据结构

使用队列存储各分量数据,等数据积累完毕以后每来一个数据就出一个老数据。

框架编写

我需要2个界面,一个主界面,就是现地坡度;另一个是关于界面,就是软件说明、作者等等。具体如图所示:





首先在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;
}

当然要给菜单添加单击处理:

@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

Android坡度计的更多相关文章

  1. 浅析Android线程模型一 --- 转

    摘要:随着中国移动在8月份相继发布基于Google Android的OPhone平台和手机网上应用商店Mobile Market,以及各大手机生产厂商在2009年北京国际通信展?上展出了各自基于And ...

  2. android线程 Handler Message Queue AsyncTask线程模型 线程交互 + 修改Button样式 示例 最终easy整合版

     首先原谅我把文章的标题写的这么长.其实我还嫌弃它短了因为 写不下去了所以我就不写了.因为我实在不知道该怎么定义这篇文章的标题或许应该叫 "乱谈"比较合适. 这样可能还体现了 ...

  3. Fragment提交transaction导致state loss异常

    下面自从Honeycomb发布后,下面栈跟踪信息和异常信息已经困扰了StackOverFlow很久了. java.lang.IllegalStateException: Can not perform ...

  4. Android中SQLite数据库小计

    2016-03-16 Android数据库支持 本文节选并翻译<Enterprise Android - Programing Android Database Applications for ...

  5. Android平台录音音量计的实现

    今天博主要给大家分享的是怎样在Android平台上实现录音时的音量指示计.开门见山.先来看一张Demo的效果图: 如上图所看到的,两个button各自是開始录音和停止录音,中间的两个数字前后分别代表音 ...

  6. Android - ScrollView 使用小计 里面嵌套的View 如何设置全屏

    设置ScrollView的属性android:fillViewport="true" 即可 <?xml version="1.0" encoding=&q ...

  7. Android开发过程遇到的问题小计

    1.在真机上正常运行,而模拟器会报出一些so文件找不到 unexpected e_machine: 40. 解决方法:采用x86的NDK进行编译,问题解决.

  8. android自定义控件一站式入门

    自定义控件 Android系统提供了一系列UI相关的类来帮助我们构造app的界面,以及完成交互的处理. 一般的,所有可以在窗口中被展示的UI对象类型,最终都是继承自View的类,这包括展示最终内容的非 ...

  9. [Android Pro] Android开发实践:自定义ViewGroup的onLayout()分析

    reference to : http://www.linuxidc.com/Linux/2014-12/110165.htm 前一篇文章主要讲了自定义View为什么要重载onMeasure()方法( ...

随机推荐

  1. python (1) 还不是大全的小问题

    1.pythone 获取系统时间 import datetime nowTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')#现在 pa ...

  2. yum 本地仓库搭建

    一,配置yum源 设置镜像 挂载 查看是否挂在成功 复制镜像内容到opt下面 删除不相关内容 进入/mnt/Packages 安装生成缓存文件 选择这个结尾的 更新本地yum源 yum clean a ...

  3. Maths Intro - Probability

    设事件A,B,C两辆独立,且满足ABC=空集,及P(A)=P(B)=P(C)=x,求max(x) x最大值为1/2分析: x值要保证所有的由A.B.C交或并得到的集合的概率测度在0到1之间. 先考虑A ...

  4. OSG DB的插件地址设置

    今天搞了一整天OSG,结果每次都说could not find plugin,就是说找不到OSG的插件去加载文件,我大概看了下OSG的插件机制,发现他是用插件的形式下去读取文件的 http://blo ...

  5. 安装Drupal

    我在虚拟机里面安装了Ubuntu Server 14.参考https://www.digitalocean.com/community/tutorials/how-to-install-drupal- ...

  6. SpringMvc上传文件遇到重复读取InputStream的问题

    文件上传配置: <bean id="multipartResolver" class="org.springframework.web.multipart.comm ...

  7. C和C++中include 搜索路径的一般形式以及gcc搜索头文件的路径

    C和C++中include 搜索路径的一般形式 对于include 搜索的路径: C中可以通过 #include <stdio.h> 和 #include "stidio.h&q ...

  8. pat00-自测2. 素数对猜想 (20)

    00-自测2. 素数对猜想 (20) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 让我们定义 dn 为:dn  ...

  9. Js内存泄漏的几种情况

    想解决内存泄露问题,必须知道什么是内存泄露,什么情况下出现内存泄露,才能在遇到问题时,逐个排除.这里只讨论那些不经意间的内存泄露. 一.什么是内存泄露 内存泄露是指一块被分配的内存既不能使用,又不能回 ...

  10. 【Postman】Postman的安装和使用

    Postman一款非常流行的API调试工具.其实,开发人员用的更多.因为测试人员做接口测试会有更多选择,例如Jmeter.soapUI等.不过,对于开发过程中去调试接口,Postman确实足够的简单方 ...