图片键盘支持

图 1. 图片键盘支持示例

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

在 Android 7.1(API 级别 25)中,Android SDK 包含 Commit Content API,它提供了一种通用方式,使 IME 可以将图片和其他富媒体内容直接发送到应用中的文本编辑器。从版本 25.0.0 开始,v13 支持库中也提供此 API。我们建议使用支持库,因为它在搭载 Android 3.2(API 级别 13)及更高版本的设备上运行,并且包含可简化实现的帮助程序方法。

借助此 API,您可以构建可接受来自任意键盘的富媒体内容的即时通讯应用,以及可向任何应用发送富媒体内容的键盘。Google 键盘Google Messenger 等应用在 Android 7.1 中支持 Commit Content API(参见图 1)。

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

工作原理

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

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

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

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

为应用添加图片支持

为了接受来自 IME 的富媒体内容,应用必须告知 IME 它所接受的内容类型,并指定在接收到内容时执行的回调方法。以下示例展示了如何创建接受 PNG 图片的 EditText

Kotlin

val editText = object : EditText(this) {

    override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection {
        val ic: InputConnection = super.onCreateInputConnection(editorInfo)
        EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/png"))

        val callback =
                InputConnectionCompat.OnCommitContentListener { inputContentInfo, flags, opts ->
                    val lacksPermission = (flags and
                            InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0
                    // read and display inputContentInfo asynchronously
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && lacksPermission) {
                        try {
                            inputContentInfo.requestPermission()
                        } catch (e: Exception) {
                            return@OnCommitContentListener false // return false if failed
                        }
                    }

                    // read and display inputContentInfo asynchronously.
                    // call inputContentInfo.releasePermission() as needed.

                    true  // return true if succeeded
                }
        return InputConnectionCompat.createWrapper(ic, editorInfo, callback)
    }
}

Java

EditText editText = new EditText(this) {
    @Override
    public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
        final InputConnection ic = super.onCreateInputConnection(editorInfo);
        EditorInfoCompat.setContentMimeTypes(editorInfo,
                new String [] {"image/png"});

        final InputConnectionCompat.OnCommitContentListener callback =
            new InputConnectionCompat.OnCommitContentListener() {
                @Override
                public boolean onCommitContent(InputContentInfoCompat inputContentInfo,
                        int flags, Bundle opts) {
                    // read and display inputContentInfo asynchronously
                    if (BuildCompat.isAtLeastNMR1() && (flags &
                        InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
                        try {
                            inputContentInfo.requestPermission();
                        }
                        catch (Exception e) {
                            return false; // return false if failed
                        }
                    }

                    // read and display inputContentInfo asynchronously.
                    // call inputContentInfo.releasePermission() as needed.

                    return true;  // return true if succeeded
                }
            };
        return InputConnectionCompat.createWrapper(ic, editorInfo, callback);
    }
};

此过程较为复杂,下面我们来详细说明一下。

下面是一些推荐做法:

  • 不支持富媒体内容的编辑器不应调用 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 corresponding content
    } else {
        // the target editor does not support GIFs. disable 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 corresponding content
    } else {
        // the target editor does not support GIFs. disable 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 ProviderFile Provider 文档。

  • 如果您要构建自己的 Content Provider,我们建议您不要将其导出(将 android:exported 设置为 false),而应将 android:grantUriPermission 设置为 true,以在 Content Provider 中启用权限授予功能。然后,IME 会在内容提交时授予访问内容 URI 的权限。您可以采用下列两种方法:

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