图片键盘支持

用户通常希望使用表情符号、贴纸和其他类型的富媒体内容进行通信。在以前的 Android 版本中,软键盘(也称为输入法,即 IME)只能向应用发送 Unicode 表情符号。对于富媒体内容,应用构建的应用专用 API 无法在其他应用中使用,也不支持通过简单分享操作或剪贴板发送图片等解决方法。

图片中显示的是支持图片搜索的键盘
图 1. 图片键盘支持示例。

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

借助此 API,您可以构建能够接受来自任何键盘以及可向任何应用发送富媒体内容的键盘的富媒体内容的即时通讯应用。Google 键盘Google 信息等应用支持 Android 7.1 中的 Commit Content API,如图 1 所示。

本文档介绍了如何在 IME 和应用中实现 Commit Content API。

了解运作方式

键盘图片插入需要 IME 和应用的参与。以下序列描述了图片插入流程中的各个步骤:

  1. 当用户点按 EditText 时,编辑器会发送其在 EditorInfo.contentMimeTypes 中接受的 MIME 内容类型列表。

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

  3. 当用户选择一张图片时,IME 会调用 commitContent() 并向编辑器发送 InputContentInfocommitContent() 调用类似于 commitText() 调用,但适用于富媒体内容。InputContentInfo 包含用于标识 content provider 中的内容的 URI。

此过程如图 2 所示:

图片显示从“应用”到“IME”再到“应用”的顺序
图 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 必须实现 Commit Content 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 的作者,您很可能需要实现自己的 content provider,才能响应内容 URI 请求。例外情况是,如果您的 IME 支持来自 MediaStore 等现有 content provider 的内容,则属于例外情况。如需了解如何构建 content provider,请参阅 content provider文件提供程序文档。

如果您正在构建自己的 content provider,我们建议您不要通过将 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 应用。