如何在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
相关产品推荐
相关产品推荐

