Help us understand the problem. What is going on with this article?

WebViewでページ内検索

More than 3 years have passed since last update.

先日、Qiitappにページ内検索機能を追加しました。

要望はあったのですが、QiitappはAPIから取得したHTMLをローカルに保存して表示するため、Chrome Custom Tabsは使えません。また、WebViewでのページ内検索はChrome Custom TabsのようなUIにはできないだろう、と勝手に思い込んでいて(あまり調べてなかった)、対応に消極的でした。

やってみたところ、割と簡単にまぁまぁなUIにできたので、一例として紹介します。

スクリーンショット

Chrome Custom Tabs版

ss_chromecustomtabs.png 

WebView版(Qiitappとは異なります)

ss_webview.png

見ての通り、Chrome Custom Tabs版とWebView版の違いは、スクロールバー部分とActionBar部分です。残念ながら、スクロールバー部分はできていないため、この先出てきません。

環境

  • Android 5.0(APIレベル21)以上
  • Support Library
    • appcompat-v7 26.1.0
    • customtabs 26.1.0

もう少し低くても動くと思います。

WebView版の実装(XML)

ActionBar部分は以下のように分けられます。
3.png

0.全体

Contextual Action Barを使用しています。
Contextual Action Barの背景をChrome Custom Tabsと同じ白色にするため、styles.xmlactionModebackgroundを追加します。

values/styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    ...
    <item name="actionModeBackground">@android:color/white</item>
    ...
</style>

1.actionModeCloseButtonStyle

Contextual Action Bar左部の閉じるボタンアイコンはデフォルトで白色のため、上記0の対応の結果見えなくなります。そのため、Chrome Custom Tabsと合わせてグレーにします(値は適当)。
styles.xmlactionModeCloseButtonStyleにtintを指定して色を変更します。

values/styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    ...
    <item name="actionModeCloseButtonStyle">@style/ActionModeCloseMode</item>
    ...
</style>

<style name="ActionModeCloseMode" parent="Widget.AppCompat.Light.ActionButton.CloseMode">
    <item name="android:tint">#9E9E9E</item>
</style>

2.ActionMode#setCustomView

検索文字列入力用のEditTextとマッチ数表示用のTextViewLinearLayoutで囲んでActionMode#setCustomViewに指定します(ここではそのレイアウトのみ)。

layout/activity_web_view_cab.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <EditText
        android:id="@+id/query"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:hint="@string/find_in_page"
        android:background="@android:color/transparent"
        android:theme="@style/FindInPageEditText" />

    <TextView
        android:id="@+id/count"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" />
</LinearLayout>
values/styles.xml
<style name="FindInPageEditText">
    <item name="android:maxLines">1</item>
    <item name="android:inputType">text</item>
    <item name="android:imeOptions">actionSearch</item>
    <item name="android:colorControlActivated">@color/colorCursor</item>
    <item name="android:textCursorDrawable">@drawable/cursor</item>
</style>
values/colors.xml
<resources>
    ...
    <color name="colorCursor">#4285F4</color>
    ...
</resources>
drawable/cursor.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <size android:width="2dp" />
    <solid android:color="@color/colorCursor" />
</shape>

EditTextのスタイル設定は以下を参考にしました。

3.OptionsMenu

Contextual Action Bar右部の前後の検索結果への移動ボタンはOptionsMenuにAndroidのMaterial iconを指定しています(上記1と同じ色を指定)。

menu/activity_web_view_find_in_page.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/previous"
        android:icon="@drawable/ic_keyboard_arrow_up"
        android:title="@string/previous" />
    <item
        android:id="@+id/next"
        android:icon="@drawable/ic_keyboard_arrow_down"
        android:title="@string/next" />
</menu>
drawable/ic_keyboard_arrow_up.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportHeight="24.0"
    android:viewportWidth="24.0">
    <path
        android:fillColor="#FF9E9E9E"
        android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z" />
</vector>

drawable/ic_keyboard_arrow_down.xmlは省略。

WebView版の実装(Java)

WebViewの検索関連のAPIは、WebView#findAllAsync, WebView#findNext, WebView#clearMatchesの3つ。名前からわかりますが、それぞれ、全検索、次検索、強調表示の解除です。
全検索は非同期(同期版はAPIレベル16でdeprecated)となっており、検索結果(総数、現在のフォーカス位置)を受け取りたい場合はWebView.FindListenerを登録する必要があります。
あとはこれらをActionModeTextWatcherなどのイベントに合わせて呼ぶだけです。

Activity#onCreateなど
mWebView.setFindListener(this);  // FindListenerの設定
ActionMode.Callback
// TextWatcherの指定
// TextView. OnEditorActionListenerの指定
// カスタムビューの作成・ActionMode#setCustomViewの呼び出し
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    mActionMode = mode;

    getMenuInflater().inflate(R.menu.activity_web_view_find_in_page, menu);

    final View view = getLayoutInflater().inflate(R.layout.acitivity_web_view_cab, null);
    mQueryView = view.findViewById(R.id.query);
    mCountView = view.findViewById(R.id.count);
    mDefaultTextColor = mCountView.getCurrentTextColor();
    mQueryView.addTextChangedListener(this);
    mQueryView.setOnEditorActionListener(this);

    mode.setCustomView(view);
    return true;
}

// 前方検索・後方検索
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    switch (item.getItemId()) {
        case R.id.previous:
            mWebView.findNext(false);
            return true;
        case R.id.next:
            mWebView.findNext(true);
            return true;
        default:
            return false;
    }
}

// ActionMode終了による強調表示の解除
@Override
public void onDestroyActionMode(ActionMode mode) {
    mWebView.clearMatches();

    mActionMode = null;
}
TextWatcher
// 検索(検索文字列未指定の場合は検索結果数用のViewを表示しない)
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
    mWebView.findAllAsync(s.toString());
    mCountView.setVisibility(count != 0 ? View.VISIBLE : View.GONE);
}
TextView.OnEditorActionListener
// キーボードのSearchボタン押下時の前方検索
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
    if (actionId == EditorInfo.IME_ACTION_SEARCH) {
        mWebView.findNext(true);
        return true;
    }

    return false;
}
WebView.FindListener
// 検索終了時に <現在の位置(0起算)>/<マッチ数> を表示
// マッチ数が0の場合は赤色表示
@Override
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
    if (isDoneCounting) {
        mCountView.setTextColor(numberOfMatches != 0 ? mDefaultTextColor : Color.RED);
        mCountView.setText(getString(R.string.count, numberOfMatches == 0 ? activeMatchOrdinal : activeMatchOrdinal + 1, numberOfMatches));
    }
}

ソース

GitHubにあります。

alzybaad
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away