github 地址:https://github.com/zhouguangfu09/StepCounter
1. 程序圖標
2. 點擊圖標,進入如下界面:
這個界面會有緩衝效果,然後進入程序的主界面.
3.程序主界面:
點擊開始按鈕,並甩動胳膊,計步器開始計數,也可以暫停計數,如下圖所示:
5.每走150步,系統獎勵一個星星,最高10顆星星。同時軟件會粗略的計算行程、熱量、速度等參數。如上圖所示。
6.點擊手機菜單鍵,點擊“設置”選項,進入如下界面:
軟件記步數的精準度跟用戶的補償以及體重有關,也跟用戶設置的傳感器的靈敏度有關係,在設置頁面可以對相應的參數進行調節。一旦調節結束,可以重新開始。
7.在手機的主界面按返回鍵,退出程序。
源碼部分解析:
源碼主要包含UI控制邏輯代碼以及胳膊甩動檢測步數代碼,下面重點說一下 步數檢測代碼,主要集中在StepDector.Java文件中:
- public void onSensorChanged(SensorEventevent) {
- // Log.i(Constant.STEP_SERVER, "StepDetector");
- Sensor sensor = event.sensor;
- // Log.i(Constant.STEP_DETECTOR,"onSensorChanged");
- synchronized (this) {
- if (sensor.getType() ==Sensor.TYPE_ORIENTATION) {
- } else {
- int j =(sensor.getType() == Sensor.TYPE_ACCELEROMETER) ? 1 : 0;
- if (j == 1) {
- float vSum =0;
- for (int i =0; i < 3; i++) {
- finalfloat v = mYOffset + event.values[i] * mScale[j];
- vSum+= v;
- }
- int k = 0;
- float v =vSum / 3;
- floatdirection = (v > mLastValues[k] ? 1: (v < mLastValues[k] ? -1 : 0));
- if (direction== -mLastDirections[k]) {
- //Direction changed
- intextType = (direction > 0 ? 0 : 1); // minumum or
- //maximum?
- mLastExtremes[extType][k]= mLastValues[k];
- floatdiff = Math.abs(mLastExtremes[extType][k]- mLastExtremes[1 - extType][k]);
- if(diff > SENSITIVITY) {
- booleanisAlmostAsLargeAsPrevious = diff > (mLastDiff[k] * 2 / 3);
- booleanisPreviousLargeEnough = mLastDiff[k] > (diff / 3);
- booleanisNotContra = (mLastMatch != 1 - extType);
- if(isAlmostAsLargeAsPrevious && isPreviousLargeEnough &&isNotContra) {
- end= System.currentTimeMillis();
- if(end - start > 500) {// 此時判斷爲走了一步
- Log.i("StepDetector","CURRENT_SETP:"
- +CURRENT_SETP);
- CURRENT_SETP++;
- mLastMatch= extType;
- start= end;
- }
- }else {
- mLastMatch= -1;
- }
- }
- mLastDiff[k]= diff;
- }
- mLastDirections[k]= direction;
- mLastValues[k]= v;
- }
- }
- }
這個函數是一個傳感器的回調函數,在其中可以根據從系統地加速度傳感器獲取的數值進行胳膊甩動動作的判斷。主要從以下幾個方面判斷:
(1)人如果走起來了,一般會連續多走幾步。因此,如果沒有連續4-5個波動,那麼就極大可能是干擾。
(2)人走動的波動,比坐車產生的波動要大,因此可以看波峯波谷的高度,只檢測高於某個高度的波峯波谷。
(3)人的反射神經決定了人快速動的極限,怎麼都不可能兩步之間小於0.2秒,因此間隔小於0.2秒的波峯波谷直接跳過
通過重力加速計感應,重力變化的方向,大小。與正常走路或跑步時的重力變化比對,達到一定相似度時認爲是在走路或跑步。實現起來很簡單,只要手機有重力感應器就能實現。
1. 啓動界面
程序啓動界面帶有緩衝效果,其中界面緩衝效果的配置文件在res/anim/目錄下。如果計步器已經開始工作,則跳過程序啓動界面,直接進入主界面。
- if (StepCounterService.FLAG || StepDetector.CURRENT_SETP > 0) {// 程序已經啓動,直接跳轉到運行界面
- Intent intent = new Intent(SplashActivity.this,StepCounterActivity.class); //創建一個新的Intent,指定當前應用程序上下文
- //和要啓動的StepActivity類
- startActivity(intent); //傳遞這個intent給startActivity
- this.finish();
- } else {
- this.requestWindowFeature(Window.FEATURE_NO_TITLE);
- this.setContentView(R.layout.splash);
- animation = AnimationUtils.loadAnimation(SplashActivity.this,
- R.anim.animation_splash);
- this.findViewById(R.id.iv_index).setAnimation(animation);
- animation.setAnimationListener(new AnimationListener() {// 動畫監聽,通過AnimationListener可以監聽Animation的運行過程
- @Override
- public void onAnimationStart(Animation animation){
- // TODOAuto-generated method stub
- }
- @Override
- public void onAnimationRepeat(Animationanimation) {
- // TODOAuto-generated method stub
- }
- @Override
- public void onAnimationEnd(Animation animation) {// 動畫結束時跳轉至運行界面
- // TODOAuto-generated method stub
- Intent intent = new Intent(SplashActivity.this,
- StepCounterActivity.class);
- SplashActivity.this.startActivity(intent);
- SplashActivity.this.finish();
- }
- });
- }
- }
2. 程序主界面
(1)界面元素的初始化(比如步數,星期,日期,運行時間,行程,卡路里,速度,開始按鈕和停止按鈕)。
- //定義文本框控件
- private TextView tv_show_step;// 步數
- private TextView tv_week_day;// 星期
- private TextView tv_date;// 日期
- private TextView tv_timer;// 運行時間
- private TextView tv_distance;// 行程
- private TextView tv_calories;// 卡路里
- private TextView tv_velocity;// 速度
- private Button btn_start;// 開始按鈕
- private Button btn_stop;// 停止按鈕
- // 十顆星標
- private ImageView iv_star_1;
- private ImageView iv_star_2;
- private ImageView iv_star_3;
- private ImageView iv_star_4;
- private ImageView iv_star_5;
- private ImageView iv_star_6;
- private ImageView iv_star_7;
- private ImageView iv_star_8;
- private ImageView iv_star_9;
- private ImageView iv_star_10;
- private long timer = 0;// 運動時間
- private static long startTimer = 0;// 開始時間
- private static long tempTime = 0;
- private Double distance = 0.0;// 路程:米
- private Double calories = 0.0;// 熱量:卡路里
- private Double velocity = 0.0;// 速度:米每秒
- private int step_length = 0; //步長
- private int weight = 0; //體重
- private int total_step = 0; //走的總步數
- DatagramSocket socket;
- InetAddress serverAddress;
- DatagramPacket send_package1;
- private Thread thread; //定義線程對象
(2)利用消息機制對主界面的UI元素進行更新。定義了一個handler,當檢測的步數變化時,更新主界面顯示的信息。主要代碼如下:
- Handler handler = new Handler() {// Handler對象用於更新當前步數,定時發送消息,調用方法查詢數據用於顯示??????????
- //主要接受子線程發送的數據, 並用此數據配合主線程更新UI
- //Handler運行在主線程中(UI線程中), 它與子線程可以通過Message對象來傳遞數據,
- //Handler就承擔着接受子線程傳過來的(子線程用sendMessage()方法傳遞Message對象,(裏面包含數據)
- //把這些消息放入主線程隊列中,配合主線程進行更新UI。
- @Override //這個方法是從父類/接口繼承過來的,需要重寫一次
- public void handleMessage(Message msg) {
- // TODO Auto-generated method stub
- super.handleMessage(msg); // 此處可以更新UI
- countDistance(); //調用距離方法,看一下走了多遠
- if (timer != 0 && distance != 0.0) {
- // 體重、距離
- // 跑步熱量(kcal)=體重(kg)×距離(公里)×1.036
- calories = weight * distance * 0.001;
- //速度velocity
- velocity = distance * 1000 / timer;
- } else {
- calories =0.0;
- velocity =0.0;
- }
- countStep(); //調用步數方法
- tv_show_step.setText(total_step + "");// 顯示當前步數
- tv_distance.setText(formatDouble(distance));// 顯示路程
- tv_calories.setText(formatDouble(calories));// 顯示卡路里
- tv_velocity.setText(formatDouble(velocity));// 顯示速度
- tv_timer.setText(getFormatTime(timer));// 顯示當前運行時間
- changeStep();// 設置當前步數和星標
- }
(3)初始化主界面的信息。
- /**
- * 初始化界面
- */
- private void init() {
- step_length =SettingsActivity.sharedPreferences.getInt(
- SettingsActivity.STEP_LENGTH_VALUE, 70);
- weight =SettingsActivity.sharedPreferences.getInt(
- SettingsActivity.WEIGHT_VALUE, 50);
- countDistance();
- countStep();
- if ((timer += tempTime) != 0 && distance != 0.0) { //tempTime記錄運動的總時間,timer記錄每次運動時間
- // 體重、距離
- // 跑步熱量(kcal)=體重(kg)×距離(公里)×1.036,換算一下
- calories = weight * distance * 0.001;
- velocity = distance * 1000 / timer;
- } else {
- calories = 0.0;
- velocity = 0.0;
- }
- tv_timer.setText(getFormatTime(timer + tempTime));
- tv_distance.setText(formatDouble(distance));
- tv_calories.setText(formatDouble(calories));
- tv_velocity.setText(formatDouble(velocity));
- tv_show_step.setText(total_step + "");
- btn_start.setEnabled(!StepCounterService.FLAG);
- btn_stop.setEnabled(StepCounterService.FLAG);
- if(StepCounterService.FLAG) {
- btn_stop.setText(getString(R.string.pause));
- } else if (StepDetector.CURRENT_SETP > 0) {
- btn_stop.setEnabled(true);
- btn_stop.setText(getString(R.string.cancel));
- }
- setDate();
- }
(4)計算距離和步長。
- /**
- * 計算行走的距離
- */
- private void countDistance() {
- if (StepDetector.CURRENT_SETP % 2 == 0) {
- distance = (StepDetector.CURRENT_SETP / 2) * 3 * step_length * 0.01;
- } else {
- distance = ((StepDetector.CURRENT_SETP / 2) * 3 + 1) * step_length * 0.01;
- }
- }
- /**
- * 實際的步數
- */
- private void countStep() {
- if (StepDetector.CURRENT_SETP % 2 == 0) {
- total_step = StepDetector.CURRENT_SETP;
- } else {
- total_step = StepDetector.CURRENT_SETP +1;
- }
- total_step = StepDetector.CURRENT_SETP;
- }
3. 後臺檢測傳感器信息的服務(service).
- //service負責後臺的需要長期運行的任務
- // 計步器服務
- // 運行在後臺的服務程序,完成了界面部分的開發後
- // 就可以開發後臺的服務類StepService
- // 註冊或註銷傳感器監聽器,在手機屏幕狀態欄顯示通知,與StepActivity進行通信,走過的步數記到哪裏了???
- public class StepCounterService extends Service {
- public static Boolean FLAG = false;// 服務運行標誌
- private SensorManagermSensorManager;// 傳感器服務
- private StepDetector detector;// 傳感器監聽對象
- private PowerManager mPowerManager;// 電源管理服務
- private WakeLock mWakeLock;// 屏幕燈
- @Override
- public IBinder onBind(Intent intent) {
- // TODOAuto-generated method stub
- return null;
- }
- @Override
- public void onCreate() {
- // TODOAuto-generated method stub
- super.onCreate();
- FLAG = true;// 標記爲服務正在運行
- // 創建監聽器類,實例化監聽對象
- detector = new StepDetector(this);
- // 獲取傳感器的服務,初始化傳感器
- mSensorManager = (SensorManager) this.getSystemService(SENSOR_SERVICE);
- // 註冊傳感器,註冊監聽器
- mSensorManager.registerListener(detector,
- mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
- SensorManager.SENSOR_DELAY_FASTEST);
- // 電源管理服務
- mPowerManager = (PowerManager) this
- .getSystemService(Context.POWER_SERVICE);
- mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK
- |PowerManager.ACQUIRE_CAUSES_WAKEUP, "S");
- mWakeLock.acquire();
- }
- @Override
- public void onDestroy() {
- // TODOAuto-generated method stub
- super.onDestroy();
- FLAG = false;// 服務停止
- if (detector != null) {
- mSensorManager.unregisterListener(detector);
- }
- if (mWakeLock != null) {
- mWakeLock.release();
- }
- }