自定义View实践:指南针的实现
本文将介绍如何通过自定义View实现了一个指南针的效果,效果图如下:
源码GitHub地址
首先是根据磁力计和加速度计计算南向和手机的夹角。通过Android的SensorManager类进行计算,使用的是右手坐标系:
获取SensorManager,并初始化磁力计和加速度计:
public class CompassActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager mSensorManager;
private Sensor mMagneticSensor;
private Sensor mAccelerateSensor;
@Overrideprotected void onCreate(Bundle savedInstanceState) {
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null && mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null)
{
mMagneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mAccelerateSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mHasNeededSensors = true;
} else {
Toast.makeText(this, "没有磁力计或加速度计", Toast.LENGTH_SHORT).show();
return;
}
}
在onResume里面注册磁力计和加速度计,并在onPause的时候解除注册:
@Override
protected void onResume() {
super.onResume();
if (mHasNeededSensors) {
mSensorManager.registerListener(this, mMagneticSensor, SensorManager.SENSOR_DELAY_NORMAL);
mSensorManager.registerListener(this, mAccelerateSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
@Override
protected void onPause() {
super.onPause();
if (mHasNeededSensors) {
mSensorManager.unregisterListener(this);
}
}
实现onSensorChanged接口,这样当磁力计或加速度计数值发生变化的时候会调用该函数告知新数值:
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
mMagneticFieldValues = event.values;
} else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mAccelerometerValues = event.values;
}
calculateOrientation();
}
通过calculateOrientation函数计算南向跟手机x轴的夹角,这里用到SensorManager的两个函数,getRotationMatrix和getOrientation。具体计算原理可以参阅两个函数的实现,用法很简单,传入加速度计和磁力计数值即可。得到一个3X1的矩阵,矩阵的三个值代表南向绕三个坐标轴旋转的角度,单位是弧度,我们绘制指南针只需要使用矩阵的第一个值,即南向绕Z轴顺时针旋转过的角度,用alpha表示。当南向指向手机正上方时,alpha=0;指向手机正下方时,alpha=MATH.PI,如下图所示:
为了方便使用极坐标绘制指南针的罗盘,我们把它转换成和X轴的夹角seta,seta=alpha-Math.PI/2,计算的代码如下:
// 计算指南针的南向和手机x轴的角度,以弧度表示(-PI, PI]
private void calculateOrientation() {
float[] results = new float[3];
float[] rotates = new float[9];
SensorManager.getRotationMatrix(rotates, null, mAccelerometerValues, mMagneticFieldValues);
SensorManager.getOrientation(rotates, results);
// alpha是南向和手机Y轴的夹角
float alpha = results[0];
float seta;
// 将alpha转换成南向和手机X轴的夹角,便于使用极坐标系绘制指南针的圆盘
if ((alpha - (-Math.PI)) < 0.000000001) {
seta = (float) (Math.PI / 2);
} else {
seta = (float) (alpha - Math.PI / 2);
}
Log.i("compass:", Math.toDegrees(seta)+"");
mCompassView.setSouth(seta);
mCompassView.invalidate();
}
利用夹角seta,我们就可以利用自定义View绘制指南针了。
首先定义指南针View的属性,通过这些属性,我们可以控制指南针的外观:
<resources>
<declare-styleable name="CompassViewStyle">
<attr name="radius" format="dimension" /> <!--罗盘半径-->
<attr name="short_dash" format="dimension" /> <!--罗盘外圈短辐射线的长度-->
<attr name="long_dash" format="dimension" /> <!--罗盘外圈长辐射线的长度-->
<attr name="text_size" format="dimension" /> <!--罗盘上文字的尺寸-->
</declare-styleable>
</resources>
在自定义View里解析这些属性: