安卓应用开发学习:手机摇一摇功能应用尝试--摇骰子和摇红包

一、引言

前几天,我发布的日志《安卓应用开发学习:查看手机传感器信息》记录了如何查看手机传感器的信息,通过上述的方法,可以看到我的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);
    }
}
相关推荐
猫爪笔记1 分钟前
前端:HTML (学习笔记)【2】
前端·笔记·学习·html
计算机毕设指导616 分钟前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
Gu Gu Study18 分钟前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言
Chris _data20 分钟前
二叉树oj题解析
java·数据结构
牙牙70526 分钟前
Centos7安装Jenkins脚本一键部署
java·servlet·jenkins
paopaokaka_luck34 分钟前
[371]基于springboot的高校实习管理系统
java·spring boot·后端
-一杯为品-42 分钟前
【51单片机】程序实验5&6.独立按键-矩阵按键
c语言·笔记·学习·51单片机·硬件工程
以后不吃煲仔饭1 小时前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师1 小时前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
The_Ticker1 小时前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程