You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java实现的Android计时器中按特定间隔添加振动功能

问题与解决方案

需求说明

现有一个基于TextView的10分钟倒计时器,运行正常。需要添加以下振动逻辑:

  • 计时器启动后,第7秒首次振动
  • 之后按7秒、5秒的间隔循环振动,直到倒计时结束
  • 倒计时结束时触发一次长振动
    同时询问是否有更高效的计时器实现方式。

实现步骤

1. 添加振动权限

在AndroidManifest.xml中添加必要权限:

<uses-permission android:name="android.permission.VIBRATE" />
<!-- API 31+ 需添加通知权限,避免振动功能受限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

2. 振动功能实现

  • 采用Vibrator(兼容旧版系统)或VibratorManager(API31+)控制振动
  • 新增变量跟踪振动周期状态、上次振动时间,确保间隔逻辑准确
  • 在倒计时的onTick回调中计算已运行时长,判断是否到达振动节点
  • 在倒计时结束的onFinish中触发长振动

3. 计时器优化建议

CountDownTimer对于前台短计时场景已经足够高效,它基于Handler实现,性能稳定。如果需要后台持续运行或跨配置变化(如屏幕旋转),可以考虑:

  • 使用WorkManager实现后台计时(适合长时间任务)
  • 结合ViewModel保存计时器状态,避免屏幕旋转时重置

修改后的完整代码

public class RelaxActivity extends AppCompatActivity {

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main_menu, menu);
        return super.onCreateOptionsMenu(menu);
    }

    private static final long START_TIME_IN_MILLIS = 600000;
    // 振动配置参数
    private static final long FIRST_VIBRATE_DELAY = 7000;
    private static final long VIBRATE_INTERVAL_A = 7000;
    private static final long VIBRATE_INTERVAL_B = 5000;
    private static final long SHORT_VIBRATE_DURATION = 200;
    private static final long LONG_VIBRATE_DURATION = 2000;

    private TextView mTextViewCountDown;
    private Button mButtonStartPause;
    private Button mButtonReset;

    private CountDownTimer mCountDownTimer;
    private boolean mTimerRunning;
    private long mTimerLeftInMillis = START_TIME_IN_MILLIS;

    // 振动相关变量
    private Vibrator mVibrator;
    private long mLastVibrateElapsed = 0;
    private boolean mIsCycleA = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_relax);

        mTextViewCountDown = findViewById(R.id.text_view_countdown);
        mButtonStartPause = findViewById(R.id.button_start_stop);
        mButtonReset = findViewById(R.id.button_reset);

        // 初始化振动器
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            VibratorManager vibratorManager = (VibratorManager) getSystemService(Context.VIBRATOR_MANAGER_SERVICE);
            mVibrator = vibratorManager.getDefaultVibrator();
        } else {
            mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
        }

        mButtonStartPause.setOnClickListener(view -> {
            if (mTimerRunning) {
                pauseTimer();
            } else {
                startTimer();
            }
        });

        mButtonReset.setOnClickListener(view -> resetTimer());

        updateCountDownText();
    }

    private void startTimer() {
        // 重置振动状态
        mLastVibrateElapsed = 0;
        mIsCycleA = true;

        mCountDownTimer = new CountDownTimer(mTimerLeftInMillis, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                mTimerLeftInMillis = millisUntilFinished;
                updateCountDownText();
                checkVibrateTrigger();
            }

            @Override
            public void onFinish() {
                mTimerRunning = false;
                mButtonStartPause.setText("Start");
                mButtonStartPause.setVisibility(View.INVISIBLE);
                mButtonReset.setVisibility(View.VISIBLE);
                // 结束时长振动
                if (mVibrator != null && mVibrator.hasVibrator()) {
                    mVibrator.vibrate(LONG_VIBRATE_DURATION);
                }
            }
        }.start();

        mTimerRunning = true;
        mButtonStartPause.setText("Pause");
        mButtonReset.setVisibility(View.INVISIBLE);
    }

    private void pauseTimer() {
        mCountDownTimer.cancel();
        mTimerRunning = false;
        mButtonStartPause.setText("Start");
        mButtonReset.setVisibility(View.VISIBLE);
        // 暂停时取消当前振动
        if (mVibrator != null) {
            mVibrator.cancel();
        }
    }

    private void resetTimer() {
        mTimerLeftInMillis = START_TIME_IN_MILLIS;
        updateCountDownText();
        mButtonReset.setVisibility(View.INVISIBLE);
        mButtonStartPause.setVisibility(View.VISIBLE);
        // 重置振动状态
        mLastVibrateElapsed = 0;
        mIsCycleA = true;
        if (mVibrator != null) {
            mVibrator.cancel();
        }
    }

    private void updateCountDownText() {
        int minutes = (int) (mTimerLeftInMillis / 1000) / 60;
        int seconds = (int) (mTimerLeftInMillis / 1000) % 60;
        String timeLeftFormatted = String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);
        mTextViewCountDown.setText(timeLeftFormatted);
    }

    // 检查是否触发振动
    private void checkVibrateTrigger() {
        if (mVibrator == null || !mVibrator.hasVibrator()) {
            return;
        }

        long elapsedTime = START_TIME_IN_MILLIS - mTimerLeftInMillis;
        long timeSinceLastVibrate = elapsedTime - mLastVibrateElapsed;

        // 首次振动逻辑
        if (mLastVibrateElapsed == 0) {
            if (elapsedTime >= FIRST_VIBRATE_DELAY) {
                mVibrator.vibrate(SHORT_VIBRATE_DURATION);
                mLastVibrateElapsed = elapsedTime;
                mIsCycleA = false;
            }
            return;
        }

        // 循环振动逻辑:7秒、5秒交替
        if (mIsCycleA) {
            if (timeSinceLastVibrate >= VIBRATE_INTERVAL_A) {
                mVibrator.vibrate(SHORT_VIBRATE_DURATION);
                mLastVibrateElapsed = elapsedTime;
                mIsCycleA = false;
            }
        } else {
            if (timeSinceLastVibrate >= VIBRATE_INTERVAL_B) {
                mVibrator.vibrate(SHORT_VIBRATE_DURATION);
                mLastVibrateElapsed = elapsedTime;
                mIsCycleA = true;
            }
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.mnuMain:
                startActivity(new Intent(getApplicationContext(), MainMenuActivity.class));
                return true;
            case R.id.mnuExit:
                finish();
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

内容的提问来源于stack exchange,提问作者JoeFoxCo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 08:45:49