图像键盘支持

用户通常希望使用表情符号、贴纸和其他类型的富媒体进行交流。在以前的 Android 版本中,软键盘(也称为 输入法编辑器 或 IME)只能将 Unicode 表情符号发送到应用。对于富媒体,应用会构建特定于应用的 API,这些 API 不能在其他应用中使用,或者使用一些变通方法,例如通过 简单共享操作 或剪贴板发送图像。

An image showing a keyboard that support image search
图 1. 图像键盘支持示例。

从 Android 7.1(API 级别 25)开始,Android SDK 包含提交内容 API,该 API 为 IME 提供了一种通用方法,可以将图像和其他富媒体直接发送到应用中的文本编辑器。该 API 也包含在 v13 支持库(从修订版 25.0.0 开始)中。我们建议使用支持库,因为它包含简化实现的帮助器方法。

使用此 API,您可以构建接受来自任何键盘的富媒体的短信应用,以及可以将富媒体发送到任何应用的键盘。例如,Google 键盘Google 消息 等应用在 Android 7.1 中支持提交内容 API,如图 1 所示。

本文档说明了如何在 IME 和应用中实现提交内容 API。

工作原理

键盘图像插入需要 IME 和应用共同参与。以下序列描述了图像插入过程中的每个步骤

  1. 当用户点击 EditText 时,编辑器会发送一个它接受的 MIME 内容类型列表,位于 EditorInfo.contentMimeTypes 中。

  2. IME 会读取支持的类型列表,并在软键盘中显示编辑器可以接受的内容。

  3. 当用户选择图像时,IME 会调用 commitContent() 并向编辑器发送一个 InputContentInfo。调用 commitContent() 与调用 commitText() 相似,但用于富媒体。 InputContentInfo 包含一个 URI,该 URI 在 内容提供程序 中标识内容。

此过程如图 2 所示

An image showing the sequence from Application to IME and back to Application
图 2. 应用到 IME 到应用流程。

向应用添加图像支持

要从 IME 接受富媒体,应用必须告知 IME 它接受什么内容类型,并指定一个在收到内容时执行的回调方法。以下示例演示如何创建一个接受 PNG 图像的 EditText

Kotlin

var editText: EditText = object : EditText(this) {
    override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
        var ic = super.onCreateInputConnection(outAttrs)
        EditorInfoCompat.setContentMimeTypes(outAttrs, arrayOf("image/png"))
        val mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this)
        if (mimeTypes != null) {
            EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes)
            ic = InputConnectionCompat.createWrapper(this, ic, outAttrs)
        }
        return ic
    }
}

Java

EditText editText = new EditText(this) {
    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        InputConnection ic = super.onCreateInputConnection(outAttrs);
        EditorInfoCompat.setContentMimeTypes(outAttrs, new String[]{"image/png"});
        String[] mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this);
        if (mimeTypes != null) {
            EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes);
            ic = InputConnectionCompat.createWrapper(this, ic, outAttrs);
        }
        return ic;
    }
};

以下是对其的进一步解释

以下是一些建议做法

  • 不支持富媒体的编辑器不会调用 setContentMimeTypes(),并且它们会将自己的 EditorInfo.contentMimeTypes 设置为 null

  • 如果 InputContentInfo 中指定的 MIME 类型与它们接受的任何类型都不匹配,则编辑器会忽略该内容。

  • 富媒体不会影响文本光标的位置,反之亦然。编辑器在处理内容时可以忽略光标位置。

  • 在编辑器的 OnCommitContentListener.onCommitContent() 方法中,您可以返回 true,即使在加载内容之前也可以异步返回。

  • 与可以在提交之前在 IME 中编辑的文本不同,富媒体会立即提交。如果要让用户编辑或删除内容,请自行实现逻辑。

要测试您的应用,请确保您的设备或模拟器具有可以发送富媒体的键盘。您可以在 Android 7.1 或更高版本中使用 Google 键盘。

向 IME 添加图像支持

要将富媒体发送到应用,IME 必须实现提交内容 API,如下例所示

  • 覆盖 onStartInput()onStartInputView() 并从目标编辑器读取支持的内容类型列表。以下代码片段显示了如何检查目标编辑器是否接受 GIF 图像。

Kotlin

override fun onStartInputView(editorInfo: EditorInfo, restarting: Boolean) {
    val mimeTypes: Array<String> = EditorInfoCompat.getContentMimeTypes(editorInfo)

    val gifSupported: Boolean = mimeTypes.any {
        ClipDescription.compareMimeTypes(it, "image/gif")
    }

    if (gifSupported) {
        // The target editor supports GIFs. Enable the corresponding content.
    } else {
        // The target editor doesn't support GIFs. Disable the corresponding
        // content.
    }
}

Java

@Override
public void onStartInputView(EditorInfo info, boolean restarting) {
    String[] mimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo);

    boolean gifSupported = false;
    for (String mimeType : mimeTypes) {
        if (ClipDescription.compareMimeTypes(mimeType, "image/gif")) {
            gifSupported = true;
        }
    }

    if (gifSupported) {
        // The target editor supports GIFs. Enable the corresponding content.
    } else {
        // The target editor doesn't support GIFs. Disable the corresponding
        // content.
    }
}

  • 当用户选择图像时,将内容提交到应用。在存在任何正在撰写的文本时,避免调用 commitContent(),因为它可能会导致编辑器失去焦点。以下代码片段显示了如何提交 GIF 图像。

Kotlin

// Commits a GIF image.

// @param contentUri = Content URI of the GIF image to be sent.
// @param imageDescription = Description of the GIF image to be sent.

fun commitGifImage(contentUri: Uri, imageDescription: String) {
    val inputContentInfo = InputContentInfoCompat(
            contentUri,
            ClipDescription(imageDescription, arrayOf("image/gif")),
            null
    )
    val inputConnection = currentInputConnection
    val editorInfo = currentInputEditorInfo
    var flags = 0
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
        flags = flags or InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION
    }
    InputConnectionCompat.commitContent(inputConnection, editorInfo, inputContentInfo, flags, null)
}

Java

// Commits a GIF image.

// @param contentUri = Content URI of the GIF image to be sent.
// @param imageDescription = Description of the GIF image to be sent.

public static void commitGifImage(Uri contentUri, String imageDescription) {
    InputContentInfoCompat inputContentInfo = new InputContentInfoCompat(
            contentUri,
            new ClipDescription(imageDescription, new String[]{"image/gif"}),
            null
    );
    InputConnection inputConnection = getCurrentInputConnection();
    EditorInfo editorInfo = getCurrentInputEditorInfo();
    Int flags = 0;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
        flags |= InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
    }
    InputConnectionCompat.commitContent(
            inputConnection, editorInfo, inputContentInfo, flags, null);
}

作为 IME 作者,您很可能需要实现自己的内容提供程序才能响应内容 URI 请求。唯一例外是,如果您的 IME 支持来自现有内容提供程序(如 MediaStore)的内容。有关构建内容提供程序的信息,请参阅 内容提供程序文件提供程序 文档。

如果您正在构建自己的内容提供程序,我们建议您通过将 android:exported 设置为 false 来避免导出它。相反,通过将 android:grantUriPermission 设置为 true 来启用提供程序中的权限授予。然后,您的 IME 可以授予访问内容 URI 的权限(在内容提交时)。您可以通过两种方式执行此操作

  • 在 Android 7.1(API 级别 25)及更高版本上,在调用 commitContent() 时,将标志参数设置为 INPUT_CONTENT_GRANT_READ_URI_PERMISSION。然后,应用接收到的 InputContentInfo 对象可以通过调用 requestPermission()releasePermission() 来请求和释放临时读取权限。

  • 在 Android 7.0(API 级别 24)及更低版本上,INPUT_CONTENT_GRANT_READ_URI_PERMISSION 将被忽略,因此请手动授予对内容的权限。一种方法是使用 grantUriPermission(),但您可以实施满足您自己要求的机制。

要测试您的 IME,请确保您的设备或模拟器具有可以接收富媒体的应用。您可以在 Android 7.1 或更高版本中使用 Google Messenger 应用。