先日、Qiitappにページ内検索機能を追加しました。
要望はあったのですが、QiitappはAPIから取得したHTMLをローカルに保存して表示するため、Chrome Custom Tabsは使えません。また、WebView
でのページ内検索はChrome Custom Tabs
のようなUIにはできないだろう、と勝手に思い込んでいて(あまり調べてなかった)、対応に消極的でした。
やってみたところ、割と簡単にまぁまぁなUIにできたので、一例として紹介します。
スクリーンショット
Chrome Custom Tabs版
WebView版(Qiitappとは異なります)
見ての通り、Chrome Custom Tabs
版とWebView
版の違いは、スクロールバー部分とActionBar
部分です。残念ながら、スクロールバー部分はできていないため、この先出てきません。
環境
- Android 5.0(APIレベル21)以上
- Support Library
- appcompat-v7 26.1.0
- customtabs 26.1.0
もう少し低くても動くと思います。
WebView版の実装(XML)
0.全体
Contextual Action Bar
を使用しています。
Contextual Action Bar
の背景をChrome Custom Tabs
と同じ白色にするため、styles.xml
にactionModebackground
を追加します。
<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.xml
のactionModeCloseButtonStyle
にtintを指定して色を変更します。
<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
とマッチ数表示用のTextView
はLinearLayout
で囲んでActionMode#setCustomView
に指定します(ここではそのレイアウトのみ)。
<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>
<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>
<resources>
...
<color name="colorCursor">#4285F4</color>
...
</resources>
<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 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>
<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
を登録する必要があります。
あとはこれらをActionMode
やTextWatcher
などのイベントに合わせて呼ぶだけです。
mWebView.setFindListener(this); // FindListenerの設定
// 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;
}
// 検索(検索文字列未指定の場合は検索結果数用の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);
}
// キーボードのSearchボタン押下時の前方検索
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
mWebView.findNext(true);
return true;
}
return false;
}
// 検索終了時に <現在の位置(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にあります。