Android 计步器开发

转自:http://blog.csdn.net/guang09080908/article/details/41411679

github 地址:https://github.com/zhouguangfu09/StepCounter

1.    程序图标


2.    点击图标,进入如下界面:


这个界面会有缓冲效果,然后进入程序的主界面.

3.程序主界面:

点击开始按钮,并甩动胳膊,计步器开始计数,也可以暂停计数,如下图所示:


5.每走150步,系统奖励一个星星,最高10颗星星。同时软件会粗略的计算行程、热量、速度等参数。如上图所示。


6.点击手机菜单键,点击“设置”选项,进入如下界面:


软件记步数的精准度跟用户的补偿以及体重有关,也跟用户设置的传感器的灵敏度有关系,在设置页面可以对相应的参数进行调节。一旦调节结束,可以重新开始。

7.在手机的主界面按返回键,退出程序。

 

源码部分解析

源码主要包含UI控制逻辑代码以及胳膊甩动检测步数代码,下面重点说一下 步数检测代码,主要集中在StepDector.Java文件中:

[java]  view plain  copy
  1. public void onSensorChanged(SensorEventevent) {  
  2.        // Log.i(Constant.STEP_SERVER, "StepDetector");  
  3.        Sensor sensor = event.sensor;  
  4.        // Log.i(Constant.STEP_DETECTOR,"onSensorChanged");  
  5.        synchronized (this) {  
  6.               if (sensor.getType() ==Sensor.TYPE_ORIENTATION) {  
  7.               } else {  
  8.                      int j =(sensor.getType() == Sensor.TYPE_ACCELEROMETER) ? 1 : 0;  
  9.                      if (j == 1) {  
  10.                             float vSum =0;  
  11.                             for (int i =0; i < 3; i++) {  
  12.                                    finalfloat v = mYOffset + event.values[i] * mScale[j];  
  13.                                    vSum+= v;  
  14.                             }  
  15.                             int k = 0;  
  16.                             float v =vSum / 3;  
  17.   
  18.                             floatdirection = (v > mLastValues[k] ? 1: (v < mLastValues[k] ? -1 : 0));  
  19.                             if (direction== -mLastDirections[k]) {  
  20.                                    //Direction changed  
  21.                                    intextType = (direction > 0 ? 0 : 1); // minumum or  
  22.                                    //maximum?  
  23.                                    mLastExtremes[extType][k]= mLastValues[k];  
  24.                                    floatdiff = Math.abs(mLastExtremes[extType][k]- mLastExtremes[1 - extType][k]);  
  25.   
  26.                                    if(diff > SENSITIVITY) {  
  27.                                           booleanisAlmostAsLargeAsPrevious = diff > (mLastDiff[k] * 2 / 3);  
  28.                                           booleanisPreviousLargeEnough = mLastDiff[k] > (diff / 3);  
  29.                                           booleanisNotContra = (mLastMatch != 1 - extType);  
  30.   
  31.                                           if(isAlmostAsLargeAsPrevious && isPreviousLargeEnough &&isNotContra) {  
  32.                                                  end= System.currentTimeMillis();  
  33.                                                  if(end - start > 500) {// 此时判断为走了一步  
  34.                                                         Log.i("StepDetector","CURRENT_SETP:"  
  35.                                                                       +CURRENT_SETP);  
  36.                                                         CURRENT_SETP++;  
  37.                                                         mLastMatch= extType;  
  38.                                                         start= end;  
  39.                                                  }  
  40.                                           }else {  
  41.                                                  mLastMatch= -1;  
  42.                                           }  
  43.                                    }  
  44.                                    mLastDiff[k]= diff;  
  45.                             }  
  46.                             mLastDirections[k]= direction;  
  47.                             mLastValues[k]= v;  
  48.                      }  
  49.               }  
  50.        }  
 

这个函数是一个传感器的回调函数,在其中可以根据从系统地加速度传感器获取的数值进行胳膊甩动动作的判断。主要从以下几个方面判断:

(1)人如果走起来了,一般会连续多走几步。因此,如果没有连续4-5个波动,那么就极大可能是干扰。 

(2)人走动的波动,比坐车产生的波动要大,因此可以看波峰波谷的高度,只检测高于某个高度的波峰波谷。

(3)人的反射神经决定了人快速动的极限,怎么都不可能两步之间小于0.2秒,因此间隔小于0.2秒的波峰波谷直接跳过

通过重力加速计感应,重力变化的方向,大小。与正常走路或跑步时的重力变化比对,达到一定相似度时认为是在走路或跑步。实现起来很简单,只要手机有重力感应器就能实现。

 

1.    启动界面

程序启动界面带有缓冲效果,其中界面缓冲效果的配置文件在res/anim/目录下。如果计步器已经开始工作,则跳过程序启动界面,直接进入主界面。

[java]  view plain  copy
  1.        if (StepCounterService.FLAG || StepDetector.CURRENT_SETP > 0) {// 程序已经启动,直接跳转到运行界面  
  2.            Intent intent = new Intent(SplashActivity.this,StepCounterActivity.class); //创建一个新的Intent,指定当前应用程序上下文      
  3. //和要启动的StepActivity类  
  4.            startActivity(intent);                                         //传递这个intent给startActivity  
  5.            this.finish();  
  6.        } else {  
  7.            this.requestWindowFeature(Window.FEATURE_NO_TITLE);  
  8.            this.setContentView(R.layout.splash);  
  9.    
  10.            animation = AnimationUtils.loadAnimation(SplashActivity.this,  
  11.                   R.anim.animation_splash);  
  12.            this.findViewById(R.id.iv_index).setAnimation(animation);  
  13.            animation.setAnimationListener(new AnimationListener() {// 动画监听,通过AnimationListener可以监听Animation的运行过程  
  14.                      @Override  
  15.                      public void onAnimationStart(Animation animation){  
  16.                          // TODOAuto-generated method stub  
  17.                      }  
  18.                      @Override  
  19.                      public void onAnimationRepeat(Animationanimation) {  
  20.                          // TODOAuto-generated method stub  
  21.                      }  
  22.                      @Override  
  23.                      public void onAnimationEnd(Animation animation) {// 动画结束时跳转至运行界面  
  24.                          // TODOAuto-generated method stub  
  25.                          Intent intent = new Intent(SplashActivity.this,  
  26.                                 StepCounterActivity.class);  
  27.                          SplashActivity.this.startActivity(intent);  
  28.                          SplashActivity.this.finish();  
  29.                      }  
  30.                   });  
  31.        }  
  32. }  


2.  程序主界面

(1)界面元素的初始化(比如步数,星期,日期,运行时间,行程,卡路里,速度,开始按钮和停止按钮)。

[java]  view plain  copy
  1. //定义文本框控件  
  2.    private TextView tv_show_step;// 步数  
  3.    private TextView tv_week_day;// 星期  
  4.    private TextView tv_date;// 日期  
  5.   
  6.    private TextView tv_timer;// 运行时间  
  7.   
  8.    private TextView tv_distance;// 行程  
  9.    private TextView tv_calories;// 卡路里  
  10.    private TextView tv_velocity;// 速度  
  11.   
  12.    private Button btn_start;// 开始按钮  
  13.    private Button btn_stop;// 停止按钮  
  14.   
  15.    // 十颗星标  
  16.    private ImageView iv_star_1;                 
  17.    private ImageView iv_star_2;  
  18.    private ImageView iv_star_3;  
  19.    private ImageView iv_star_4;  
  20.    private ImageView iv_star_5;  
  21.    private ImageView iv_star_6;  
  22.    private ImageView iv_star_7;  
  23.    private ImageView iv_star_8;  
  24.    private ImageView iv_star_9;  
  25.    private ImageView iv_star_10;  
  26.   
  27.    private long timer = 0;// 运动时间  
  28.    private static long startTimer = 0;// 开始时间  
  29.    private static long tempTime = 0;  
  30.   
  31.    private Double distance = 0.0;// 路程:米  
  32.    private Double calories = 0.0;// 热量:卡路里  
  33.    private Double velocity = 0.0;// 速度:米每秒  
  34.   
  35.    private int step_length = 0;  //步长  
  36.    private int weight = 0;       //体重  
  37.    private int total_step = 0;   //走的总步数  
  38.     
  39.    DatagramSocket socket;  
  40.    InetAddress serverAddress;  
  41.    DatagramPacket  send_package1;  
  42.    private Thread thread;  //定义线程对象  


2)利用消息机制对主界面的UI元素进行更新。定义了一个handler,当检测的步数变化时,更新主界面显示的信息。主要代码如下:

[java]  view plain  copy
  1. Handler handler = new Handler() {// Handler对象用于更新当前步数,定时发送消息,调用方法查询数据用于显示??????????  
  2.                                        //主要接受子线程发送的数据, 并用此数据配合主线程更新UI  
  3.                                    //Handler运行在主线程中(UI线程中), 它与子线程可以通过Message对象来传递数据,  
  4.    //Handler就承担着接受子线程传过来的(子线程用sendMessage()方法传递Message对象,(里面包含数据)  
  5.    //把这些消息放入主线程队列中,配合主线程进行更新UI。  
  6.   
  7.    @Override                  //这个方法是从父类/接口继承过来的,需要重写一次  
  8.    public void handleMessage(Message msg) {  
  9.        // TODO Auto-generated method stub  
  10.        super.handleMessage(msg);        // 此处可以更新UI  
  11.   
  12.        countDistance();     //调用距离方法,看一下走了多远  
  13.   
  14.        if (timer != 0 && distance != 0.0) {  
  15.   
  16.           // 体重、距离  
  17.            // 跑步热量(kcal)=体重(kg)×距离(公里)×1.036  
  18.           calories = weight * distance * 0.001;  
  19.           //速度velocity  
  20.           velocity = distance * 1000 / timer;  
  21.        } else {  
  22.           calories =0.0;  
  23.           velocity =0.0;  
  24.        }  
  25.   
  26.        countStep();          //调用步数方法  
  27.   
  28.        tv_show_step.setText(total_step + "");// 显示当前步数  
  29.   
  30.        tv_distance.setText(formatDouble(distance));// 显示路程  
  31.        tv_calories.setText(formatDouble(calories));// 显示卡路里  
  32.        tv_velocity.setText(formatDouble(velocity));// 显示速度  
  33.   
  34.        tv_timer.setText(getFormatTime(timer));// 显示当前运行时间  
  35.   
  36.        changeStep();// 设置当前步数和星标  
  37.   
  38.    }  

(3)初始化主界面的信息。

[java]  view plain  copy
  1. /** 
  2.  * 初始化界面 
  3.  */  
  4. private void init() {  
  5.   
  6.    step_length =SettingsActivity.sharedPreferences.getInt(  
  7.           SettingsActivity.STEP_LENGTH_VALUE, 70);  
  8.    weight =SettingsActivity.sharedPreferences.getInt(  
  9.           SettingsActivity.WEIGHT_VALUE, 50);  
  10.   
  11.    countDistance();  
  12.    countStep();  
  13.    if ((timer += tempTime) != 0 && distance != 0.0) {  //tempTime记录运动的总时间,timer记录每次运动时间  
  14.   
  15.        // 体重、距离  
  16.        // 跑步热量(kcal)=体重(kg)×距离(公里)×1.036,换算一下  
  17.        calories = weight * distance * 0.001;  
  18.   
  19.        velocity = distance * 1000 / timer;  
  20.    } else {  
  21.        calories = 0.0;  
  22.        velocity = 0.0;  
  23.    }  
  24.   
  25.    tv_timer.setText(getFormatTime(timer + tempTime));  
  26.   
  27.    tv_distance.setText(formatDouble(distance));  
  28.    tv_calories.setText(formatDouble(calories));  
  29.    tv_velocity.setText(formatDouble(velocity));  
  30.   
  31.    tv_show_step.setText(total_step + "");  
  32.   
  33.    btn_start.setEnabled(!StepCounterService.FLAG);  
  34.    btn_stop.setEnabled(StepCounterService.FLAG);  
  35.   
  36.    if(StepCounterService.FLAG) {  
  37.        btn_stop.setText(getString(R.string.pause));  
  38.    } else if (StepDetector.CURRENT_SETP > 0) {  
  39.        btn_stop.setEnabled(true);  
  40.        btn_stop.setText(getString(R.string.cancel));  
  41.    }  
  42.   
  43.    setDate();  
  44. }  

(4)计算距离和步长。

[java]  view plain  copy
  1. /** 
  2.  * 计算行走的距离                                                                        
  3.  */  
  4. private void countDistance() {  
  5.    if (StepDetector.CURRENT_SETP % 2 == 0) {  
  6.        distance = (StepDetector.CURRENT_SETP / 2) * 3 * step_length * 0.01;  
  7.    } else {  
  8.        distance = ((StepDetector.CURRENT_SETP / 2) * 3 + 1) * step_length * 0.01;  
  9.    }  
  10. }  
  11.   
  12. /** 
  13.  * 实际的步数 
  14.  */  
  15. private void countStep() {  
  16.    if (StepDetector.CURRENT_SETP % 2 == 0) {  
  17.        total_step = StepDetector.CURRENT_SETP;  
  18.    } else {  
  19.        total_step = StepDetector.CURRENT_SETP +1;  
  20.    }  
  21.     
  22.    total_step = StepDetector.CURRENT_SETP;  
  23. }  


3.    后台检测传感器信息的服务(service).

[java]  view plain  copy
  1. //service负责后台的需要长期运行的任务  
  2.  // 计步器服务  
  3.  // 运行在后台的服务程序,完成了界面部分的开发后  
  4.  // 就可以开发后台的服务类StepService  
  5.  // 注册或注销传感器监听器,在手机屏幕状态栏显示通知,与StepActivity进行通信,走过的步数记到哪里了???  
  6. public class StepCounterService extends Service {  
  7.    
  8.     public static Boolean FLAG = false;// 服务运行标志  
  9.    
  10.     private SensorManagermSensorManager;// 传感器服务  
  11.     private StepDetector detector;// 传感器监听对象  
  12.    
  13.     private PowerManager mPowerManager;// 电源管理服务  
  14.     private WakeLock mWakeLock;// 屏幕灯  
  15.    
  16.     @Override  
  17.     public IBinder onBind(Intent intent) {  
  18.        // TODOAuto-generated method stub  
  19.        return null;  
  20.     }  
  21.    
  22.     @Override  
  23.     public void onCreate() {  
  24.        // TODOAuto-generated method stub  
  25.        super.onCreate();  
  26.    
  27.        FLAG = true;// 标记为服务正在运行  
  28.    
  29.        // 创建监听器类,实例化监听对象  
  30.        detector = new StepDetector(this);  
  31.    
  32.        // 获取传感器的服务,初始化传感器  
  33.        mSensorManager = (SensorManager) this.getSystemService(SENSOR_SERVICE);  
  34.        // 注册传感器,注册监听器  
  35.        mSensorManager.registerListener(detector,  
  36.               mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),  
  37.               SensorManager.SENSOR_DELAY_FASTEST);  
  38.    
  39.        // 电源管理服务  
  40.        mPowerManager = (PowerManager) this  
  41.               .getSystemService(Context.POWER_SERVICE);  
  42.        mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK  
  43.               |PowerManager.ACQUIRE_CAUSES_WAKEUP, "S");  
  44.        mWakeLock.acquire();  
  45.     }  
  46.    
  47.     @Override  
  48.     public void onDestroy() {  
  49.        // TODOAuto-generated method stub  
  50.        super.onDestroy();  
  51.        FLAG = false;// 服务停止  
  52.        if (detector != null) {  
  53.            mSensorManager.unregisterListener(detector);  
  54.        }  
  55.    
  56.        if (mWakeLock != null) {  
  57.            mWakeLock.release();  
  58.        }  
  59.     }  

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
描述 android计步器的实现,自定义的一个弧形进度条,记步通过手机的传感器来实现,也就是说不支持传感器的机子(应该很老的了吧)就没有效果。看看效果图: 这里写图片描述这里写图片描述 自定义View public class StepView extends View { /** * 圆弧的宽度 */ private float borderWidth = dipToPx(10); /** * 画步数的数值的字体大小 */ private float numberTextSize = 0; /** * 步数 */ private String stepNumber = "0"; /** * 开始绘制圆弧的角度 */ private float startAngle = 125; /** * 终点对应的角度和起始点对应的角度的夹角 */ private float angleLength = 290; /** * 所要绘制的当前步数的蓝色圆弧终点到起点的夹角 */ private float currentAngleLength = 0; /** * 动画时长 */ private int animationLength = 3000; /** * 当前运动类型 */ private String type = "Riding"; /** * 当前活跃等级 */ private String level = "等级:轻度活跃"; /** * 步数上方文字 */ private String today = "今日步数"; /** * 单位km是否显示 */ private String unit = "Km"; public StepView(Context context) { super(context); } public StepView(Context context, AttributeSet attrs) { super(context, attrs); } public StepView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /**中心点的x坐标*/ float centerX = (getWidth()) / 2; /**指定圆弧的外轮廓矩形区域*/ RectF rectF = new RectF(0 + borderWidth, borderWidth, 2 * centerX - borderWidth, 2 * centerX - borderWidth); /**【第一步】绘制整体的灰色圆弧*/ drawArcYellow(canvas, rectF); /**【第二步】绘制当前进度的蓝色圆弧*/ drawArcRed(canvas, rectF); /**【第三步】绘制当前进度的白色数字*/ drawTextNumber(canvas, centerX); /**【第四步】绘制"本次步数"的灰色文字*/ drawTextStepString(canvas, centerX); /**【第五步】绘制当前记步类型*/ drawTextType(canvas, centerX); /**【第六步】绘制当前等级类型*/ drawTextLevel(canvas, centerX); /**【第七步】绘制骑行距离单位*/ drawTextUnit(canvas, centerX); } /** * 1.绘制总步数的灰色圆弧 * * @param canvas 画笔 * @param rectF 参考的矩形 */ private void drawArcYellow(Canvas canvas, RectF rectF) { Paint paint = new Paint(); /** 默认画笔颜色,灰色 */ paint.setColor(getResources().getColor(R.color.near_black)); /** 结合处为圆弧*/ paint.setStrokeJoin(Paint.Join.MITER); /** 设置画笔的样式 Paint.Cap.Round ,Cap.SQUARE等分别为圆形、方形*/ paint.setStrokeCap(Paint.Cap.BUTT); /** 设置画笔的填充样式 Paint.Style.FILL :填充内部;Paint.Style.FILL_AND_STROKE :填充内部和描边; Paint.Style.STROKE :仅描边*/ paint.setStyle(Paint.Style.STROKE); float[] floats = {4,16,4,16}; paint.setPathEffect(new DashPathEffect(floats, 0)); /**抗锯齿功能*/ paint.setAntiAlias(true); /**设置画笔宽度*/ paint.setStrokeWidth(borderWidth); /**绘制圆弧的方法 * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧, 参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧, 参数二是起始角(度)在电弧的开始,圆弧起始角度,单位为度。 参数三圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度。 参数四是如果这是true(真)的话,在绘制圆弧时将圆心包括在内,通常用来绘制扇形;如果它是false(假)这将是一个弧线, 参数五是Paint对象; */ canvas.drawArc(rectF, startAngle, angleLength, false, paint); } /** * 2.绘制当前步数的蓝色圆弧 */ private void drawArcRed(Canvas canvas, RectF rectF) { Paint paintCurrent = new Paint(); paintCurrent.setStrokeJoin(Paint.Join.MITER); paintCurrent.setStrokeCap(Paint.Cap.BUTT);//圆角弧度 paintCurrent.setStyle(Paint.Style.STROKE);//设置填充样式 paintCurrent.setAntiAlias(true);//抗锯齿功能 paintCurrent.setStrokeWidth(borderWidth);//设置画笔宽度 paintCurrent.setColor(getResources().getColor(R.color.colorPrimary));//设置画笔颜色 float[] floats = {4,16,4,16}; paintCurrent.setPathEffect(new DashPathEffect(floats, 0)); canvas.drawArc(rectF, startAngle, currentAngleLength, false, paintCurrent); } /** * 3.圆环中心的步数 */ private void drawTextNumber(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗锯齿功能 vTextPaint.setTextSize(numberTextSize); Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL); vTextPaint.setTypeface(font);//字体风格 vTextPaint.setColor(getResources().getColor(R.color.white)); Rect bounds_Number = new Rect(); vTextPaint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); canvas.drawText(stepNumber, centerX, getHeight() / 2 + bounds_Number.height() / 2, vTextPaint); } /** * 4.圆环中心[本次步数]的文字 */ private void drawTextStepString(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextSize(dipToPx(16)); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗锯齿功能 vTextPaint.setColor(getResources().getColor(R.color.gray)); Rect bounds = new Rect(); vTextPaint.getTextBounds(today, 0, today.length(), bounds); canvas.drawText(today, centerX, getHeight() / 2 + bounds.height() - 2 * getFontHeight(numberTextSize), vTextPaint); } /** * 5.圆环中下[Walking]等文字 */ private void drawTextType(Canvas canvas, float centerX) { Paint mTypePaint = new Paint(); mTypePaint.setTextSize(dipToPx(22)); mTypePaint.setTextAlign(Paint.Align.CENTER); mTypePaint.setAntiAlias(true); mTypePaint.setColor(getResources().getColor(R.color.text_blue)); Rect bounds = new Rect(); mTypePaint.getTextBounds(type, 0, type.length(), bounds); canvas.drawText(type, centerX, getHeight() / 2 + 2 * bounds.height() + getFontHeight(numberTextSize), mTypePaint); } /** * 6.绘制圆环下方等级 */ private void drawTextLevel(Canvas canvas, float centerX) { Paint mLevelPaint = new Paint(); mLevelPaint.setTextSize(dipToPx(12)); mLevelPaint.setTextAlign(Paint.Align.CENTER); mLevelPaint.setAntiAlias(true); mLevelPaint.setColor(getResources().getColor(R.color.input_hint_gray)); Rect bounds = new Rect(); mLevelPaint.getTextBounds(level, 0, level.length(), bounds); canvas.drawText(level, centerX, getHeight() / 2 + 2 * bounds.height() + 2 * getFontHeight(numberTextSize), mLevelPaint); } /** * 7.绘制骑行单位km */ private void drawTextUnit(Canvas canvas, float centerX) { Paint mUnitPaint = new Paint(); mUnitPaint.setTextSize(dipToPx(16)); mUnitPaint.setTextAlign(Paint.Align.CENTER); mUnitPaint.setAntiAlias(true); mUnitPaint.setColor(getResources().getColor(R.color.input_hint_gray)); Rect bounds = new Rect(); mUnitPaint.getTextBounds(unit, 0, unit.length(), bounds); canvas.drawText(unit, centerX+ stepNumber.length()*80, getHeight() / 2 + bounds.height() * 3 / 2, mUnitPaint); } /** * 获取当前步数的数字的高度 * * @param fontSize 字体大小 * @return 字体高度 */ public int getFontHeight(float fontSize) { Paint paint = new Paint(); paint.setTextSize(fontSize); Rect bounds_Number = new Rect(); paint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); return bounds_Number.height(); } /** * dip 转换成px * * @param dip * @return */ private int dipToPx(float dip) { float density = getContext().getResources().getDisplayMetrics().density; return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1)); } /** * 所走的步数进度 * @param totalStepNum 设置的步数 * @param currentCounts 所走步数 */ public void setCurrentCount(int totalStepNum, int currentCounts) { /**如果当前走的步数超过总步数则圆弧还是270度,不能成为园*/ if (currentCounts > totalStepNum) { currentCounts = totalStepNum; } /**上次所走步数占用总共步数的百分比*/ float scalePrevious = (float) Integer.valueOf(stepNumber) / totalStepNum; /**换算成弧度最后要到达的角度的长度-->弧长*/ float previousAngleLength = scalePrevious * angleLength; /**所走步数占用总共步数的百分比*/ float scale = (float) currentCounts / totalStepNum; /**换算成弧度最后要到达的角度的长度-->弧长*/ float currentAngleLength = scale * angleLength; /**开始执行动画*/ setAnimation(previousAngleLength, currentAngleLength, animationLength); stepNumber = String.valueOf(currentCounts); setTextSize(currentCounts); } /** * 设置各个参数 */ public void setParams(String today, String unit, String type, String level) { this.today = today; this.unit = unit; this.type = type; this.level = level; } /** * 为进度设置动画 * ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的, * 而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。 * 它的内部使用一种时间循环的机制来计算值与值之间的动画过渡, * 我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长, * 那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。 * * @param start 初始值 * @param current 结束值 * @param length 动画时长 */ private void setAnimation(float start, float current, int length) { ValueAnimator progressAnimator = ValueAnimator.ofFloat(start, current); progressAnimator.setDuration(length); progressAnimator.setTarget(currentAngleLength); progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { /**每次在初始值和结束值之间产生的一个平滑过渡的值,逐步去更新进度*/ currentAngleLength = (float) animation.getAnimatedValue(); invalidate(); } }); progressAnimator.start(); } /** * 设置文本大小,防止步数特别大之后放不下,将字体大小动态设置 * * @param num */ public void setTextSize(int num) { String s = String.valueOf(num); int length = s.length(); if (length 4 && length 6 && length 8) { numberTextSize = dipToPx(25); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 整个View的代码逻辑还是比较清晰的,自定义的view会首先调用onDraw()方法,获取了整个布局的中心和轮廓,然后开始七步分别绘制控件,这就是大家可以发挥的部分了,想怎么设计怎么设计。 第一步着重看一下,这个虚线的实现: float[] floats = {4,16,4,16}; paint.setPathEffect(new DashPathEffect(floats, 0)); 1 2 floats的四个参数意思是{画宽度,间隔宽度,画宽度,间隔宽度},这里的floats是数组也就是说你可以把每一个宽度和间隔都写出来,当然如果一样的话你可以只列出两个,系统会自动识别来延续后面的宽度和间隔。 因为注释很清楚,其他的这里就不详述了,想改啥基本都能看明白。 记步服务 public class StepService extends Service implements SensorEventListener { private String TAG = "StepService"; /** * 默认为10秒进行一次存储 */ private static int duration = 10 * 1000; /** * 传感器管理对象 */ private SensorManager sensorManager; /** * 保存记步计时器 */ private TimeCount time; /** * 当前所走的步数 */ private int CURRENT_STEP; /** * 计步传感器类型 Sensor.TYPE_STEP_COUNTER或者Sensor.TYPE_STEP_DETECTOR */ private static int stepSensorType = -1; /** * 每次第一次启动记步服务时是否从系统中获取了已有的步数记录 */ private boolean hasRecord = false; /** * 系统中获取到的已有的步数 */ private int hasStepCount = 0; /** * 上一次的步数 */ private int previousStepCount = 0; /** * IBinder对象,向Activity传递数据的桥梁 */ private StepBinder stepBinder = new StepBinder(); @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate()"); new Thread(new Runnable() { public void run() { startStepDetector(); } }).start(); startTimeCount(); } /** * 开始保存记步数据 */ private void startTimeCount() { if (time == null) { time = new TimeCount(duration, 1000); } time.start(); } /** * UI监听器对象 */ private UpdateUiCallBack mCallback; /** * 注册UI更新监听 * * @param paramICallback */ public void registerCallback(UpdateUiCallBack paramICallback) { this.mCallback = paramICallback; } @Override public IBinder onBind(Intent intent) { return stepBinder; } /** * 向Activity传递数据的纽带 */ public class StepBinder extends Binder { /** * 获取当前service对象 * * @return StepService */ public StepService getService() { return StepService.this; } } /** * 获取当前步数 * * @return */ public int getStepCount() { return CURRENT_STEP; } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } /** * 获取传感器实例 */ private void startStepDetector() { if (sensorManager != null) { sensorManager = null; } // 获取传感器管理器的实例 sensorManager = (SensorManager) this .getSystemService(SENSOR_SERVICE); //android4.4以后可以使用计步传感器 addCountStepListener(); } /** * 添加传感器监听 * 1. TYPE_STEP_COUNTER API的解释说返回从开机被激活后统计的步数,当重启手机后该数据归零, * 该传感器是一个硬件传感器所以它是低功耗的。 * 为了能持续的计步,请不要反注册事件,就算手机处于休眠状态它依然会计步。 * 当激活的时候依然会上报步数。该sensor适合在长时间的计步需求。 * * 2.TYPE_STEP_DETECTOR翻译过来就是走路检测, * API文档也确实是这样说的,该sensor只用来监监测走步,每次返回数字1.0。 * 如果需要长事件的计步请使用TYPE_STEP_COUNTER。 */ private void addCountStepListener() { Sensor countSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER); Sensor detectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR); if (countSensor != null) { stepSensorType = Sensor.TYPE_STEP_COUNTER; Log.v(TAG, "Sensor.TYPE_STEP_COUNTER"); sensorManager.registerListener(StepService.this, countSensor, SensorManager.SENSOR_DELAY_NORMAL); } else if (detectorSensor != null) { stepSensorType = Sensor.TYPE_STEP_DETECTOR; Log.v(TAG, "Sensor.TYPE_STEP_DETECTOR"); sensorManager.registerListener(StepService.this, detectorSensor, SensorManager.SENSOR_DELAY_NORMAL); } } /** * 传感器监听回调 * @param event */ @Override public void onSensorChanged(SensorEvent event) { if (stepSensorType == Sensor.TYPE_STEP_COUNTER) { //获取当前传感器返回的临时步数 int tempStep = (int) event.values[0]; //首次如果没有获取手机系统中已有的步数则获取一次系统中APP还未开始记步的步数 if (!hasRecord) { hasRecord = true; hasStepCount = tempStep; } else { //获取APP打开到现在的总步数=本次系统回调的总步数-APP打开之前已有的步数 int thisStepCount = tempStep - hasStepCount; //本次有效步数=(APP打开后所记录的总步数-上一次APP打开后所记录的总步数) int thisStep = thisStepCount - previousStepCount; //总步数=现有的步数+本次有效步数 CURRENT_STEP += (thisStep); //记录最后一次APP打开到现在的总步数 previousStepCount = thisStepCount; } } else if (stepSensorType == Sensor.TYPE_STEP_DETECTOR) { if (event.values[0] == 1.0) { CURRENT_STEP++; } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } /** * 保存记步数据 */ class TimeCount extends CountDownTimer { public TimeCount(long millisInFuture, long countDownInterval) { super(millisInFuture, countDownInterval); } @Override public void onFinish() { // 如果计时器正常结束,则开始计步 time.cancel(); mCallback.updateUi(CURRENT_STEP); startTimeCount(); } @Override public void onTick(long millisUntilFinished) { } } @Override public void onDestroy() { super.onDestroy(); //取消前台进程 stopForeground(true); } @Override public boolean onUnbind(Intent intent) { return super.onUnbind(intent); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 因为记步是在后台进行的,开启之后我们可以干其他事情,所以采用服务的方式,而我们需要获取记步的数据,所以要通过绑定的方式来注册服务,这样才能获得通信用的connection。逻辑思路是,通过计步器获取步数,然后设置监听器来监听步数的改变,如果步数改变更新步数的变量,然后开启一个计时器来定时去记录,设置回调参数来返回步数。回调接口只有一个方法: public interface UpdateUiCallBack { /** * 更新UI步数 * * @param stepCount 步数 */ void updateUi(int stepCount); } 1 2 3 4 5 6 7 8 当然别忘了在AndroidManifest.xml中注册Service: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 还有记步需要用的权限: 1 2 3 调用MainActivity public class MainActivity extends AppCompatActivity { private StepView stepView; private Button button; private boolean isBind = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); } private void initView() { stepView = (StepView) findViewById(R.id.step_walk_arv); button = (Button) findViewById(R.id.begin_btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { begin(); } }); } private void initData() { stepView.setParams("今日步数", "", "Walking", "等级:轻度活跃"); stepView.setCurrentCount(1000,0); } private void begin() { if (isBind == false){ //开启记步 Intent intent = new Intent(this, StepService.class); isBind = bindService(intent, conn, Context.BIND_AUTO_CREATE); startService(intent); button.setText("正在记步"); } } /** * 用于查询应用服务(application Service)的状态的一种interface, * 更详细的信息可以参考Service 和 context.bindService()中的描述, * 和许多来自系统的回调方式一样,ServiceConnection的方法都是进程的主线程中调用的。 */ ServiceConnection conn = new ServiceConnection() { /** * 在建立起于Service的连接时会调用该方法,目前Android是通过IBind机制实现与服务的连接。 * @param name 实际所连接到的Service组件名称 * @param service 服务的通信信道的IBind,可以通过Service访问对应服务 */ @Override public void onServiceConnected(ComponentName name, IBinder service) { StepService stepService = ((StepService.StepBinder) service).getService(); //设置初始化数据 stepView.setCurrentCount(1000, stepService.getStepCount()); //设置步数监听回调 stepService.registerCallback(new UpdateUiCallBack() { @Override public void updateUi(int stepCount) { stepView.setCurrentCount(1000, stepCount); } }); } /** * 当与Service之间的连接丢失的时候会调用该方法, * 这种情况经常发生在Service所在的进程崩溃或者被Kill的时候调用, * 此方法不会移除与Service的连接,当服务重新启动的时候仍然会调用 onServiceConnected()。 * @param name 丢失连接的组件名称 */ @Override public void onServiceDisconnected(ComponentName name) { } }; //服务解绑 @Override public void onDestroy() { super.onDestroy(); if (isBind) { unbindService(conn); isBind = false; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 调用也很简单,首先在布局文件中引用StepView,然后初始化,设置圆弧里面的参数,设置总步数和当前步数(开始当然为0): stepView.setParams("今日步数", "", "Walking", "等级:轻度活跃"); stepView.setCurrentCount(1000,0); 1 2 其中还设置了一个boolean值isBind区分是否已经启动服务并绑定到了activity上,以便在onDestroy()方法是解除绑定。 整个计步器的实现还是相对简单的,之前还想过要在圆弧圈外面画一个手指来指向当前的进度,不过暂时还没想通怎么实现,有兴趣的小伙伴研究之后告诉一声。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值