一、引言
前几天,我发布的日志《安卓应用开发学习:查看手机传感器信息》记录了如何查看手机传感器的信息,通过上述的方法,可以看到我的OPPO手机支持19种传感器。本篇日志就记录一下常见的加速度传感器的典型应用——“摇一摇”功能。本应用通过加速度传感器来实现摇骰子或摇红包。最终效果如下:
摇骰子
摇红包
游戏结束
二、功能实现
加速传感器是最常见的传感器之一,有很多应用的摇手机功能就是用到了这个传感器。本次通过学习相关资料,在我的手机上实现了摇骰子和摇红包两个小应用,并且在摇动手机的过程中手机还会振动。大体的实现方法如下:
1.实现振动功能
1.1先要在AndroidManifest.xml文件中添加如下权限。
<uses-permission android:name="android.permission.VIBRATE" />
1.2 在你创建的Activity中申明一个Vibrator对象。
private Vibrator mVibrator;
1.3在需要实现手机振动功能的代码块中,执行vibrate()方法。
mVibrator.vibrate(300); // 系统检测到摇一摇事件后,震动手机提示用户
该方法参数有两种形式:
一是形如 mVibrator.vibrate(300) 的单参数,表示让手机持续振动指定的毫秒数;
二是形如 mVibrator.vibrate({500, 200, 500}, -1) 的双参数,表示先振动500毫秒,然后停止200毫秒,再振动500毫秒。第二个参数为-1表示无循环,为正数,表示循环次数。
2.摇一摇功能的实现
2.1在你创建的Activity中申明一个SensorManager对象。
private SensorManager mSensorMgr;
2.2重写活动页面的onResume方法,在该方法中注册传感器监听事件,并指定待监听的传感器类型为加速度传感器。
@Override
protected void onResume() {
super.onResume();
// 给加速度传感器注册传感监听器
mSensorMgr.registerListener(this,
mSensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL);
}
2.3重写活动页面的onPause方法,在该方法中注销监听器。
@Override
protected void onPause() {
super.onPause();
mSensorMgr.unregisterListener(this); // 注销当前活动的传感监听器
}
2.3编写一个传感器事件监听器,该监听器继承自SensorEventListener。
在活动页面名称后面添加“implements SensorEventListener”(如下),
public class ShakeActivity extends AppCompatActivity implements SensorEventListener {...}
然后按Alt + Enter,Android Studio 自动添加onSensorChanged方法和onAccuracyChanged方法。
onSensorChanged方法在感应信息变化时触发,业务逻辑就写在这里。在本应用中添加的代码是检测手机晃动的幅度是否大于阀值,一旦大于阀值,就让手机振动500毫秒。
onAccuracyChanged方法在精度改变时触发,一般无需处理。
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { // 加速度变更事件
// values[0]:X轴,values[1]:Y轴,values[2]:Z轴
float[] values = event.values;
if ((Math.abs(values[0]) > 15 || Math.abs(values[1]) > 15
|| Math.abs(values[2]) > 15)) {
mVibrator.vibrate(300); // 系统检测到摇一摇事件后,震动手机提示用户
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 当传感器精度改变时回调该方法,一般无需处理
}
3.游戏模式的选择
3.1本应用支持2种游戏模式,因此在界面设计上用到了RadioGroup和RadioButton。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:text="游戏模式:" />
<RadioGroup
android:id="@+id/rg_mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_dice"
android:layout_width="80dp"
android:layout_height="20dp"
android:checked="true"
android:text="摇骰子" />
<RadioButton
android:id="@+id/rb_welfare"
android:layout_width="80dp"
android:layout_height="20dp"
android:text="摇红包" />
</RadioGroup>
3.2在活动页面声明如下变量:
private int mMode; // 游戏模式
private final int MODE_DICE = 0; // 游戏模式1:摇骰子
private final int MODE_WELFARE = 1; // 游戏模式2:摇红包
private boolean mState = false; // 游戏状态
private int diceCount; // 统计摇骰子次数
private int welfareNumber; // 统计红包个数
private final int[] welfareArr = {1, 5, 10} ; // 红包,可根据情况调整
3.2在活动页面中编写单选按钮组事件监听器,该监听器继承自RadioGroup.OnCheckedChangeListener。通过单选按钮的改变触发响应事件,在onCheckedChanged方法中编写游戏模式变更的逻辑代码。
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
// 根据单选按钮结果,设置游戏模式
if (checkedId == R.id.rb_dice) {
mMode = MODE_DICE; // 摇骰子模式
} else if (checkedId == R.id.rb_welfare) {
mMode = MODE_WELFARE; // 摇红包模式
}
}
4.游戏的执行逻辑
4.1游戏模式选择。默认是选中了摇一摇模式。
4.2点“开始”按钮。当前是非游戏状态;将变量diceCount和welfareNumber都设为0;将游戏状态变量mState设为Ture;页面中显示“请开始摇手机”;将按钮文本改为停止。游戏过程中游戏模式可随时切换,不会终止游戏。
4.3只有mState为Ture时摇动手机,才会进行检测。当检测到有效摇动时,手机会振动300毫秒,并执行startGame方法(延时300毫秒后执行,避免此方法实际执行次数与振动次数有大的差异。
4.4startGame方法中首先检查游戏模式。
如果是摇骰子模式,则产生三个1-6的随机数(设定为三个骰子)。将本次的结果显示在页面上,并将摇骰子次数统计变量diceCount加1。
如果是摇红包模式,则产生一个1-10的随机数,将该随机数与数组welfareArr中的元素进行对比,如果该随机数在数组中,则在页面中显示中奖信息。获得红包统计变量welfareNumber加1。
5.游戏状态下点“停止”按钮,结束游戏。将按钮文本改为开始;页面中显示摇骰子次数和获得红包个数。
三、代码展示
最终的代码如下:
1. 界面设计文件 activity_shake.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ShakeActivity">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="摇一摇"
android:textSize="28sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/ll_mode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="10dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:text="游戏模式:" />
<RadioGroup
android:id="@+id/rg_mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_dice"
android:layout_width="80dp"
android:layout_height="20dp"
android:checked="true"
android:text="摇骰子" />
<RadioButton
android:id="@+id/rb_welfare"
android:layout_width="80dp"
android:layout_height="20dp"
android:text="摇红包" />
</RadioGroup>
</LinearLayout>
<Button
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="开始"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ll_mode" />
<TextView
android:id="@+id/tv_shake"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:textSize="17sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_start" />
</androidx.constraintlayout.widget.ConstraintLayout>
2.逻辑代码 ShakeActivity.java
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
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.Vibrator;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.RadioGroup;
import android.widget.TextView;
import java.util.Arrays;
import java.util.Locale;
import java.util.Random;
public class ShakeActivity extends AppCompatActivity implements SensorEventListener,
RadioGroup.OnCheckedChangeListener, View.OnClickListener {
private final static String TAG = "ShakeActivity";
private TextView tv_shake; // 声明一个文本视图对象
private SensorManager mSensorMgr; // 声明一个传感管理器对象
private Vibrator mVibrator; // 声明一个震动器对象
private Button btn_start; // 开始按钮
private int mMode; // 游戏模式
private final int MODE_DICE = 0; // 游戏模式1:摇骰子
private final int MODE_WELFARE = 1; // 游戏模式2:摇红包
private boolean mState = false; // 游戏状态
private int diceCount; // 统计摇骰子次数
private int welfareNumber; // 统计红包个数
private final int[] welfareArr = {1, 5, 10} ; // 红包 , 20, 50, 100, 500, 1000
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_shake);
// 游戏模式选择按钮组
RadioGroup rg_mode = findViewById(R.id.rg_mode);
rg_mode.setOnCheckedChangeListener(this);
tv_shake = findViewById(R.id.tv_shake);
btn_start = findViewById(R.id.btn_start);
btn_start.setOnClickListener(this); // 开始按钮点击监听
// 从系统服务中获取传感管理器对象
mSensorMgr = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
// 从系统服务中获取震动器对象
mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
mMode = MODE_DICE; // 单选按钮初始状态选中的遥骰子
}
@Override
protected void onPause() {
super.onPause();
mSensorMgr.unregisterListener(this); // 注销当前活动的传感监听器
}
@Override
protected void onResume() {
super.onResume();
// 给加速度传感器注册传感监听器
mSensorMgr.registerListener(this,
mSensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { // 加速度变更事件
// values[0]:X轴,values[1]:Y轴,values[2]:Z轴
float[] values = event.values;
if ((Math.abs(values[0]) > 30 || Math.abs(values[1]) > 30
|| Math.abs(values[2]) > 30)) {
if (mState) {
mVibrator.vibrate(300); // 系统检测到摇一摇事件后,震动手机提示用户
new Handler().postDelayed(() -> {
// 延时后要执行的代码
startGame();
}, 300); // 延迟时间毫秒
}
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 当传感器精度改变时回调该方法,一般无需处理
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
// 根据单选按钮结果,设置游戏模式
if (checkedId == R.id.rb_dice) {
mMode = MODE_DICE; // 摇骰子模式
} else if (checkedId == R.id.rb_welfare) {
mMode = MODE_WELFARE; // 摇红包模式
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_start) {
if (mState) { // 点击时处于游戏状态
btn_start.setText("开始");
String info = String.format(Locale.CHINESE, "%s%d次,%s%d个。",
"本次摇骰子", diceCount, "获得红包", welfareNumber);
tv_shake.setText(info);
} else { // 点击时处于非游戏状态
diceCount = 0;
welfareNumber = 0;
tv_shake.setText("请开始摇手机");
btn_start.setText("停止");
}
mState = !mState;
}
}
private void startGame() {
String info = "";
// 检查游戏模式
if (mMode == MODE_DICE) { // 摇骰子
// 设定有3个骰子,产生3个1-6的随机数
Random random = new Random();
int dice1 = random.nextInt(6) + 1;
int dice2 = random.nextInt(6) + 1;
int dice3 = random.nextInt(6) + 1;
info = String.format(Locale.CHINESE,"%s%d,%d,%d。", "您摇出的骰子点数是:",
dice1, dice2, dice3);
diceCount +=1;
} else if (mMode == MODE_WELFARE) { // 摇红包
int lottery;
info = "很遗憾,您没有中奖";
// 生成一个0-10的随机数
int randomNumber = (int)(Math.random() * 11); // 1001
Log.d(TAG, "随机数为:" + randomNumber);
// 检查该随机数是否在红包列表中
boolean isInArray = Arrays.stream(welfareArr).anyMatch(n -> n == randomNumber);
if (isInArray) {
lottery = randomNumber;
info = String.format(Locale.CHINESE,"%s%d%s", "恭喜您中了", lottery,"元!");
welfareNumber +=1;
}
}
tv_shake.setText(info);
}
}