[转]在Android TV中实现组合按键的监听触发功能

在手机开发中,我们往往是与屏幕在做交互,而实体按键就寥寥几个。但是在Android TV开发中,按键就用得多了,大多数情况下我们是用遥控器按键来实现与电视的交互。

在Android 开发中,有时候会遇到这么一个需求:
  在按下特定的按键序列之后,启动某一个隐藏功能,或者是快捷启动某个东西。

那么我们如何实现这个触发过程呢?

我们通过需求分析,来定义一个接口。接口要定义哪些方法呢?
  首先,这个触发过程应该是有一些限制条件的吧,也就是在某些条件下才可以启动对组合键的监听,那么我们需要有个方法,来表示是否允许启动对组合键的监听。这个方法我们就叫做boolean allowTrigger()吧。

其次,我们应该要监听用户输入的每一个按键,是否对应接下来组合键各个位置的键值。比如组合键是1+2+3+4+5,那么用户按下1和2的时候应该是在检测有效的,但是本应该按下3的时候用户按下了6,那么不对应组合键的位置了,应该是无效的输入了。还有,总不能一直在监听用户接下来的组合输入吧,比如用户现在按下了1和2,那么按照我们的代码流程是继续探测用户是否输入3,但是用户要是过了一两个小时之后再输入呢?所以我们还需要探测用户输入按键的时间,来决定要不要继续响应组合键的探测。我们把上述这个过程的方法叫做boolean checkKey(int keycode,long eventTime);吧,第一个参数代表了键值,第二个代表了事件的时间。

然后,我们应该要有个方法,来判断是否已经完成了组合键的输入了吧?这里我们把这个方法叫做boolean checkMultKey();

这个过程中,我们也应该提供一个清除掉输入的方法吧,叫做void clearKeys();

最后,还要提供一个触发方法,就叫做onTrigger();吧
  基于以上的分析,编写出了接口IMultKeyTrigger:

/**
 * 组合按键触发功能的接口
 *
 */
public interface IMultKeyTrigger {
    /**
     * 是否允许触发,也就是触发组合键的条件
     * @return
     */
    boolean allowTrigger();
    /**
     * 检查输入的按键是否是对应组合键某个位置
     * @param keycode
     * @param eventTime
     * @return
     */
    boolean checkKey(int keycode,long eventTime);
    /**
     * 检查组合键是否已经输入完成
     * @return
     */
    boolean checkMultKey();
    /**
     * 清除所有记录的键
     */
    void clearKeys();
    /**
     * 组合键触发事件
     */
    void onTrigger();
}

在这里我们写一个具体实现类,实现按下1-5键的时候就自动弹出提示框。
  在实现类里面我们先定义几个变量:

//组合键序列
private final static int[] MULT_KEY = new int[]{1,2,3,4,5};
//是否限定要在时间间隔里再次输入按键
private static boolean ALLAW_SETTING_DELAYED_FLAG= true;
//允许用户在多少时间间隔里输入按键
private static int CHECK_NUM_ALLAW_MAX_DELAYED = 10000;
//记录用户连续输入了多少个有效的键
private static int check_num = 0;
//最后一次用户输入按键的时间
private static long lastEventTime = 0;

重写第一个方法, allowTrigger(),为了方便,我们直接让它返回true,表示任意条件下都允许触发。

重写checkKey方法,代码如下:

@Override
    public boolean checkKey(int keycode, long eventTime) {
        // TODO Auto-generated method stub
        boolean check;
        int delayed;
        //转换为实际数值
        int num = keycode - KeyEvent.KEYCODE_0;
        Log.i(TAG, "checkKey lastEventTime="+lastEventTime);
        Log.i(TAG, "checkKey num= "+num+" , eventTime = "+eventTime);
        //首次按键
        if(lastEventTime==0){
            delayed = 0;
        }else{
            //非首次按键
            delayed = (int)(eventTime-lastEventTime);
        }
        check = checkKeyValid(num, delayed);

        lastEventTime = check?eventTime:0L;

        Log.i(TAG, "checkKey check key valid = "+check);
        return check;
    }

代码首先是让键值码转换为实际数值,比如键值码是KeyEvent.KEYCODE_2,那么减去KeyEvent.KEYCODE_0得出的num便为2.
然后判断是首次按键的话则delayed 为 0,不是的话则取与上一次按键的时间间隔。

然后再调用checkKeyValid方法,如果返回true则更新最后一次按键的时间,否则重置为0。因为如果返回false代表按键无效,则要重置。
checkKeyValid是写在实现类里面的私有方法,源码如下:

/**
     * 传入用户输入的按键
     * @param num
     * @param delayed 两次按键之间的时间间隔
     * @return
     */
    private  boolean checkKeyValid(int num,int delayed){
        Log.i(TAG, "checkKey num= "+num+" , delayed = "+delayed);
        //如果超过最大时间间隔,则重置
        if(ALLAW_SETTING_DELAYED_FLAG&& delayed>CHECK_NUM_ALLAW_MAX_DELAYED){
            check_num = 0;
            return false;
        }
        //如果输入的数刚好等于校验位置的数,则有效输入+1
        if(check_num<MULT_KEY.length&&MULT_KEY[check_num]==num){
            check_num++;
            return true;
        }else{
            check_num = 0;//如果输入错误的话,则重置掉原先输入的
        }
        return false;
    }

checkKeyValid传入的第二个参数是时间间隔,如果是首个按键的话则是0。

然后先判断时间间隔是否有效(也就是如果是允许设置时间间隔,并且时间间隔小于我们设置的最大时间间隔,才有效)。

其次判断如果这个数值对应我们探测组合键的序列的话则check_num++;表示接下来探测下一个位置,并返回true,表示此次输入按键有效。

重写checkMultKey()方法,很显然。只需要满足已经检测完的个数是等于组合键的个数的话,说明用户已经输入完了有效的组合键。源码如下:

@Override
    public boolean checkMultKey() {
        // TODO Auto-generated method stub
        return check_num == MULT_KEY.length;
    }

重写clearKeys()方法,其实在这里需要重置的变量就是lastEventTime和check_num。代码如下:

    public void clearKeys() {
        // TODO Auto-generated method stub
        lastEventTime = 0;
        check_num = 0;
    }

最后重写一个onTrigger(),就是执行触发的方法。在这里我们测试弹出一个提示框,源码如下:

@Override
    public void onTrigger() {
        // TODO Auto-generated method stub
        //只有完成组合键的输入后才能触发弹出提示框
        if(checkMultKey()){
            startAlert();
        }else{
        //否则不能随便就调用触发
            throw new RuntimeException("you must be sure checkMultKey() is return true.");
        }
    }
    private void startAlert() {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("测试");
        builder.setMessage("组合键触发了");
        builder.show();
    }

看到上面弹出提示框需要context,我们在构造方法里传入context即可。

这样,我们就已经把实现类做好了,让我们在Activity上面监听按键,来完成我们的组合键监听吧。
创建一个上面我们写的接口实现类:

private IMultKeyTrigger  multKeyTrigger= new MyMultKeyTrigger(this); 

  在Activity的onKeyDown方法里监听数字按键1-9:

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.d(TAG, "keyCode:" + keyCode);
             if(handlerMultKey(keyCode, event)){
                return true;
            }
        return super.onKeyDown(keyCode, event);
}
public boolean handlerMultKey(int keyCode, KeyEvent event) {
        boolean vaildKey = false;
        if (keyCode >= KeyEvent.KEYCODE_0
                && keyCode <= KeyEvent.KEYCODE_9 && multKeyTrigger.allowTrigger()) {
            // 是否是有效按键输入
            vaildKey = multKeyTrigger.checkKey(keyCode, event.getEventTime());
            // 是否触发组合键
            if (vaildKey && multKeyTrigger.checkMultKey()) {
                    //执行触发
                    multKeyTrigger.onTrigger();
                    //触发完成后清除掉原先的输入
                    multKeyTrigger.clearKeys();

            }

        }

在这里我们就编写完成了,由于比较简单,就不过给出运行截图了。
最后,贴上实现类的源码吧:

public class MyMultKeyTrigger implements IMultKeyTrigger {
    private static String TAG =MyMultKeyTrigger.class.getSimpleName();

    //组合键序列
    private final static int[] MULT_KEY = new int[]{1,2,3,4,5};


    //是否限定要在时间间隔里再次输入按键
    private static boolean ALLAW_SETTING_DELAYED_FLAG = true;

    //允许用户在多少时间间隔里输入按键
    private static int CHECK_NUM_ALLAW_MAX_DELAYED = 10000;

    //记录用户连续输入了多少个有效的键
    private static int check_num = 0;

    private static long lastEventTime = 0;//最后一次用户输入按键的时间

    public Context context;

    public MyMultKeyTrigger(Context context){
        this.context = context;
    }
    @Override
    public boolean allowTrigger() {
        // TODO Auto-generated method stub
        return true;
    }
    @Override
    public boolean checkKey(int keycode, long eventTime) {
        // TODO Auto-generated method stub
        boolean check;
        int delayed;
        //转换为实际数值
        int num = keycode - KeyEvent.KEYCODE_0;
        Log.i(TAG, "checkKey lastEventTime="+lastEventTime);
        Log.i(TAG, "checkKey num= "+num+" , eventTime = "+eventTime);
        //首次按键
        if(lastEventTime==0){
            delayed = 0;
        }else{
            //非首次按键
            delayed = (int)(eventTime-lastEventTime);
        }
        check = checkKeyValid(num, delayed);

        lastEventTime = check?eventTime:0L;

        Log.i(TAG, "checkKey check key valid = "+check);
        return check;
    }
    /**
     * 传入用户输入的按键
     * @param num
     * @param delayed 两次按键之间的时间间隔
     * @return
     */
    private  boolean checkKeyValid(int num,int delayed){
        Log.i(TAG, "checkKey num= "+num+" , delayed = "+delayed);
        //如果超过最大时间间隔,则重置
        if(ALLAW_SETTING_DELAYED_FLAG && delayed>CHECK_NUM_ALLAW_MAX_DELAYED){
            check_num = 0;
            return false;
        }
        //如果输入的数刚好等于校验位置的数,则有效输入+1
        if(check_num<MULT_KEY.length&&MULT_KEY[check_num]==num){
            check_num++;
            return true;
        }else{
            check_num = 0;//如果输入错误的话,则重置掉原先输入的
        }
//      return check_num==MULT_KEY_RESTART.length;
        return false;
    }
    @Override
    public void clearKeys() {
        // TODO Auto-generated method stub
        lastEventTime = 0;
        check_num = 0;
    }

    @Override
    public boolean checkMultKey() {
        // TODO Auto-generated method stub
        return check_num == MULT_KEY.length;
    }

    @Override
    public void onTrigger() {
        // TODO Auto-generated method stub
        if(checkMultKey()){
            startAlert();
        }else{
            //throw new RuntimeException("you must be sure checkMultKey() is return true.");
        }
    }
    private void startAlert() {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("测试");
        builder.setMessage("组合键触发了");
        builder.show();

    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容