使用ACTION_PROCESS_TEXT来创建自定义的文本选择操作

英文原文:Creating custom Text Selection actions with ACTION_PROCESS_TEXT 

Android 6.0 Marshmallow中介绍了一个新的浮动文字选择工具栏, 它带有标准的文字选择操作,比如剪切,复制,与粘贴,这些操作紧挨着被选中的文字。更厉害的是新的ACTION_PROCESS_TEXT,它让任何app都能向文字选择工具栏中添加自定义操作。

1-D4zZzPlBTk5cEN9Qn0-cBA.gif

诸如 Wikipedia 和 Google Translate 这样的app都已经利用它来实现选中文字的即时查找和翻译。

你可能已经看过关于在app中获得文字选择工具栏和选项的文档 以及 博客 (简单的说来就是:使用一个标准的TextView或者EditText就可以了,但是要注意,如果你是使用的AppCompatActivity 且想在 API 23+的设备上使用原生的文字选择工具栏,你的EditText应该有一个android:id并且需要调用getDelegate().setHandleNativeActionModesEnabled(false) )。

但是如果你要寻找关于实现ACTION_PROCESS_TEXT以及添加自己的操作的信息,那么本文就是了。

跨app间通信 -> Intent Filters

正如你可能已经想到的,在建立app间的功能时,附加在每个组建上的Android Manifest 和 intent filters 担任着一个可以被其它app查询的公共API角色。

ACTION_PROCESS_TEXT也是如此。你将在manifest中的一个Activity 上添加一个 intent filter:

<activity android:name=”@string/process_text_action_name”>
  <intent-filter>
    <action android:name=”android.intent.action.PROCESS_TEXT”/
    <category android:name=”android.intent.category.DEFAULT” />
    <data android:mimeType=”text/plain” />
  </intent-filter>
</activity>

如果你想要多个操作,你需要为每个操作单独建立activity。如果你想建议专属于你自己的app的功能,不想被包含在其它app中,可以使用android:exported=”false”来确保这个操作只出现在你的app中。

注意Activity的android:name会作为文字选择工具栏的操作名来显示,所以确保它是简短的,动词化的,可以一眼就能识别出是你的app的。比如,谷歌翻译使用 ‘Translate’ 是因为翻译是一个不常用的操作(有多少人会安装多个翻译app?),而维基百科使用 ‘Search Wikipedia’ 是因为搜索对许多app来说是一个更常用的操作。

获取选中的文字

一旦你设置好了 intent filter ,其它的app就能通过选中文字并从文字选择工具栏中选择你的操作来启动你的activity。但是如果你并没有看到被选中的文字,其实这也没有什么卵用。

这就是 EXTRA_PROCESS_TEXT 的用处了。它是一个包含在代表被选中文字的intent中的 CharSequence 。-别被误导了,即便你使用的是一个text/plain intent filter,你也会得到带有一些 Spannable的CharSequence,所以如果你在app中是直接使用的CharSequence并发现一些样式,不要感到惊讶(你总是可以调用toString()来去掉所有格式)。

所以,你的onCreate()方法看起来可能就像这样:

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.process_text_main);
  CharSequence text = getIntent()
      .getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
  // process the text
}

给一个建议:如果你使用了 android:launchMode=”singleTop”,那么你应该也想在 onNewIntent() 中处理文字-一个比较好的做法是在 onCreate() 和onNewIntent() 中都调用一个你创建的handleIntent()方法。

以上就是你想使用 ACTION_PROCESS_TEXT 作为你app入口的全部信息:那么当进入你的入口之后,你该怎么做呢?

返回一个结果

在ACTION_PROCESS_TEXT Intent中还有另外一个extra :EXTRA_PROCESS_TEXT_READONLY。这个boolean extra指示你刚刚接收到的选中文字是否可以被用户编辑(比如在EditText中的情况)。

用下面的代码接收这个extra :

boolean readonly = getIntent()
  .getBooleanExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false);

你可以用它来为发送app提供替换选中文字的功能,这是因为你的Activity 实际上是 startActivityForResult() 来启动的-你可以在Activity 结束之前的任何时间调用 setResult() 来 返回一个结果 。

Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_PROCESS_TEXT, replacementText);
setResult(RESULT_OK, intent);

你可以想象使用一个 ‘Replace’ 这样的按钮去调用 setResult(),然后调用finish() 来返回调用者Activity。

常见问题

在你写回复之前,这里是一些关于ACTION_PROCESS_TEXT的常见问题:

问题: 我可以触发带有ACTION_PROCESS_TEXT的Service 吗?

答案:不能直接这样做-系统只会查询包含了正确intent filter的Activity。但是这并不意味着你不能使用 Theme.Translucent.NoTitleBar 甚至 Theme.NoDisplay (只要你立即结束这个Activity)主题Activity启动一个Service ,不过你要确保有对用户指明操作已收到的暗示。 -比如一个通知或者Toast等。

问题: 可以只针对某种类型的text才触发吗?

答案:不能。任何人每次选择了文字你的选项都将出现。当然,一般情况下除非用户想翻译不然他不会选择 ‘Translate’ 选项。但是写代码的时候还是小心防范,因为你不能确定你到底会接收到何种类型的 text context。

问题: 每个app都应该实现ACTION_PROCESS_TEXT吗?那不是让人疯了?

答案:是的,那样确实让人抓狂,但是并不是每个app都应该实现ACTION_PROCESS_TEXT。要确保你的任何操作对于安装了你app的用户都是普遍和真正有用的。

学习更多

除了前面提到的 Wikipedia 和Google Translate 包含了真正的例子外,你还可以查看安装在Marshmallow 模拟器上的ApiDemos app,或者直接 看其代码 。

#BuildBetterApps

参加 Google+ post 上的讨论并关注 Android Development Patterns Collection 获得更多内容!

blob.png