Posted at

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

More than 3 years have passed since last update.

MarshmallowにGoogle翻訳アプリを入れておけば、テキスト選択機能で、アプリ内翻訳ができるよという記事があったので、何もしなくてもサクッと使えるのかと思ったら、そうでもなかったので、翻訳機能付きのTextViewをザクッと作ってみました(厳密には翻訳専用というわけじゃないんだけど)。


とりあえず、ソース

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


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


device-2015-12-18-174004.png


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


device-2015-12-18-174022.png


はい、翻訳できました。

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

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


まとめ

要点は、


  • テキスト選択したときのコンテキスト・メニューの表示

  • ACTION_PROCESS_TEXTなインテントを使ったアプリ間連携

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

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