12

我创建了一个自定义键盘,效果很好 - 除了前两行键的预览视图显示不够高。它们的垂直位置受到父布局的限制。

这些屏幕截图说明了问题 - '0' 和 '8' 的预览位置很好,但对于 '5' 和 '2' 则不是:

键“0”的预览显示在按钮上方...

按钮“0”

键“8”的预览也显示在按钮上方...

按钮'8'

但是按钮上方未显示键“5”的预览...

按钮“5”

并且按钮上方未显示键“2”的预览...

按钮'2'

如何克服,所以“5”和“2”的预览显示在它们各自键上方的相同距离处,就像“0”和“8”一样。

这是我的keyboard.xml ...

<android.inputmethodservice.KeyboardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/keyboard"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:keyPreviewLayout="@layout/keyboard_key_preview" />

这是我的keyboard_key_preview.xml ...

<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:background="@color/keyboard_preview_bg"
    android:textColor="@color/keyboard_preview_fg"
    android:textStyle="bold"
    android:textSize="@dimen/keyboard_preview_text_size" />

这是我的keyboard_numeric.xml布局...

<Keyboard
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:keyWidth="33.33%p"
    android:keyHeight="@dimen/keyboard_key_height"
    android:horizontalGap="@dimen/keyboard_horizontal_gap"
    android:verticalGap="@dimen/keyboard_vertical_gap">
    <Row android:rowEdgeFlags="top">
        <Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left"/>
        <Key android:codes="50" android:keyLabel="2"/>
        <Key android:codes="51" android:keyLabel="3" android:keyEdgeFlags="right"/>
    </Row>
    <Row>
        <Key android:codes="52" android:keyLabel="4" android:keyEdgeFlags="left"/>
        <Key android:codes="53" android:keyLabel="5"/>
        <Key android:codes="54" android:keyLabel="6" android:keyEdgeFlags="right"/>
    </Row>
    <Row>
        <Key android:codes="55" android:keyLabel="7" android:keyEdgeFlags="left"/>
        <Key android:codes="56" android:keyLabel="8"/>
        <Key android:codes="57" android:keyLabel="9" android:keyEdgeFlags="right"/>
    </Row>
    <Row android:rowEdgeFlags="bottom">
        <Key android:codes="-5" android:keyIcon="@drawable/ic_backspace_white_24dp" android:isRepeatable="true" android:keyEdgeFlags="left" />
        <Key android:codes="48" android:keyLabel="0"/>
        <Key android:codes="-4" android:keyLabel="DONE" android:keyEdgeFlags="right"/>
    </Row>
</Keyboard>
4

3 回答 3

9

预览键实际上是PopupWindow在构造函数中为KeyboardView. (请参阅此处的代码)。

您看到的问题是因为PopupWindow它的父级正在剪辑。如果可以禁用剪辑,PopupWindow则预览键将能够“弹出”到键盘视图之外。如果您在上面引用的代码处设置断点并执行以下代码,您可以看到这种方法有效:

mPreviewPopup.setClippingEnabled(false)

请参阅文档中的setClippingEnabled以获取PopupWindow. 默认情况下,剪辑是启用的。

setClippingEnabled

void setClippingEnabled(启用布尔值)

允许弹出窗口超出屏幕边界。默认情况下,窗口被裁剪到屏幕边界。将此设置为 false 将允许准确定位窗口。

它说“屏幕”,但它也适用于键盘窗口。

剩下的问题是:如何禁用剪辑?不幸的是,mPreviewPopup是一个私有变量。反射将提供访问权限,但并不理想。我还没有找到另一种禁用此 private 剪辑的方法PopupWindow,因此这只是指向解决方案而不是解决方案本身的方法。


更新:我又看看发生了什么。这是我发现的各种 API 级别。

API 17-21:允许键预览弹出键盘边界之外。

API 22、23、25-26:按键预览受限于键盘的边界,但会完整显示。这就是OP所看到的。

API 24:键预览被限制在键盘的边界,但会被剪裁。(破碎的)

因此,API 22 发生了变化。我在 API 21 和 API 22 之间看到的唯一实质性区别是对FLAG_LAYOUT_ATTACHED_IN_DECOR标志的支持。(另见setAttachedInDecor。)这段代码确实处理了弹出窗口的位置,所以它可能与这个问题有关。(我不再相信这是真的。)

我也发现也可能是相关的。(也许...)

setClippingEnabled()在 API 22-26 上工作以允许预览键弹出键盘布局的边界之外。除了使用反射之外,我看不到解决问题的方法。

尽管新的受约束行为可能是预期的行为,但这可能有资格获得错误报告。

这是展示问题的 API 26 的视频,然后我将mClippingEnabled标志设置为falseinPopupWindow.java并显示更正的行为。

在此处输入图像描述

于 2018-04-05T23:24:40.310 回答
4

对于将来来这里的任何人,我建议不要使用KeyboardView. 至少在撰写本文时(API 27),它已经很长时间没有更新了。上述问题中描述的问题只是其众多缺点之一。

这是更多的工作,但您可以制作自己的自定义视图以用作键盘。我在这个答案的最后描述了这个过程。

当您在自定义键盘中创建弹出预览窗口时,您需要调用popupWindow.setClippingEnabled(false)以使弹出窗口显示在 Android API 22+ 的键盘上方。(感谢这个答案的洞察力。)

这是我最近的一个项目中的一个例子。

private void layoutAndShowPopupWindow(Key key, int xPosition) {
    popupWindow = new PopupWindow(popupView,
            LinearLayout.LayoutParams.WRAP_CONTENT,
            LinearLayout.LayoutParams.WRAP_CONTENT);
    popupWindow.setClippingEnabled(false);// <-- let popup display above keyboard
    int location[] = new int[2];
    key.getLocationInWindow(location);
    int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    popupView.measure(measureSpec, measureSpec);
    int popupWidth = popupView.getMeasuredWidth();
    int spaceAboveKey = key.getHeight() / 4;
    int x = xPosition - popupWidth / popupView.getChildCount() / 2;
    int y = location[1] - popupView.getMeasuredHeight() - spaceAboveKey;
    popupWindow.showAtLocation(key, Gravity.NO_GRAVITY, x, y);

    // using popupWindow.showAsDropDown(key, 0, yOffset) might be
    // easier than popupWindow.showAtLocation() 
}
于 2018-04-09T04:39:08.723 回答
1

在这个问题上花了几个小时后,我终于找到了一个我觉得相当不成熟的解决方案。在您的自定义InputMethodService中,维护对您的 KeyboardView 的引用mKeyboardView,然后覆盖onStartInputView()。然后,抓取 KeyboardView 的父级,对其顶部边界应用一些填充,最后,setPopupParent()使用修改后的父级布局调用 KeyboardView。这是我的代码:

@Override
public void onStartInputView(EditorInfo info, boolean restarting){
    ViewGroup originalParent = (ViewGroup) mKeyboardView.getParent();
    if (originalParent != null) {
        originalParent.setPadding(0,LAYOUT_PADDING, 0, 0);
        mKeyboardView.setPopupParent(originalParent);
    }
}

您可以根据存在此问题的 API 过滤此覆盖方法的行为。

感谢唐克的提及setPopupParent()。如果需要,您可以尝试覆盖不同的方法并使用相同的逻辑,但是,您不能这样做,onCreateInputView()因为尚未创建 KeyboardView 的父级。

于 2018-04-09T02:06:16.590 回答