アプリ内翻訳機能がついたTextViewをザクッと作ってみる話


Marshmallowに[Google翻訳](https://play.google.com/store/apps/details?id=com.google.android.apps.translate)アプリを入れておけば、[テキスト選択機能](http://developer.android.com/about/versions/marshmallow/android-6.0-changes.html?utm_campaign=android_update_translations_in_m_100615&utm_source=anddev&utm_medium=blog#behavior-text-selection)で、アプリ内翻訳ができるよという[記事](http://googledevjp.blogspot.jp/2015/11/android-marshmallow.html)があったので、何もしなくてもサクッと使えるのかと思ったら、そうでもなかったので、翻訳機能付きのTextViewをザクッと作ってみました（厳密には翻訳専用というわけじゃないんだけど）。

## とりあえず、ソース

```java
public class TranslatableTextView extends TextView {

    public TranslatableTextView(Context context) {
        super(context);
        init();
    }

    public TranslatableTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public TranslatableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 共通した初期化処理
     */
    private void init() {
        //  Marshmallow以降のときだけはたらく
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //  強制的にセレクタブル
            setTextIsSelectable(true);
            //  カスタムアクションを設定
            setCustomSelectionActionModeCallback(new TranslateCallback());
        }
    }

    /**
     * @return 選択されている文字列
     */
    CharSequence selectedText() {
        int start = getSelectionStart();
        int end = getSelectionEnd();
        return getText().subSequence(start, end);
    }

    @TargetApi(Build.VERSION_CODES.M)
    private class TranslateCallback extends ActionMode.Callback2 {

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            if (getSupportedActivities().size() > 0) {
                //  本当の翻訳だけなのかというのは議論の余地がある
                menu.add("翻訳");
                return true;
            } else {
                return false;
            }
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return false;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            if (item.getTitle().equals(TRANSLATE)) {
                //  ACTION_PROCESS_TEXTなインテントを投げる
                getContext().startActivity(createProcessTextIntent(selectedText()));
                //  ActionMode終わり
                mode.finish();
                return true;
            } else {
                return false;
            }
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {}

        /**
         * ACTION_PROCESS_TEXTなインテント
         *
         * @param text  nullで無ければ、processの対象
         * @return  インテント
         */
        private Intent createProcessTextIntent(@Nullable CharSequence text) {
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_PROCESS_TEXT);
            intent.setType("text/plain");

            if (text != null) {
                intent.putExtra(Intent.EXTRA_PROCESS_TEXT, text);
                intent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false);
            }

            return intent;
        }

        /**
         * ACTION_PROCESS_TEXTに反応するアクティビティのリストを取得するメソッド
         * ここでは、その数しか使用してないけど
         *
         * @return  ACTION_PROCESS_TEXTに反応するアクティビティのリスト
         */
        private List<ResolveInfo> getSupportedActivities() {
            PackageManager manager = getContext().getPackageManager();
            return  manager.queryIntentActivities(createProcessTextIntent(null), 0);
        }
    }
}
```

Marshmallowで追加されたActionMode.Callback2から、派生クラスを作って、そこでやりたいことをカスタマイズします。そのインスタンスをsetCustomSelectionActionModeCallback()で渡してやれば、カスタマイズされたコンテキスト・メニューが表示されます。

メニューがタップされたら、選択されている文字列をExtraに格納したACTION_PROCESS_TEXTなインテントを投げています。ACTION_PROCESS_TEXTは、翻訳専用というアクションではないので、対応したアプリがインストールされている場合は、なにか別のことをされてしまうかもしれないので、本当ならResolveInfoのリストを元にIntent#setClassName()でターゲットを絞るなどの工夫が必要です。



## とりあえず、試す

試してみました。

> ![device-2015-12-18-173945.png](https://qiita-image-store.s3.amazonaws.com/0/13886/3d0216b3-cad6-2069-7a2e-ed8da60b792d.png "device-2015-12-18-173945.png")

新規作成時にScrolling Activityを選択すると作られるアクティビティのTextViewをTranslatableTextViewに置き換えてあります。

> ![device-2015-12-18-174004.png](https://qiita-image-store.s3.amazonaws.com/0/13886/e72acc80-9c6d-aefd-8501-964a631316cf.png "device-2015-12-18-174004.png")

長押しして選択すると、メニューに「翻訳」が含まれています。

> ![device-2015-12-18-174022.png](https://qiita-image-store.s3.amazonaws.com/0/13886/fdeeb29f-1a82-9ca3-56d8-52dd5ffa108b.png "device-2015-12-18-174022.png")

はい、翻訳できました。
EXTRA_PROCESS_TEXT_READONLYでfalseを渡しているのに、「置換」があるのがいまいちです。

試した環境では、ACTION_PROCESS_TEXTに対応しているのがGoogle翻訳しかなかったので、素直に翻訳してくれました。

## まとめ

要点は、

* テキスト選択したときのコンテキスト・メニューの表示
* ACTION_PROCESS_TEXTなインテントを使ったアプリ間連携

の2点で、それぞれはさほど大変なことではないので、細かいことを考慮しなければザクッと作れます。

ACTION_PROCESS_TEXTが加わったことで、Androidらしいアプリ間連携の幅が広がったので、今後、対応アプリが増えてくることが期待できますね。