Skip to content

Instantly share code, notes, and snippets.

@jaredrummler
Last active May 22, 2020 22:48
Show Gist options
  • Save jaredrummler/c408c9d897fd92d5d116 to your computer and use it in GitHub Desktop.
Save jaredrummler/c408c9d897fd92d5d116 to your computer and use it in GitHub Desktop.
Customize the SearchView in an ActionBar for Android
/*
* Copyright (C) 2015 Jared Rummler <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jrummyapps.android.ui.searchview;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import android.annotation.SuppressLint;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ImageSpan;
import android.util.SparseArray;
import android.view.Menu;
import android.view.View;
import android.widget.AutoCompleteTextView;
import android.widget.ImageView;
import android.widget.SearchView;
import android.widget.TextView;
/**
*
* Helper class to style a {@link SearchView}.</p>
*
* Example usage:</p>
*
* <pre>
* <code>
* SearchViewStyle.on(searchView)
* .setCursorColor(Color.WHITE)
* .setTextColor(Color.WHITE)
* .setHintTextColor(Color.WHITE)
* .setSearchHintDrawable(R.drawable.ic_search_api_material)
* .setSearchButtonImageResource(R.drawable.ic_search_api_material)
* .setCloseBtnImageResource(R.drawable.ic_clear_material)
* .setVoiceBtnImageResource(R.drawable.ic_voice_search_api_material)
* .setGoBtnImageResource(R.drawable.ic_go_search_api_material)
* .setCommitIcon(R.drawable.ic_commit_search_api_material)
* .setSubmitAreaDrawableId(R.drawable.abc_textfield_search_activated_mtrl_alpha)
* .setSearchPlateDrawableId(R.drawable.abc_textfield_search_activated_mtrl_alpha)
* .setSearchPlateTint(Color.WHITE)
* .setSubmitAreaTint(Color.WHITE);
* </pre>
*
* </code>
*
* @author Jared Rummler <[email protected]>
* @since Oct 24, 2014
*/
public class SearchViewStyle {
// ===========================================================
// STATIC FIELDS
// ===========================================================
// The root view
// LinearLayout
public static final String SEARCH_BAR = "search_bar";
// This is actually used for the badge icon *or* the badge label (or neither)
// TextView
public static final String SEARCH_BADGE = "search_badge";
// ImageView
public static final String SEARCH_BUTTON = "search_button";
// LinearLayout
public static final String SEARCH_EDIT_FRAME = "search_edit_frame";
// ImageView
public static final String SEARCH_MAG_ICON = "search_mag_icon";
// LinearLayout
public static final String SEARCH_PLATE = "search_plate";
// android.widget.SearchView$SearchAutoComplete which extends AutoCompleteTextView
public static final String SEARCH_SRC_TEXT = "search_src_text";
// ImageView
public static final String SEARCH_CLOSE_BTN = "search_close_btn";
// LinearLayout
public static final String SUBMIT_AREA = "submit_area";
// ImageView
public static final String SEARCH_GO_BTN = "search_go_btn";
// ImageView
public static final String SEARCH_VOICE_BTN = "search_voice_btn";
// ===========================================================
// STATIC METHODS
// ===========================================================
public static SearchViewStyle on(final Menu menu, final int id) {
return new SearchViewStyle(menu, id);
}
public static SearchViewStyle on(final SearchView searchView) {
return new SearchViewStyle(searchView);
}
@SuppressLint("NewApi")
private static void setTint(final Drawable drawable, final int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
drawable.setTint(color);
} else {
drawable.setColorFilter(color, android.graphics.PorterDuff.Mode.SRC_ATOP);
}
}
// ===========================================================
// FIELDS
// ===========================================================
private final SparseArray<View> mViews;
private final SearchView mSearchView;
// ===========================================================
// INITIALIZERS
// ===========================================================
{
mViews = new SparseArray<View>();
}
// ===========================================================
// CONSTRUCTORS
// ===========================================================
private SearchViewStyle(final Menu menu, final int id) {
this((SearchView) menu.findItem(id).getActionView());
}
private SearchViewStyle(final SearchView searchView) {
mSearchView = searchView;
}
// ===========================================================
// GETTERS AND SETTERS
// ===========================================================
public SearchView getSearchView() {
return mSearchView;
}
// ===========================================================
// METHODS
// ===========================================================
// TODO: add javadoc
private int getId(final String name) {
return mSearchView.getContext().getResources()
.getIdentifier("android:id/" + name, null, null);
}
@SuppressWarnings("unchecked")
public <T extends View> T getView(final String name) {
final int id = getId(name);
if (id == 0) {
return null;
}
View view = mViews.get(id);
if (view == null) {
view = mSearchView.findViewById(id);
}
return (T) view;
}
public SearchViewStyle setSearchPlateDrawableId(final int id) {
final View view = getView(SEARCH_PLATE);
if (view != null) {
view.setBackgroundResource(id);
}
return this;
}
public SearchViewStyle setSubmitAreaDrawableId(final int id) {
final View view = getView(SUBMIT_AREA);
if (view != null) {
view.setBackgroundResource(id);
}
return this;
}
public SearchViewStyle setSearchPlateTint(final int color) {
final View view = getView(SEARCH_PLATE);
if (view != null) {
final Drawable background = view.getBackground();
if (background != null) {
setTint(background, color);
}
}
return this;
}
public SearchViewStyle setSubmitAreaTint(final int color) {
final View view = getView(SUBMIT_AREA);
if (view != null) {
final Drawable background = view.getBackground();
if (background != null) {
setTint(background, color);
}
}
return this;
}
public SearchViewStyle setGoBtnImageResource(final int resId) {
final ImageView imageView = getView(SEARCH_GO_BTN);
if (imageView != null) {
imageView.setImageResource(resId);
}
return this;
}
public SearchViewStyle setSearchButtonImageResource(final int resId) {
final ImageView imageView = getView(SEARCH_BUTTON);
if (imageView != null) {
imageView.setImageResource(resId);
}
return this;
}
public SearchViewStyle setTextColor(final int color) {
final AutoCompleteTextView editText = getView(SEARCH_SRC_TEXT);
if (editText != null) {
editText.setTextColor(color);
}
return this;
}
public SearchViewStyle setHintTextColor(final int color) {
final AutoCompleteTextView editText = getView(SEARCH_SRC_TEXT);
if (editText != null) {
editText.setHintTextColor(color);
}
return this;
}
public SearchViewStyle setSearchHintDrawable(final int id, final String... hint) {
return setSearchHintDrawable(mSearchView.getContext().getResources().getDrawable(id), hint);
}
public SearchViewStyle setSearchHintDrawable(final Drawable drawable, final String... hint) {
try {
// android.widget.SearchView$SearchAutoComplete extends AutoCompleteTextView
final AutoCompleteTextView editText = getView(SEARCH_SRC_TEXT);
if (editText == null) {
return this;
}
// http://nlopez.io/how-to-style-the-actionbar-searchview-programmatically/
final Class<?> clazz = Class.forName("android.widget.SearchView$SearchAutoComplete");
// Add the icon as an ImageSpan
final Method textSizeMethod = clazz.getMethod("getTextSize");
final Float rawTextSize = (Float) textSizeMethod.invoke(editText);
final int textSize = (int) (rawTextSize * 1.25);
drawable.setBounds(0, 0, textSize, textSize);
// Create hint text
final SpannableStringBuilder stopHint = new SpannableStringBuilder(" ");
if (hint != null && hint.length == 1) {
stopHint.append(hint[0]);
}
stopHint.setSpan(new ImageSpan(drawable), 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// Set the new hint text
final Method setHintMethod = clazz.getMethod("setHint", CharSequence.class);
setHintMethod.invoke(editText, stopHint);
} catch (final Exception ignored) {
}
return this;
}
public SearchViewStyle setCursorResId(final int drawableId) {
final AutoCompleteTextView editText = getView(SEARCH_SRC_TEXT);
if (editText != null) {
try {
final Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
f.setAccessible(true);
f.set(editText, drawableId);
} catch (final Throwable ignored) {
}
}
return this;
}
public SearchViewStyle setCursorColor(final int color) {
final AutoCompleteTextView editText = getView(SEARCH_SRC_TEXT);
if (editText != null) {
try {
final Field fCursorDrawableRes = TextView.class
.getDeclaredField("mCursorDrawableRes");
fCursorDrawableRes.setAccessible(true);
final int mCursorDrawableRes = fCursorDrawableRes.getInt(editText);
final Field fEditor = TextView.class.getDeclaredField("mEditor");
fEditor.setAccessible(true);
final Object editor = fEditor.get(editText);
final Class<?> clazz = editor.getClass();
final Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable");
fCursorDrawable.setAccessible(true);
final Drawable[] drawables = new Drawable[2];
drawables[0] = editText.getContext().getResources().getDrawable(mCursorDrawableRes);
drawables[1] = editText.getContext().getResources().getDrawable(mCursorDrawableRes);
drawables[0].setColorFilter(color, PorterDuff.Mode.SRC_IN);
drawables[1].setColorFilter(color, PorterDuff.Mode.SRC_IN);
fCursorDrawable.set(editor, drawables);
} catch (final Throwable ignored) {
}
}
return this;
}
public SearchViewStyle setCloseBtnImageResource(final int resId) {
final ImageView imageView = getView(SEARCH_CLOSE_BTN);
if (imageView != null) {
imageView.setImageResource(resId);
}
return this;
}
public SearchViewStyle setVoiceBtnImageResource(final int resId) {
final ImageView imageView = getView(SEARCH_VOICE_BTN);
if (imageView != null) {
imageView.setImageResource(resId);
}
return this;
}
public SearchViewStyle setCommitIcon(final int drawableId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
final Field commitIcon = mSearchView.getClass().getField(
"mSuggestionCommitIconResId");
commitIcon.setAccessible(true);
commitIcon.set(mSearchView, drawableId);
} catch (final Exception ignored) {
}
}
return this;
}
}
@MFamStory
Copy link

Thankyou!

@nelsonalfo
Copy link

You are the freaking best! 👍

@mc1100
Copy link

mc1100 commented Jun 21, 2018

It seems, like mViews is always empty.

@ijkmoises
Copy link

ijkmoises commented May 22, 2020

didn't work with androidx.appcompat.widget.SearchView :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment