• Android坡度计


    艺术来源于生活,对我来说,编程也是一门艺术。今天发布这篇技术博客,就是我跟朋友在一次爬山过程中的争论,他跟我说那座山至少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

  • 相关阅读:
    收集一些dos网络配置命令,从新获取ip刷新dns
    多个线程访问共享对象和数据的方式
    Oracle rownum 分页, 排序
    ORACLE中用rownum分页并排序的SQL语句
    CentOS 6.5安装MongoDB 2.6(多yum数据源)
    【编程练习】收集的一些c++代码片,算法排序,读文件,写日志,快速求积分等等
    java枚举使用详解
    PHP+MySQL动态网站开发从入门到精通(视频教学版)
    Premiere Pro CS6标准教程
    黑客攻防:实战加密与解密
  • 原文地址:https://www.cnblogs.com/hele-two/p/4563243.html
Copyright © 2020-2023  润新知