Android事件处理之物理按键

本文探讨了智能机物理按键的检测方法,包括重写onKeyDown方法和使用广播接收器监测特定按键。深入解析了返回键误触处理及音量键的多功能调整,展示了自定义音量对话框的实现,旨在提升用户体验。

随着时代的发展与进步,智能机普及率大大提高,甚至是与世隔绝的老大爷大妈都在子女的指导下能够正常地使用智能机。只要使用过智能机的人都不难发现,智能机上的按键分为物理按键和虚拟按键,虚拟按键的使用率大大高于物理按键,现在我们就来谈谈鲜有人关注的物理按键。

一、检测物理按键的方法

  1. 在我们使用虚拟按键之前,一般都会给控件注册监听器。但是当我们在使用物理按键的时候,除了上述的方法,还能够直接在活动页面上检测物理按键,即重写Activity的onKeyDown方法。
 public boolean onKeyDown(int keyCode, KeyEvent event) {
        desc = String.format("%s物理按键的编码是%d", desc, keyCode);
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            desc = String.format("%s, 按键为返回键", desc);
            // 延迟3秒后启动页面关闭任务
            new Handler().postDelayed(mFinish, 3000);
        } else if (keyCode == KeyEvent.KEYCODE_MENU) {
            desc = String.format("%s, 按键为菜单键", desc);
        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
            desc = String.format("%s, 按键为加大音量键", desc);
        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
            desc = String.format("%s, 按键为减小音量键", desc);
        }
        desc = desc + "\n";
        tv_result.setText(desc);
        // 返回true表示不再响应系统动作,返回false表示继续响应系统动作
        return true;
    }
  1. 从上面的代码中不难看出,onKeyDown方法拥有按键编码与按键事件KeyEvent两个参数。
  2. onKeyDown方法只可检测4个物理按键事件,即菜单键、返回键、加大音量键和减小音量键,而主页键和任务键则就需要通过广播接收器来监测。

二、onKeyDown与onKey的区别

在使用onKeyDown方法之后,可以发现onKeyDown与onKey的不同之处:

  1. onKeyDown只能在Activity代码中使用,而onKey只要有可注册的控件就能使用。
  2. onKeyDown只能检测物理按键,无法检测输入法按键(如回车键、删除键等),而onKey可同时检测两类按键。
  3. onKeyDown不区分按下与松开两个动作,而onKey区分这两个动作。

三、生活案例延伸

(一)返回键常常会误触

在我们生活中,经常会出现误触返回键而导致退出应用的情况,我就经常在用手机浏览网页的时候不小心触碰到返回键,导致误退出的情况发生,这会影响使用者的心情。那么,如何才能对误触情况的发生做一个有效的处理呢?我在我一直使用的一款浏览器上找到了答案,这款浏览器叫做Quark,在这也向大家推荐这款浏览器,这款浏览器界面清晰易懂,因此简洁易用,它对返回键误触的处理方法见下图:

在这里插入图片描述

实现这个效果的方法有以下两种:

  1. 在onKeyDown方法中拦截返回键。
private boolean needExit = false;  // 是否需要退出App
    
    // 在发生物理按键动作时触发
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {  // 按下返回键
            if (needExit) {
                finish();  // 关闭当前页面
            }
            needExit = true;
            Toast.makeText(this, "再按一次返回键退出!", Toast.LENGTH_SHORT).show();
            return true;
        } else {
            return super.onKeyDown(keyCode, event);
        }
    }
  1. 重写Activity代码的onBackPressed方法,该方法专门响应按返回键事件。
private boolean needExit = false;  // 是否需要退出App
    
    // 在按下返回键时触发
    public void onBackPressed() {
        if (needExit) {
            finish();  // 关闭当前页面
            return;
        }
        needExit = true;
        Toast.makeText(this, "再按一次返回键退出!", Toast.LENGTH_SHORT).show();
    }
(二)两个音量键如何满足多种类型的音量调整

在使用手机的过程中,细心的人就会发现,音量键只有增大音量和减小音量两个按键。但是音量类型缺不止一个,有按键音量、铃声音量、媒体音量、通知音量、系统音量、语音助手音量等等,这么多的音量类型,怎么能通过区区两个按键来调整呢?因此,要处理好这个问题,就要在按下音量增减键的时候,弹出一个对话框,让用户选择希望调节的铃音类型,并显示拖动条,方便用户把音量一次调整到位,不必连续按增加键或减小键。我们可以通过对音量对话框的自定义来实现这个要求,下面就是对自定义音量对话框的实现:

public class VolumeDialog implements OnSeekBarChangeListener, OnKeyListener {
    private Dialog dialog;  // 声明一个对话框对象
    private View view;  // 声明一个视图对象
    private SeekBar sb_music;  // 声明一个拖动条对象
    private AudioManager mAudioMgr;  // 声明一个音频管理器对象
    private int MUSIC = AudioManager.STREAM_MUSIC;  // 音乐的音频流类型
    private int mMaxVolume;  // 最大音量
    private int mNowVolume;  // 当前音量

    public VolumeDialog(Context context) {
        // 从系统服务中获取音频管理器
        mAudioMgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        // 获取指定音频类型的最大音量
        mMaxVolume = mAudioMgr.getStreamMaxVolume(MUSIC);
        // 获取指定音频类型的当前音量
        mNowVolume = mAudioMgr.getStreamVolume(MUSIC);
        // 根据布局文件dialog_volume.xml生成视图对象
        view = LayoutInflater.from(context).inflate(R.layout.dialog_volume, null);
        // 创建一个指定风格的对话框对象
        dialog = new Dialog(context, R.style.VolumeDialog);
        // 从布局文件中获取名叫sb_music的拖动条
        sb_music = view.findViewById(R.id.sb_music);
        // 设置拖动条的拖动变更监听器
        sb_music.setOnSeekBarChangeListener(this);
        // 设置拖动条的拖动进度
        sb_music.setProgress(sb_music.getMax() * mNowVolume / mMaxVolume);
    }

    // 显示对话框
    public void show() {
        // 设置对话框窗口的内容视图
        dialog.getWindow().setContentView(view);
        // 设置对话框窗口的布局参数
        dialog.getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        dialog.show();  // 显示对话框
        // 设置拖动条允许获得焦点
        sb_music.setFocusable(true);
        // 设置拖动条在触摸情况下允许获得焦点
        sb_music.setFocusableInTouchMode(true);
        // 设置拖动条的按键监听器
        sb_music.setOnKeyListener(this);
    }

    // 关闭对话框
    public void dismiss() {
        // 如果对话框显示出来了,就关闭它
        if (dialog != null && dialog.isShowing()) {
            dialog.dismiss();  // 关闭对话框
        }
    }

    // 判断对话框是否显示
    public boolean isShowing() {
        if (dialog != null) {
            return dialog.isShowing();
        } else {
            return false;
        }
    }

    // 按音量方向调整音量
    public void adjustVolume(int direction, boolean fromActivity) {
        if (direction == AudioManager.ADJUST_RAISE) {  // 调大音量
            mNowVolume++;
        } else {  // 调小音量
            mNowVolume--;
        }
        // 设置拖动条的当前进度
        sb_music.setProgress(sb_music.getMax() * mNowVolume / mMaxVolume);
        // 把该音频类型的当前音量往指定方向调整
        mAudioMgr.adjustStreamVolume(MUSIC, direction, AudioManager.FLAG_PLAY_SOUND);
        if (mListener != null && !fromActivity) {
            mListener.onVolumeAdjust(mNowVolume);
        }
        close();
    }

    // 准备关闭对话框
    private void close() {
        // 移除原来的对话框关闭任务
        mHandler.removeCallbacks(mClose);
        // 延迟两秒后启动对话框关闭任务
        mHandler.postDelayed(mClose, 2000);
    }

    private Handler mHandler = new Handler();  // 声明一个处理器对象
    // 声明一个关闭对话框任务
    private Runnable mClose = new Runnable() {
        public void run() {
            dismiss();
        }
    };

    // 在进度变更时触发。第三个参数为true表示用户拖动,为false表示代码设置进度
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}

    // 在开始拖动进度时触发
    public void onStartTrackingTouch(SeekBar seekBar) {}

    // 在停止拖动进度时触发
    public void onStopTrackingTouch(SeekBar seekBar) {
        // 计算拖动后的当前音量
        mNowVolume = mMaxVolume * seekBar.getProgress() / seekBar.getMax();
        // 设置该音频类型的当前音量
        mAudioMgr.setStreamVolume(MUSIC, mNowVolume, AudioManager.FLAG_PLAY_SOUND);
        if (mListener != null) {
            mListener.onVolumeAdjust(mNowVolume);
        }
        close();
    }

    // 在发生按键动作时触发
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_VOLUME_UP 
                && event.getAction() == KeyEvent.ACTION_DOWN) {  // 按下了音量加键
            adjustVolume(AudioManager.ADJUST_RAISE, false);  // 调大音量
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN 
                && event.getAction() == KeyEvent.ACTION_DOWN) {  // 按下了音量减键
            adjustVolume(AudioManager.ADJUST_LOWER, false);  // 调小音量
            return true;
        } else {
            return false;
        }
    }

    private VolumeAdjustListener mListener;  // 声明一个音量调节的监听器对象
    // 设置音量调节监听器
    public void setVolumeAdjustListener(VolumeAdjustListener listener) {
        mListener = listener;
    }

    // 定义一个音量调节的监听器接口
    public interface VolumeAdjustListener {
        void onVolumeAdjust(int volume);
    }
}
在页面代码中通过检测音量增加键和减小键弹出音量对话框,代码如下:
public class VolumeSetActivity extends AppCompatActivity implements VolumeAdjustListener {
    private TextView tv_volume;
    private VolumeDialog dialog;  // 声明一个音量调节对话框对象
    private AudioManager mAudioMgr;  // 声明一个音量管理器对象

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_volume_set);
        tv_volume = findViewById(R.id.tv_volume);
        // 从系统服务中获取音量管理器
        mAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    }

    // 在发生物理按键动作时触发
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_VOLUME_UP 
                && event.getAction() == KeyEvent.ACTION_DOWN) {  // 按下音量加键
            // 显示音量调节对话框,并将音量调大一级
            showVolumeDialog(AudioManager.ADJUST_RAISE);
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN 
                && event.getAction() == KeyEvent.ACTION_DOWN) {  // 按下音量减键
            // 显示音量调节对话框,并将音量调小一级
            showVolumeDialog(AudioManager.ADJUST_LOWER);
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_BACK) {  // 按下返回键
            finish();  // 关闭当前页面
            return false;
        } else {  // 其它按键
            return false;
        }
    }

    // 显示音量调节对话框
    private void showVolumeDialog(int direction) {
        if (dialog == null || !dialog.isShowing()) {
            // 创建一个音量调节对话框
            dialog = new VolumeDialog(this);
            // 设置音量调节对话框的音量调节监听器
            dialog.setVolumeAdjustListener(this);
            // 显示音量调节对话框
            dialog.show();
        }
        // 令音量调节对话框按音量方向调整音量
        dialog.adjustVolume(direction, true);
        onVolumeAdjust(mAudioMgr.getStreamVolume(AudioManager.STREAM_MUSIC));
    }

    // 在音量调节完成后触发
    public void onVolumeAdjust(int volume) {
        tv_volume.setText("调节后的音乐音量大小为:" + volume);
    }
}

遇到的问题:因为自定义对话框的代码不在Activity中,所以无法通过onKeyDown方法检测按键,只能给拖动条注册按键监听器OnKeyListener。另外,在页面代码要重写onKeyDown方法,通过检测音量增加键和减小键弹出音量对话框。

效果图如下:

按音量键后弹出对话框

​ 按音量键后弹出对话框

拖动对话框上的拖动条来调整音量大小

​ 拖动对话框上的拖动条来调整音量大小

四、总结

技术来源于生活,服务于生活。细心的人观察生活,创造技术,让生活变得更简单。不管对学习还是生活,不管遇到多大的bug,都不能焦躁和放弃,耐心、细心地对待问题,才能做出实事,从根本上解决问题。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值