android 计步器代码(prdometer)

应用主要是实现记步功能,可以比较准确的显示出步数。

1、在启动应用的时候先初始化ui和启动线程,在线程里更新步数。然后启动Service。

package com.example.pedometer;

import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Handler.Callback;
import android.os.Message;
import android.os.Messenger;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends Activity implements Callback {

	private static final String TAG="nsc";
	public static final int MSG_FROM_CLIENT = 0;
	public static final int MSG_FROM_SERVER = 1;//返回服务
	public static final int REQUEST_SERVER = 2;//取消服务
	private long TIME_INTERVAL = 500;
	
	private TextView text;
	private Handler delayHandler;
	private Messenger messenger;
	private Messenger mGetReplyMessenger = new Messenger(new Handler(this));
	
	ServiceConnection conn = new ServiceConnection() {
		
		@Override
		public void onServiceDisconnected(ComponentName arg0) {
			// TODO Auto-generated method stub
			
		}	
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			// TODO Auto-generated method stub
			try{
				messenger = new Messenger(service);
				Message msg = Message.obtain(null,MSG_FROM_CLIENT);
				msg.replyTo = mGetReplyMessenger;//replyTo消息管理器
				Log.d(TAG,"msg ="+ msg);
				messenger.send(msg);//发送消息出去
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	};
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		initUI();//初始化ui和启动线程
		setupService();//启动服务
	}

	/**
	 * 启动服务
	 */
	private void setupService() {
		// TODO Auto-generated method stub
		Intent intent = new Intent(this,StepService.class);
		//使用这个ServiceConnection,客户端可以绑定到一个service,通过把它传给bindService()
		//第一个bindService()的参数是一个明确指定了要绑定的service的Intent.
		//第二个参数是ServiceConnection对象.
		//第三个参数是一个标志,它表明绑定中的操作.它一般应是BIND_AUTO_CREATE,这样就会在service不存在时创建一个.其它可选的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,不想指定时设为0即可.。
		bindService(intent, conn, BIND_AUTO_CREATE);//BIND_AUTO_CREATE =1
		startService(intent);
	}

	private void initUI() {
		// TODO Auto-generated method stub
		text = (TextView)findViewById(R.id.text);
		delayHandler = new Handler(this);
	}

	@Override
	public boolean handleMessage(Message msg) {
		// TODO Auto-generated method stub
		switch(msg.what){
			case MSG_FROM_SERVER:
				Log.d(TAG,"text="+ msg.getData().getInt("step"));
				text.setText(msg.getData().getInt("step")+"");//显示记步数
				//延时500ms发送值为REQUEST_SERVER 消息
				delayHandler.sendEmptyMessageDelayed(REQUEST_SERVER, TIME_INTERVAL);
				break;
			case REQUEST_SERVER:
				try{
					Message message = Message.obtain(null,MSG_FROM_CLIENT);//发送消息
					message.replyTo = mGetReplyMessenger;
					Log.d(TAG,"message="+ message);
					messenger.send(message);
				}catch(Exception e){
					e.printStackTrace();
				}
				break;
		}
		return false;
	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		unbindService(conn);//解除服务的绑定
	}
	

}
代码布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="@drawable/aa"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#000000"
        android:layout_centerInParent="true"
        android:textSize="100sp"/>

</RelativeLayout>
2、StepService.java 代码:

package com.example.pedometer;


import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;

public class StepService extends Service implements SensorEventListener {

	private final static String TAG="SetpService";

	private SensorManager sensorManager;
	private StepDcretor stepDetector;
	private BroadcastReceiver mBroadcastReceiver;
	private final static int MSG=0;
	private final static int MSG_SERVER=1;
	
	//计步器传感器类型 0-counter 1-detector
	private static int stepSensor = -1;
	
	private Messenger messenger = new Messenger(new MessenerHandler());
	private static class MessenerHandler extends Handler{

		@Override
		public void handleMessage(Message msg) {
			// TODO Auto-generated method stub
			switch(msg.what){
			case MSG:
				try{
					Messenger messenger = msg.replyTo;
                    Message replyMsg = Message.obtain(null, MSG_SERVER);
                    Bundle bundle = new Bundle();
                    bundle.putInt("step", StepDcretor.CURRENT_SETP);
                    replyMsg.setData(bundle);
                    Log.d(TAG, replyMsg+"");
                    messenger.send(replyMsg);
				}catch(Exception e){
					e.printStackTrace();
				}
				break;
				default:
					super.handleMessage(msg);	
			}		
		}
		
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return messenger.getBinder();
	}
	
	@Override
	@Deprecated
	public void onStart(Intent intent, int startId) {
		// TODO Auto-generated method stub
		super.onStart(intent, startId);
	}


	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		return START_STICKY;
	}
	
	private void startStepDetector(){
		if(sensorManager != null && stepDetector !=null){
			sensorManager.unregisterListener(stepDetector);
			sensorManager = null;
			stepDetector = null;
		}
		//getLock(this);
		//获取传感器管理器的实例
		sensorManager = (SensorManager)this.getSystemService(SENSOR_SERVICE);
		//android 4.4以后可以使用计步传感器  在根据API不同版本分别执行addCountStepListener和addBasePedoListener两个方法
		int VERSION_CODES = android.os.Build.VERSION.SDK_INT;
		Log.d(TAG, VERSION_CODES+"");
		if(VERSION_CODES>19){//sdk版本
			addCountStepListener();
		}else{
			addBasePedoListener();
		}
	}


	private void addBasePedoListener() {
		// TODO Auto-generated method stub
		 stepDetector = new StepDcretor(this);
	        // 获得传感器的类型,这里获得的类型是加速度传感器
	        // 此方法用来注册,只有注册过才会生效,参数:SensorEventListener的实例,Sensor的实例,更新速率
	        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
	        // sensorManager.unregisterListener(stepDetector);
	        sensorManager.registerListener(stepDetector, sensor,SensorManager.SENSOR_DELAY_UI);
	        stepDetector.setOnSensorChangeListener(new StepDcretor.OnSensorChangeListener() {

                @Override
                public void onChange() {
                   // updateNotification("今日步数:" + StepDcretor.CURRENT_SETP + " 步");
                }
            });
	}

	private void addCountStepListener() {
		// TODO Auto-generated method stub
		//android 两种计步方式 detector启动后,确认了,才启动counter.
		Sensor detectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);//计步传感器
		Sensor countSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);//步行检测传感器
		 if (countSensor != null) {
	            stepSensor = 0;
	            Log.v("base", "countSensor");
	            //第一个参数是Listener,第二个参数是所得传感器类型,第三个参数值获取传感器信息的频率
	            sensorManager.registerListener(StepService.this, countSensor, SensorManager.SENSOR_DELAY_UI);
	        } else if (detectorSensor != null) {
	            stepSensor = 1;
	            Log.v("base", "detector");
	            sensorManager.registerListener(StepService.this, detectorSensor, SensorManager.SENSOR_DELAY_UI);
	        } else {
	            Log.v("xf", "Count sensor not available!");
	            addBasePedoListener();
	        }
	}
	
	@Override
	public void onSensorChanged(SensorEvent event) {
		// TODO Auto-generated method stub
		if(stepSensor ==0){
			StepDcretor.CURRENT_SETP = (int)event.values[0];
		}else if(stepSensor ==1){
			StepDcretor.CURRENT_SETP++;
		}
		//updateNotification("今日步数:" + StepDcretor.CURRENT_SETP + " 步");
	}
	

	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		//这里开启了一个线程,因为后台服务也是在主线程中进行,这样可以安全点,防止主线程阻塞
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				startStepDetector();
			}
		}).start();
		
	}
	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		unregisterReceiver(mBroadcastReceiver);//注销广播
		Intent intent = new Intent(this,StepService.class);
		startService(intent);//重新启动StepService 服务
		super.onDestroy();
	}

	@Override
	public boolean onUnbind(Intent intent) {
		// TODO Auto-generated method stub
		return super.onUnbind(intent);
	}

	@Override
	public void onAccuracyChanged(Sensor arg0, int arg1) {
		// TODO Auto-generated method stub
		
	}
	
}
3、StepDcretor.java是记步算法的实现,详细可看代码。

package com.example.pedometer;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.os.CountDownTimer;
import android.util.Log;

import java.util.Timer;
import java.util.TimerTask;

public class StepDcretor implements SensorEventListener {
    //存放三轴数据
    float[] oriValues = new float[3];
    final int valueNum = 4;
    //用于存放计算阈值的波峰波谷差值
    float[] tempValue = new float[valueNum];
    int tempCount = 0;
    //是否上升的标志位
    boolean isDirectionUp = false;
    //持续上升次数
    int continueUpCount = 0;
    //上一点的持续上升的次数,为了记录波峰的上升次数
    int continueUpFormerCount = 0;
    //上一点的状态,上升还是下降
    boolean lastStatus = false;
    //波峰值
    float peakOfWave = 0;
    //波谷值
    float valleyOfWave = 0;
    //此次波峰的时间
    long timeOfThisPeak = 0;
    //上次波峰的时间
    long timeOfLastPeak = 0;
    //当前的时间
    long timeOfNow = 0;
    //当前传感器的值
    float gravityNew = 0;
    //上次传感器的值
    float gravityOld = 0;
    //动态阈值需要动态的数据,这个值用于这些动态数据的阈值
    final float initialValue = (float) 1.7;
    //初始阈值
    float ThreadValue = (float) 2.0;

    private final String TAG = "StepDcretor";
    // alpha 由 t / (t + dT)计算得来,其中 t 是低通滤波器的时间常数,dT 是事件报送频率
//    private final float alpha = 0.8f;
//    private long perCalTime = 0;
//
//    //最新修改的精度值
//    private final float minValue = 9.8f;
//    private final float maxValue = 9.9f;
//    //9.5f
//    private final float verminValue = 8.5f;
//    //10.0f
//    private final float vermaxValue = 11.5f;
//    private final float minTime = 150;
//    private final float maxTime = 2000;

    /**
     * 0-准备计时   1-计时中  2-准备为正常计步计时  3-正常计步中
     */
    private int CountTimeState = 0;
    public static int CURRENT_SETP = 0;
    public static int TEMP_STEP = 0;
    private int lastStep = -1;
    // 加速计的三个维度数值
    public static float[] gravity = new float[3];
    public static float[] linear_acceleration = new float[3];
    //用三个维度算出的平均值
    public static float average = 0;

    private Timer timer;
    // 倒计时4秒,4秒内不会显示计步,用于屏蔽细微波动
    private long duration = 4000;
    private TimeCount time;

    OnSensorChangeListener onSensorChangeListener;

    public interface OnSensorChangeListener {
        void onChange();
    }

    public StepDcretor(Context context) {
        super();
    }

    public void onAccuracyChanged(Sensor arg0, int arg1) {

    }

    public OnSensorChangeListener getOnSensorChangeListener() {
        return onSensorChangeListener;
    }

    public void setOnSensorChangeListener(
            OnSensorChangeListener onSensorChangeListener) {
        this.onSensorChangeListener = onSensorChangeListener;
    }

    public void onSensorChanged(SensorEvent event) {
        Sensor sensor = event.sensor;
        synchronized (this) {
            if (sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
                calc_step(event);
            }
        }
    }
    /**
     * 获取传感器xyz值 然后传入  DetectorNewStep
     * @param event
     */
    synchronized private void calc_step(SensorEvent event) {
        average = (float) Math.sqrt(Math.pow(event.values[0], 2)
                + Math.pow(event.values[1], 2) + Math.pow(event.values[2], 2));
        DetectorNewStep(average);
    }

    /*
     * 检测步子,并开始计步
	 * 1.传入sersor中的数据
	 * 2.如果检测到了波峰,并且符合时间差以及阈值的条件,则判定为1步
	 * 3.符合时间差条件,波峰波谷差值大于initialValue,则将该差值纳入阈值的计算中
	 * */
    public void DetectorNewStep(float values) {
        if (gravityOld == 0) {
            gravityOld = values;
        } else {
            if (DetectorPeak(values, gravityOld)) {
                timeOfLastPeak = timeOfThisPeak;
                timeOfNow = System.currentTimeMillis();
                if (timeOfNow - timeOfLastPeak >= 200
                        && (peakOfWave - valleyOfWave >= ThreadValue) && timeOfNow - timeOfLastPeak <= 2000) {
                    timeOfThisPeak = timeOfNow;
                    //更新界面的处理,不涉及到算法
                    preStep();
                }
                if (timeOfNow - timeOfLastPeak >= 200
                        && (peakOfWave - valleyOfWave >= initialValue)) {
                    timeOfThisPeak = timeOfNow;
                    ThreadValue = Peak_Valley_Thread(peakOfWave - valleyOfWave);
                }
            }
        }
        gravityOld = values;
    }

    /**
     *
     */
    private void preStep() {
        if (CountTimeState == 0) {
            // 开启计时器
            time = new TimeCount(duration, 700);
            time.start();
            CountTimeState = 1;
            Log.v(TAG, "开启计时器");
        } else if (CountTimeState == 1) {
            TEMP_STEP++;
            Log.v(TAG, "计步中 TEMP_STEP:" + TEMP_STEP);
        } else if (CountTimeState == 3) {
            CURRENT_SETP++;
            if (onSensorChangeListener != null) {
                onSensorChangeListener.onChange();
            }
        }
    }


    /*
     * 检测波峰
     * 以下四个条件判断为波峰:
     * 1.目前点为下降的趋势:isDirectionUp为false
     * 2.之前的点为上升的趋势:lastStatus为true
     * 3.到波峰为止,持续上升大于等于2次
     * 4.波峰值大于1.2g,小于2g
     * 记录波谷值
     * 1.观察波形图,可以发现在出现步子的地方,波谷的下一个就是波峰,有比较明显的特征以及差值
     * 2.所以要记录每次的波谷值,为了和下次的波峰做对比
     * */
    public boolean DetectorPeak(float newValue, float oldValue) {
        lastStatus = isDirectionUp;
        if (newValue >= oldValue) {
            isDirectionUp = true;
            continueUpCount++;
        } else {
            continueUpFormerCount = continueUpCount;//记住上升次数,如果下降则清零
            continueUpCount = 0;
            isDirectionUp = false;
        }

        if (!isDirectionUp && lastStatus
                && (continueUpFormerCount >= 2 && (oldValue >= 11.76 && oldValue < 19.6))) {
            peakOfWave = oldValue;//算出波峰值
            return true;
        } else if (!lastStatus && isDirectionUp) {
            valleyOfWave = oldValue;
            return false;
        } else {
            return false;
        }
    }

    /*
     * 阈值的计算
     * 1.通过波峰波谷的差值计算阈值
     * 2.记录4个值,存入tempValue[]数组中
     * 3.在将数组传入函数averageValue中计算阈值
     * */
    public float Peak_Valley_Thread(float value) {
        float tempThread = ThreadValue;
        if (tempCount < valueNum) {
            tempValue[tempCount] = value;
            tempCount++;
        } else {
            tempThread = averageValue(tempValue, valueNum);
            for (int i = 1; i < valueNum; i++) {
                tempValue[i - 1] = tempValue[i];
            }
            tempValue[valueNum - 1] = value;
        }
        return tempThread;

    }

    /*
     * 梯度化阈值
     * 1.计算数组的均值
     * 2.通过均值将阈值梯度化在一个范围里
     * */
    public float averageValue(float value[], int n) {
        float ave = 0;
        for (int i = 0; i < n; i++) {
            ave += value[i];
        }
        ave = ave / valueNum;
        if (ave >= 8)
            ave = (float) 4.3;
        else if (ave >= 7 && ave < 8)
            ave = (float) 3.3;
        else if (ave >= 4 && ave < 7)
            ave = (float) 2.3;
        else if (ave >= 3 && ave < 4)
            ave = (float) 2.0;
        else {
            ave = (float) 1.3;
        }
        return ave;
    }

    class TimeCount extends CountDownTimer {
        public TimeCount(long millisInFuture, long countDownInterval) {
            super(millisInFuture, countDownInterval);
        }

        @Override
        public void onFinish() {
            // 如果计时器正常结束,则开始计步
            time.cancel();
            CURRENT_SETP += TEMP_STEP;
            lastStep = -1;
//            CountTimeState = 2;
            Log.v(TAG, "计时正常结束");

            timer = new Timer(true);
            TimerTask task = new TimerTask() {
                public void run() {
                    if (lastStep == CURRENT_SETP) {
                        timer.cancel();
                        CountTimeState = 0;
                        lastStep = -1;
                        TEMP_STEP = 0;
                        Log.v(TAG, "停止计步:" + CURRENT_SETP);
                    } else {
                        lastStep = CURRENT_SETP;
                    }
                }
            };
            timer.schedule(task, 0, 3000);
            CountTimeState = 3;//正常记步
        }

        @Override
        public void onTick(long millisUntilFinished) {
            if (lastStep == TEMP_STEP) {
                Log.v(TAG, "onTick 计时停止");
                time.cancel();
                CountTimeState = 0;
                lastStep = -1;
                TEMP_STEP = 0;
            } else {
                lastStep = TEMP_STEP;
            }
        }

    }
    //废弃的算法
    //    private void oldCalStep(SensorEvent event) {
//        // 用低通滤波器分离出重力加速度
//        gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
//        gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
//        gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];
//
//        average = (float) Math.sqrt(Math.pow(gravity[0], 2)
//                + Math.pow(gravity[1], 2) + Math.pow(gravity[2], 2));
//
//        if (average <= minValue) {
//            Log.v("xfblog", "低");
//            perCalTime = System.currentTimeMillis();
//        }
//        else if (average >= maxValue) {
//            Log.v("xfblog", "高");
//            float betweentime = System.currentTimeMillis()
//                    - perCalTime;
//            if (betweentime >= minTime && betweentime < maxTime) {
//                perCalTime = 0;
//                if (CountTimeState == 0) {
//                    // 开启计时器
//                    time = new TimeCount(duration, 800);
//                    time.start();
//                    CountTimeState = 1;
//                    Log.v(TAG, "开启计时器");
//                } else if (CountTimeState == 1) {
//                    TEMP_STEP++;
//                    Log.v(TAG, "计步中 TEMP_STEP:" + TEMP_STEP);
//                }
//                else if (CountTimeState == 3) {
//                    CURRENT_SETP++;
//                    if (onSensorChangeListener != null) {
//                        onSensorChangeListener.onChange();
//                    }
//                }
//
//
//            }
//        }
//    }
}

4、权限代码:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.pedometer"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-feature android:name="android.hardware.sensor.accelerometer" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.pedometer.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name="com.example.pedometer.StepService"
            android:process="com.example.pedometer.step"
            android:priority="1000">
            <intent-filter >
                <!-- 系统启动完成后会调用-->
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.DATE_CHANGED"/>
                <action android:name="android.intent.action.MEDIA_MOUNTED" />
                <action android:name="android.intent.action.USER_PRESENT" />
                <action android:name="android.intent.action.ACTION_TIME_TICK" />
                <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
                <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
            </intent-filter>
        </service>
    </application>

</manifest>

此算法为网上看到的,裁剪了部分不需要的代码,代码下载:点击打开链接


  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
描述 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()方法是解除绑定。 整个计步器的实现还是相对简单的,之前还想过要在圆弧圈外面画一个手指来指向当前的进度,不过暂时还没想通怎么实现,有兴趣的小伙伴研究之后告诉一声。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值