Android自定义键盘:InputMethodService中PopupWindow按键预览不显示求助
解决自定义键盘PopupWindow按键预览不显示的问题
核心问题分析
- 窗口层级不匹配:输入法属于系统级窗口,普通PopupWindow无法在其上显示,需指定适配的窗口类型。
- 预览窗口被立即关闭:原代码在点击事件中调用
writeText时直接关闭PopupWindow,导致窗口刚显示就消失。 - 坐标计算错误:
getX()/getY()获取的是按键相对父布局的坐标,而showAtLocation需要屏幕绝对坐标,导致窗口位置异常。 - PopupWindow初始化不完整:未设置背景、窗口类型等必要参数,部分Android版本下无法渲染窗口。
修复后的完整代码
1. 调整PopupWindow初始化与触摸事件逻辑
import android.inputmethodservice.InputMethodService; import android.os.Build; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.view.inputmethod.InputConnection; import android.widget.Button; import android.widget.PopupWindow; import android.widget.TextView; public class ButtonsKeyboard extends InputMethodService { final int KEY_CODE_DELETE = -1; View layout_KeyPad, layout_KeyPreview; PopupWindow keyPopup; TextView tvKeyPreview; @Override public View onCreateInputView() { layout_KeyPad = getLayoutInflater().inflate(R.layout.number_key_pad, null); layout_KeyPreview = getLayoutInflater().inflate(R.layout.key_preview, null); tvKeyPreview = layout_KeyPreview.findViewById(R.id.tvKeyPreview); Button button1 = layout_KeyPad.findViewById(R.id.Button1); Button button2 = layout_KeyPad.findViewById(R.id.Button2); Button button3 = layout_KeyPad.findViewById(R.id.Button3); Button buttonDelete = layout_KeyPad.findViewById(R.id.ButtonDelete); // 初始化PopupWindow,提前配置核心参数 keyPopup = new PopupWindow(layout_KeyPreview, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); keyPopup.setBackgroundDrawable(getResources().getDrawable(android.R.color.transparent)); // 设置适配输入法的窗口类型 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { keyPopup.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); } else { keyPopup.setWindowLayoutType(WindowManager.LayoutParams.TYPE_INPUT_METHOD); } keyPopup.setOutsideTouchable(false); keyPopup.setFocusable(false); // 改用触摸事件实现:按下显示预览,抬起/取消时输入文本并关闭预览 button1.setOnTouchListener(getKeyTouchListener(button1)); button2.setOnTouchListener(getKeyTouchListener(button2)); button3.setOnTouchListener(getKeyTouchListener(button3)); buttonDelete.setOnClickListener(v -> { InputConnection ic = getCurrentInputConnection(); if (ic != null) ic.deleteSurroundingText(1, 0); }); return layout_KeyPad; } // 封装通用触摸事件逻辑,避免重复代码 private View.OnTouchListener getKeyTouchListener(Button button) { return (v, event) -> { int keyCode = Integer.parseInt(v.getTag().toString()); if (keyCode > 0) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: tvKeyPreview.setText(button.getText()); // 获取按键的屏幕绝对坐标 int[] location = new int[2]; v.getLocationOnScreen(location); // 设置预览窗口大小与位置(显示在按键上方) keyPopup.setWidth(v.getWidth() + 4); keyPopup.setHeight(v.getHeight() + 4); keyPopup.showAtLocation(layout_KeyPad, Gravity.NO_GRAVITY, location[0] - 2, location[1] - v.getHeight() - 6); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: writeText(keyCode); keyPopup.dismiss(); break; } } return true; }; } private void writeText(int keyCode) { InputConnection ic = getCurrentInputConnection(); if (ic == null) return; char code = (char) keyCode; ic.commitText(String.valueOf(code), 1); } }
2. 布局文件无需修改(保留原key_preview.xml与number_key_pad.xml)
额外优化建议
- 可以根据屏幕分辨率动态调整预览窗口的偏移量,保证在不同设备上显示位置合理。
- 给预览窗口添加阴影、圆角等样式,提升视觉体验。
- 对删除键也添加预览效果,保持交互一致性。
内容的提问来源于stack exchange,提问作者SHIMON HANSDA
相关产品推荐
相关产品推荐

