在手机开发中,我们往往是与屏幕在做交互,而实体按键就寥寥几个。但是在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。代码如下:
@Override
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();
}
}
可能有读者觉得,干嘛要编写个接口呢,直接写上面这个类也可以的。其实在项目开发里面,提倡的是面向接口编程或者说抽象编程,这样带来的好处是已扩展,易维护。比如上面的需求:在特定条件下,触发的是另外一个组合按键,就是按下5-9键后触发启动一个界面。那么我们只需要实现这个接口,在activity里面,不需要更改源码,只需要在创建对象的时候,根据条件而选择创建某个实现类,这就是接口带来的多态的好处。