根据Android系统源码拨号应用的键盘的实现方式实现进行实现

之前有个需求是和手机拨号键盘一样类似的需求,虽然之前也做过类似的需求,但是觉得之前的实现不够优雅和完善,刚好因为一些原因在对源码进行各种探究,于是顺便看看源码中的拨号键盘是如何实现.
最开始也在网上进行了一些查找,不过比较细致和完善的也没有,这里直接把系统源码中的实现进行完整展示.
拨号的系统应用是Dialer,对这部分的处理在它的依赖库PhoneCommon和InCallUI中,具体路径如下:

PhoneCommon
com.android.phone.common.dialpad.DigitsEditText
PhoneCommon\res\layout\dialpad_view_unthemed.xml
InCallUI
com.android.phone.common.dialpad.DialpadView

处理要点

  1. 进入界面和点击EditText时不弹出系统输入法
  2. 保持EditText按住时左右滑动文字也进行滑动效果
  3. 未输入和在最末尾输入时一般不显示光标,在中间时进行显示
  4. 有时是设置为可以点击将光标移动到中央,有时是禁用点击将光标移动到文字中
  5. 禁用点击移动光标时进行滑动到中间后继续输入会自动跳转到末尾
  6. 将选择的字符输入到EditText

禁用系统输入法

1. EditText简单自定义

这里直接贴出PhoneCommon中的源码

/**
* EditText which suppresses IME show up.
*/
public class DigitsEditText extends EditText {

public DigitsEditText(Context context, AttributeSet attrs) {
super(context, attrs);
setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
setShowSoftInputOnFocus(false);
}

@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
final InputMethodManager imm = ((InputMethodManager) getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE));
if (imm != null && imm.isActive(this)) {
imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0);
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {
final boolean ret = super.onTouchEvent(event);
// Must be done after super.onTouchEvent()
final InputMethodManager imm = ((InputMethodManager) getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE));
if (imm != null && imm.isActive(this)) {
imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0);
}
return ret;
}
}

SUGGESTIONS是禁用拼写检查,其他的处理和网上的一些帖子说的一样,就是在点击效果时隐藏掉输入法

<DigitsEditText
android:background="@android:color/transparent"
android:cursorVisible="false"
android:focusableInTouchMode="true"
android:freezesText="true"
android:gravity="center"
android:maxLines="1"
android:scrollHorizontally="true"
android:singleLine="true"
android:textCursorDrawable="@null"/>

这是源码中进行使用时的一些配置
cursorVisible是光标默认显示隐藏
freezesText是旋转时会保存已输入的内容

2. 进入界面时不弹出软键盘

即使自定义了EditText,但是在进入界面时如果未进行配置还是会因为光标会默认找寻EditText进行选中弹出
这里有2种方式进行解决:

  1. 界面打开时焦点默认选中到别处
    在EditText的父控件上设置两个属性

    android:focusable="true"
    android:focusableInTouchMode="true"
  2. 在AndroidManifest中对该界面的输入法弹出行为进行配置
    在Activity配置android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"即可

点击和光标的处理

点击和光标由以下几个方法进行控制:

EditText.setClickable(Boolean);
EditText.setLongClickable(Boolean);
EditText.setFocusableInTouchMode(Boolean);
EditText.setCursorVisible(Boolean);

1. 可进行点击来移动光标位置,长按具有选中全部效果

具体效果就是手机中拨号键盘输入后未播出时的效果

  1. 将上述4个方法中前3个全部置为true
  2. 因为开始默认设置了光标不显示,如果有输入内容时点击显示光标
    设置点击效果:
    if (content.etInput.length() != 0) {
    content.etInput.isCursorVisible = true
    }
    输入内容清空时隐藏光标
    etInput.addTextChangedListener(this)

    override fun afterTextChanged(s: Editable) {
    if (etInput.length() == 0) {
    etInput.isCursorVisible = false
    }
    }
  3. 在末尾继续输入时隐藏光标
    参加下面进行输入的keyPressed方法中的处理

2. 只能继续末尾输入和滑动查看,滑动到中间进行输入自动跳到末尾

具体效果也就是手机中播出接通后进行输入时的效果
将上面的四个方法全部置为false即可

优雅的进行输入

虽然直接setText()也是可以进行输入控制,不过源码中的方式更为优雅,并且直接setText()在是插入时需要进行额外处理

android.view.KeyEvent中定义了键盘输入的所有键值,比如0-9对应KeyEvent.KEYCODE_0-9

EditText继承于TextView的onKeyDown(int keyCode, KeyEvent event)方法即可进行输入,并且根据光标位置进行插入

R.id.tvNum0 -> keyPressed(KeyEvent.KEYCODE_0)
R.id.tvNum1 -> keyPressed(KeyEvent.KEYCODE_1)
R.id.tvNum2 -> keyPressed(KeyEvent.KEYCODE_2)
R.id.tvNum3 -> keyPressed(KeyEvent.KEYCODE_3)
R.id.tvNum4 -> keyPressed(KeyEvent.KEYCODE_4)
R.id.tvNum5 -> keyPressed(KeyEvent.KEYCODE_5)
R.id.tvNum6 -> keyPressed(KeyEvent.KEYCODE_6)
R.id.tvNum7 -> keyPressed(KeyEvent.KEYCODE_7)
R.id.tvNum8 -> keyPressed(KeyEvent.KEYCODE_8)
R.id.tvNum9 -> keyPressed(KeyEvent.KEYCODE_9)
R.id.tvSymbolAsterisk -> keyPressed(KeyEvent.KEYCODE_STAR)
R.id.tvSymbolPoundSign -> keyPressed(KeyEvent.KEYCODE_POUND)

private fun keyPressed(keyCode: Int) {
etInput.onKeyDown(keyCode, KeyEvent(KeyEvent.ACTION_DOWN, keyCode))

// If the cursor is at the end of the text we hide it.
val length = content.etInput.length()
if (length == etInput.selectionStart && length == etInput.selectionEnd) {
etInput.isCursorVisible = false
}
}

源码中对实现EditText自定义键盘进行输入的基本内容如上述,在源码中还有很多额外处理,比如按键音的播放也在其中,有兴趣和需要的可以自行查看.