【翻译】TextClassification介绍(三)

TextClassification介绍(一)

一、说明

这是一个关于介绍 TextClassification API 的系列文章,总共分三篇,本文是最后一篇。上一篇在此:【翻译】TextClassification介绍(二)

原文作者:Mark Allison
阅读时间: 5 分钟
原文链接:https://blog.stylingandroid.com/textclassification-part-3/

二、正文

在 API 26 (奥利奥)中安卓引入了一个新的文字功能系统: TextClassification 。这个系统将会在 API 28 ( π )中进一步改进完善。在本次简短的系列中,我们主要会探讨它是一个什么样的系统,如何使用它,以及如何为它添加一些自定义行为。

上一篇文章中我们开始研究自定义 TextClassifier 的实现,并研究了如何实现我们自己的“文本选择建议”。在本系列的最后一篇文章中,我们将会实现相应的 classifyText() 方法,并应用到我们自定义的 TextClassifier 文本分类器的实现中。

这里的 classifyText() 方法的实现实际上非常简单直接。我们检查选择的文本是否与请求中所选择的相匹配,如果相匹配则返回一个 TextClassification 的实例,这个我们稍后再详细了解。如果选择的文本与我们的正则表达式不匹配,那么我们会将结果进行回滚(也就是 TextClassifier 的默认实现):

override fun classifyText(request: TextClassification.Request): TextClassification {
    //line 36
    return if (regex.matches(request.subSequence())) {
        factory.buildTextClassification(
                //line 38
                request.subSequence().toString(),
                listOf(TextClassifier.TYPE_URL to 1.0f),
                listOf(factory.buildRemoteAction(
                        context,
                        R.drawable.ic_stylingandroid,
                        stylingAndroid,
                        contentDescription,
                        stylingAndroidUri
                ))
        )
    } else {
        fallback.classifyText(request)
    }
}

private fun TextClassification.Request.subSequence() =
        text.subSequence(startIndex, endIndex)

这里有一个 TextClassification.Request 的扩展函数,它返回文本中已选择的子文本 subSequence ,我们正是使用它来匹配正则表达式(第 36 行)。然后,我们构建一个 TextClassification 对象,它需要一些参数,包括匹配到的字符串(第 38 行),分类类型与其各自的可信度分数列表(第 39 行),以及一列 RemoteAction 实例对象,每个 RemoteAction 对象对应一个分类类型条目(第 39-45 行)。

这里的 buildRemoteAction() 方法返回每个 RemoteAction 实例:

override fun buildRemoteAction(
        context: Context,
        drawableId: Int,
        title: String,
        contentDescription: String,
        uri: String
): RemoteAction {
    return RemoteAction(
            Icon.createWithResource(context, drawableId),
            title,
            contentDescription,
            PendingIntent.getActivity(
                    context,
                    0,
                    Intent(Intent.ACTION_VIEW, Uri.parse(uri)),
                    0
            )
    )
}

这里 RemoteAction 的构造函数需要四个参数:一个 Icon 对象,它将显示为操作按钮中的一部分;一个要显示的文字;一个表示可访问的内容描述;最后还需一个 PendingIntent 对象,它表示用户在点击操作按钮时将会执行的操作。在我们的示例代码中,我们使用了一个 Styling Android 标志图形作为图标,使用了 “Styling Android” 作为标题,以及简单的一些内容描述,再加一个能登录浏览器打开 URL 链接 "https://blog.stylingandroid.com" 的 PendingIntent 对象。

buildTextClassification() 函数使用 TextClassification.Builder 实例来创建 TextClassification 对象:

override fun buildTextClassification(
        text: String,
        entityTypes: List<Pair<String, Float>>,
        actions: List<RemoteAction>
): TextClassification {
    return TextClassification.Builder()
            .run {
                setText(text)
                entityTypes.forEach { setEntityType(it.first, it.second) }
                actions.forEach { addAction(it) }
                build()
            }
}

这里需要传递三个参数:匹配到的文本,一系列以类型及其对应的可信度范围作为 Pair 的列表,以及与每个类型/置信度组合所对应的 RemoteActions 列表。在这里示例中,这些参数分别是被选定的子字符串,包含一对 TextClassifier.TYPE_URL 及其可信度为 1.0f 组成的 Pair 的列表,以及一列包含我们刚刚说过的 RemoteAction 类单例的列表。

TextClassifier 已经完成了,剩下的事情就是将它连接到我们的 TextView 文本控件中:

class MainActivity : AppCompatActivity() {

    private val emailText = "dummy@email.com"
    private val urlText = "https://blog.stylingandroid.com"
    private val hybridText = "Email: $emailText"

    private lateinit var textClassificationManager: TextClassificationManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        textClassificationManager = getSystemService(Context.TEXT_CLASSIFICATION_SERVICE) as TextClassificationManager

        classifier()

        text1.textClassifier = StylingAndroidTextClassifier(this, textClassificationManager.textClassifier)
    }

    private fun classifier() = async(CommonPool) {
        val textClassifier = textClassificationManager.textClassifier
        val emailClassification = textClassifier.classifyText(emailText, 0, emailText.length, LocaleList.getDefault())
        println(emailClassification)
        val urlClassification = textClassifier.classifyText(urlText, 0, urlText.length, LocaleList.getDefault())
        println(urlClassification)
        val suggestions = textClassifier.suggestSelection(hybridText, 10, 11, LocaleList.getDefault())
        println(suggestions)
    }
}

一行代码就可以实现我们要做的一切。我们创建了一个自定义 TextClassifier 对象,并将其作为参数传递给默认的 TextClassifier 对象的构造函数,并将 TextViewtextClassifier 属性设置为我们的自定义实例。

我们目前得到的文本处理行为是:如果用户长时间按下的文字是无法识别的类型,那就只有简单的复制,粘贴和选择所有的选项功能;如果他们长时间按下系统默认的文本分类器所支持的任何类型,那么他们会得到我们在第一篇文章中所看到的所有相同的行为和动作;但是,如果他们长按 "Styling Android" 字符串(或者与正则表达式相匹配的类似字符串),那么将获得我们所自定义的“流行广告”操作,这个操作将启动浏览器并登录加载链接: https://blog.stylingandroid.com

custom.gif

TextClassifier 文本分类器中还有一个机制,用来识别具体的文本类型,然后使用文本生成链接,不过在本系列文章中我们不会去研究它,因为我们这里所看到的技术已经涵盖了 TextViewWebView 内置的一些主要功能。如果看到这里有人对此感兴趣,那么请告诉我,我将会单独写一篇文章作详细说明。

三、总结

这篇文章的源代码可以在这里找到: https://github.com/StylingAndroid/TextClassification/tree/Part3

© 2018 , Mark Allison 。保留所有版权。

我的博客地址: http://liuqingwen.me ,欢迎关注我的微信公众号:

IT自学不成才

推荐阅读更多精彩内容