Skip to content

Instantly share code, notes, and snippets.

@jaredrummler
Last active May 22, 2020 22:48
Show Gist options
  • Select an option

  • Save jaredrummler/c408c9d897fd92d5d116 to your computer and use it in GitHub Desktop.

Select an option

Save jaredrummler/c408c9d897fd92d5d116 to your computer and use it in GitHub Desktop.

Revisions

  1. Jared Rummler revised this gist Feb 11, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion SearchViewStyle.java
    Original file line number Diff line number Diff line change
    @@ -41,7 +41,7 @@
    *
    * <pre>
    * <code>
    * CustomizeSearchView.on(searchView)
    * SearchViewStyle.on(searchView)
    * .setCursorColor(Color.WHITE)
    * .setTextColor(Color.WHITE)
    * .setHintTextColor(Color.WHITE)
  2. Jared Rummler revised this gist Feb 11, 2015. 1 changed file with 5 additions and 4 deletions.
    9 changes: 5 additions & 4 deletions SearchViewStyle.java
    Original file line number Diff line number Diff line change
    @@ -166,14 +166,14 @@ public SearchView getSearchView() {

    // TODO: add javadoc

    private int getId(String name) {
    private int getId(final String name) {
    return mSearchView.getContext().getResources()
    .getIdentifier("android:id/" + name, null, null);
    }

    @SuppressWarnings("unchecked")
    public <T extends View> T getView(String name) {
    int id = getId(name);
    public <T extends View> T getView(final String name) {
    final int id = getId(name);
    if (id == 0) {
    return null;
    }
    @@ -344,7 +344,8 @@ public SearchViewStyle setVoiceBtnImageResource(final int resId) {
    public SearchViewStyle setCommitIcon(final int drawableId) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    try {
    Field commitIcon = mSearchView.getClass().getField("mSuggestionCommitIconResId");
    final Field commitIcon = mSearchView.getClass().getField(
    "mSuggestionCommitIconResId");
    commitIcon.setAccessible(true);
    commitIcon.set(mSearchView, drawableId);
    } catch (final Exception ignored) {
  3. Jared Rummler revised this gist Feb 11, 2015. 2 changed files with 356 additions and 353 deletions.
    353 changes: 0 additions & 353 deletions SearchViewCustomizer.java
    Original file line number Diff line number Diff line change
    @@ -1,353 +0,0 @@
    /*
    * Copyright (C) 2014 Jared Rummler <jared@jrummyapps.com>
    *
    * 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.ui;

    import java.lang.reflect.Field;
    import java.lang.reflect.Method;

    import android.graphics.Typeface;
    import android.graphics.drawable.Drawable;
    import android.os.Build;
    import android.text.InputType;
    import android.text.Spannable;
    import android.text.SpannableStringBuilder;
    import android.text.style.ImageSpan;
    import android.view.Menu;
    import android.view.View;
    import android.widget.AutoCompleteTextView;
    import android.widget.ImageView;
    import android.widget.SearchView;
    import android.widget.TextView;

    /**
    * Customize {@link android.widget.SearchView}</p>
    *
    * This should be used in {@link android.app.Activity#onCreateOptionsMenu(Menu)} after inflating the
    * menu which has the action view class set as android.widget.SearchView</p>
    *
    * Some example usage:</p>
    *
    * <pre>
    * <code>
    * new SearchViewCustomizer(menu, R.id.menu_search).setBackgroundColor(Color.TRANSPARENT)
    * .setEditTextColor(ColorUtils.WHITE).setEditTextHintColor(Color.LTGRAY)
    * .setEditTextInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS)
    * .setEditTextTypeface(robotoLight).setSearchHintDrawable(searchIcon, "filename")
    * .setEditTextCursorResId(R.drawable.white_cursor)
    * .setCloseDrawable(closeDrawable)
    * .setEditTextBackground(R.drawable.material_edit_text);
    * </code>
    * </pre>
    *
    * @author Jared Rummler <jared@jaredrummler.com>
    * @version 1.0
    * @since Oct 24, 2014
    */
    public class SearchViewCustomizer {

    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Static Fields
    // ===========================================================

    // ===========================================================
    // Static Initializers
    // ===========================================================

    // ===========================================================
    // Static Methods
    // ===========================================================

    // ===========================================================
    // Fields
    // ===========================================================

    private final SearchView mSearchView;

    private final int mSearchPlateId;

    private final int mSearchSrcTextId;

    private final int mSearchCloseBtnId;

    // ===========================================================
    // Initializers
    // ===========================================================

    // ===========================================================
    // Constructors
    // ===========================================================

    public SearchViewCustomizer(Menu menu, int id) {
    this((SearchView) menu.findItem(id).getActionView());
    }

    public SearchViewCustomizer(SearchView searchView) {
    mSearchView = searchView;
    mSearchSrcTextId = searchView.getContext().getResources()
    .getIdentifier("android:id/search_src_text", null, null);
    mSearchPlateId = searchView.getContext().getResources()
    .getIdentifier("android:id/search_plate", null, null);
    mSearchCloseBtnId = searchView.getContext().getResources()
    .getIdentifier("android:id/search_close_btn", null, null);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    // ===========================================================
    // Methods
    // ===========================================================

    /**
    * Set the background of the search plate.
    *
    * @param resid
    * The identifier of the resource.
    * @return
    */
    public SearchViewCustomizer setBackgroundResource(int resid) {
    if (mSearchPlateId != 0) {
    View searchPlate = findById(mSearchPlateId);
    searchPlate.setBackgroundResource(resid);
    }
    return this;
    }

    /**
    * Set the background of the search plate
    *
    * @param color
    * The color of the background
    * @return
    */
    public SearchViewCustomizer setBackgroundColor(int color) {
    if (mSearchPlateId != 0) {
    View searchPlate = findById(mSearchPlateId);
    searchPlate.setBackgroundColor(color);
    }
    return this;
    }

    /**
    * Set the background of the search plate
    *
    * @param background
    * The Drawable to use as the background, or null to remove the background
    * @return
    */
    @SuppressWarnings("deprecation")
    public SearchViewCustomizer setBackgroundDrawable(Drawable background) {
    if (mSearchPlateId != 0) {
    View searchPlate = findById(mSearchPlateId);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    searchPlate.setBackground(background);
    } else {
    searchPlate.setBackgroundDrawable(background);
    }
    }
    return this;
    }

    /**
    * Set the text color for the EditText
    *
    * @param color
    * @return
    */
    public SearchViewCustomizer setEditTextColor(int color) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    searchEditText.setTextColor(color);
    }
    return this;
    }

    /**
    * Sets the color of the hint text for all the states (disabled, focussed, selected...)
    *
    * @param color
    * @return
    */
    public SearchViewCustomizer setEditTextHintColor(int color) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    searchEditText.setHintTextColor(color);
    }
    return this;
    }

    /**
    * Set the background for the EditText displayed in the SearchView
    *
    * @param resId
    * The identifier of the resource.
    * @return
    */
    public SearchViewCustomizer setEditTextBackground(int resId) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    searchEditText.setBackgroundResource(resId);
    }
    return this;
    }

    /**
    * Set any input types on the EditText, such as {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS}
    *
    * @param type
    * @return
    */
    public SearchViewCustomizer setEditTextInputType(int type) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    searchEditText.setInputType(type);
    }
    return this;
    }

    /**
    * Apply a custom typeface to the search field EditText
    *
    * @param tf
    * @return
    */
    public SearchViewCustomizer setEditTextTypeface(Typeface tf) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    searchEditText.setTypeface(tf);
    }
    return this;
    }

    /**
    * Set the search icon that appears before the hint text.
    *
    * @param drawable
    * The drawable to use for the hint.
    * @param hint
    * The text for the hint.
    * @return
    */
    public SearchViewCustomizer setSearchHintDrawable(Drawable drawable, String hint) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    try {
    // http://nlopez.io/how-to-style-the-actionbar-searchview-programmatically/
    Class<?> clazz = Class.forName("android.widget.SearchView$SearchAutoComplete");
    // Add the icon as an spannable
    Method textSizeMethod = clazz.getMethod("getTextSize");
    Float rawTextSize = (Float) textSizeMethod.invoke(searchEditText);
    int textSize = (int) (rawTextSize * 1.25);
    drawable.setBounds(0, 0, textSize, textSize);
    // Create hint text
    SpannableStringBuilder stopHint = new SpannableStringBuilder(" ");
    stopHint.append(hint);
    stopHint.setSpan(new ImageSpan(drawable), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    // Set the new hint text
    Method setHintMethod = clazz.getMethod("setHint", CharSequence.class);
    setHintMethod.invoke(searchEditText, stopHint);
    } catch (Exception ignored) {
    }
    }
    return this;
    }

    /**
    * Set the cursor that will be used in the EditText.
    *
    * @param resId
    * The resource id for your custom cursor.
    * @return
    */
    public SearchViewCustomizer setEditTextCursorResId(int resId) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    try {
    // https://github.com/android/platform_frameworks_base/blob/kitkat-release/core/java/android/widget/TextView.java#L562-564
    Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
    f.setAccessible(true);
    f.set(searchEditText, resId);
    } catch (Exception ignored) {
    }
    }
    return this;
    }

    /**
    * Set the cancel drawable.
    *
    * @param drawable
    * The drawable to set
    * @return
    */
    public SearchViewCustomizer setCloseDrawable(Drawable drawable) {
    if (mSearchCloseBtnId != 0) {
    ImageView searchCloseBtn = findById(mSearchCloseBtnId);
    searchCloseBtn.setImageDrawable(drawable);
    }
    return this;
    }

    /**
    *
    * @return The {@link AutoCompleteTextView} that is used in the {@link SearchView} or
    * {@code null} if we failed to find it.
    */
    public AutoCompleteTextView getAutoCompleteTextView() {
    if (mSearchSrcTextId == 0) return null;
    return findById(mSearchSrcTextId);
    }

    /**
    *
    * @return The {@link ImageView} that is used as the close button in the {@link SearchView} or
    * {@code null} if we failed to find it.
    */
    public ImageView getCloseButton() {
    if (mSearchCloseBtnId == 0) return null;
    return findById(mSearchCloseBtnId);
    }

    /**
    *
    * @return The search plate that holds most of the views in the {@link SearchView} or
    * {@code null} if we failed to find it.
    */
    public View getSearchPlate() {
    if (mSearchPlateId == 0) return null;
    return findById(mSearchPlateId);
    }

    @SuppressWarnings("unchecked")
    private <T extends View> T findById(int id) {
    return (T) mSearchView.findViewById(id);
    }

    // ===========================================================
    // Inner and Anonymous Classes and/or Interfaces
    // ===========================================================

    }
    356 changes: 356 additions & 0 deletions SearchViewStyle.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,356 @@
    /*
    * Copyright (C) 2015 Jared Rummler <jared.rummler@gmail.com>
    *
    * 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>
    * CustomizeSearchView.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 <jared.rummler@gmail.com>
    * @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(String name) {
    return mSearchView.getContext().getResources()
    .getIdentifier("android:id/" + name, null, null);
    }

    @SuppressWarnings("unchecked")
    public <T extends View> T getView(String name) {
    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 {
    Field commitIcon = mSearchView.getClass().getField("mSuggestionCommitIconResId");
    commitIcon.setAccessible(true);
    commitIcon.set(mSearchView, drawableId);
    } catch (final Exception ignored) {
    }
    }
    return this;
    }

    }
  4. Jared Rummler created this gist Oct 24, 2014.
    353 changes: 353 additions & 0 deletions SearchViewCustomizer.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,353 @@
    /*
    * Copyright (C) 2014 Jared Rummler <jared@jrummyapps.com>
    *
    * 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.ui;

    import java.lang.reflect.Field;
    import java.lang.reflect.Method;

    import android.graphics.Typeface;
    import android.graphics.drawable.Drawable;
    import android.os.Build;
    import android.text.InputType;
    import android.text.Spannable;
    import android.text.SpannableStringBuilder;
    import android.text.style.ImageSpan;
    import android.view.Menu;
    import android.view.View;
    import android.widget.AutoCompleteTextView;
    import android.widget.ImageView;
    import android.widget.SearchView;
    import android.widget.TextView;

    /**
    * Customize {@link android.widget.SearchView}</p>
    *
    * This should be used in {@link android.app.Activity#onCreateOptionsMenu(Menu)} after inflating the
    * menu which has the action view class set as android.widget.SearchView</p>
    *
    * Some example usage:</p>
    *
    * <pre>
    * <code>
    * new SearchViewCustomizer(menu, R.id.menu_search).setBackgroundColor(Color.TRANSPARENT)
    * .setEditTextColor(ColorUtils.WHITE).setEditTextHintColor(Color.LTGRAY)
    * .setEditTextInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS)
    * .setEditTextTypeface(robotoLight).setSearchHintDrawable(searchIcon, "filename")
    * .setEditTextCursorResId(R.drawable.white_cursor)
    * .setCloseDrawable(closeDrawable)
    * .setEditTextBackground(R.drawable.material_edit_text);
    * </code>
    * </pre>
    *
    * @author Jared Rummler <jared@jaredrummler.com>
    * @version 1.0
    * @since Oct 24, 2014
    */
    public class SearchViewCustomizer {

    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Static Fields
    // ===========================================================

    // ===========================================================
    // Static Initializers
    // ===========================================================

    // ===========================================================
    // Static Methods
    // ===========================================================

    // ===========================================================
    // Fields
    // ===========================================================

    private final SearchView mSearchView;

    private final int mSearchPlateId;

    private final int mSearchSrcTextId;

    private final int mSearchCloseBtnId;

    // ===========================================================
    // Initializers
    // ===========================================================

    // ===========================================================
    // Constructors
    // ===========================================================

    public SearchViewCustomizer(Menu menu, int id) {
    this((SearchView) menu.findItem(id).getActionView());
    }

    public SearchViewCustomizer(SearchView searchView) {
    mSearchView = searchView;
    mSearchSrcTextId = searchView.getContext().getResources()
    .getIdentifier("android:id/search_src_text", null, null);
    mSearchPlateId = searchView.getContext().getResources()
    .getIdentifier("android:id/search_plate", null, null);
    mSearchCloseBtnId = searchView.getContext().getResources()
    .getIdentifier("android:id/search_close_btn", null, null);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    // ===========================================================
    // Methods
    // ===========================================================

    /**
    * Set the background of the search plate.
    *
    * @param resid
    * The identifier of the resource.
    * @return
    */
    public SearchViewCustomizer setBackgroundResource(int resid) {
    if (mSearchPlateId != 0) {
    View searchPlate = findById(mSearchPlateId);
    searchPlate.setBackgroundResource(resid);
    }
    return this;
    }

    /**
    * Set the background of the search plate
    *
    * @param color
    * The color of the background
    * @return
    */
    public SearchViewCustomizer setBackgroundColor(int color) {
    if (mSearchPlateId != 0) {
    View searchPlate = findById(mSearchPlateId);
    searchPlate.setBackgroundColor(color);
    }
    return this;
    }

    /**
    * Set the background of the search plate
    *
    * @param background
    * The Drawable to use as the background, or null to remove the background
    * @return
    */
    @SuppressWarnings("deprecation")
    public SearchViewCustomizer setBackgroundDrawable(Drawable background) {
    if (mSearchPlateId != 0) {
    View searchPlate = findById(mSearchPlateId);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    searchPlate.setBackground(background);
    } else {
    searchPlate.setBackgroundDrawable(background);
    }
    }
    return this;
    }

    /**
    * Set the text color for the EditText
    *
    * @param color
    * @return
    */
    public SearchViewCustomizer setEditTextColor(int color) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    searchEditText.setTextColor(color);
    }
    return this;
    }

    /**
    * Sets the color of the hint text for all the states (disabled, focussed, selected...)
    *
    * @param color
    * @return
    */
    public SearchViewCustomizer setEditTextHintColor(int color) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    searchEditText.setHintTextColor(color);
    }
    return this;
    }

    /**
    * Set the background for the EditText displayed in the SearchView
    *
    * @param resId
    * The identifier of the resource.
    * @return
    */
    public SearchViewCustomizer setEditTextBackground(int resId) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    searchEditText.setBackgroundResource(resId);
    }
    return this;
    }

    /**
    * Set any input types on the EditText, such as {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS}
    *
    * @param type
    * @return
    */
    public SearchViewCustomizer setEditTextInputType(int type) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    searchEditText.setInputType(type);
    }
    return this;
    }

    /**
    * Apply a custom typeface to the search field EditText
    *
    * @param tf
    * @return
    */
    public SearchViewCustomizer setEditTextTypeface(Typeface tf) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    searchEditText.setTypeface(tf);
    }
    return this;
    }

    /**
    * Set the search icon that appears before the hint text.
    *
    * @param drawable
    * The drawable to use for the hint.
    * @param hint
    * The text for the hint.
    * @return
    */
    public SearchViewCustomizer setSearchHintDrawable(Drawable drawable, String hint) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    try {
    // http://nlopez.io/how-to-style-the-actionbar-searchview-programmatically/
    Class<?> clazz = Class.forName("android.widget.SearchView$SearchAutoComplete");
    // Add the icon as an spannable
    Method textSizeMethod = clazz.getMethod("getTextSize");
    Float rawTextSize = (Float) textSizeMethod.invoke(searchEditText);
    int textSize = (int) (rawTextSize * 1.25);
    drawable.setBounds(0, 0, textSize, textSize);
    // Create hint text
    SpannableStringBuilder stopHint = new SpannableStringBuilder(" ");
    stopHint.append(hint);
    stopHint.setSpan(new ImageSpan(drawable), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    // Set the new hint text
    Method setHintMethod = clazz.getMethod("setHint", CharSequence.class);
    setHintMethod.invoke(searchEditText, stopHint);
    } catch (Exception ignored) {
    }
    }
    return this;
    }

    /**
    * Set the cursor that will be used in the EditText.
    *
    * @param resId
    * The resource id for your custom cursor.
    * @return
    */
    public SearchViewCustomizer setEditTextCursorResId(int resId) {
    if (mSearchSrcTextId != 0) {
    AutoCompleteTextView searchEditText = findById(mSearchSrcTextId);
    try {
    // https://github.com/android/platform_frameworks_base/blob/kitkat-release/core/java/android/widget/TextView.java#L562-564
    Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
    f.setAccessible(true);
    f.set(searchEditText, resId);
    } catch (Exception ignored) {
    }
    }
    return this;
    }

    /**
    * Set the cancel drawable.
    *
    * @param drawable
    * The drawable to set
    * @return
    */
    public SearchViewCustomizer setCloseDrawable(Drawable drawable) {
    if (mSearchCloseBtnId != 0) {
    ImageView searchCloseBtn = findById(mSearchCloseBtnId);
    searchCloseBtn.setImageDrawable(drawable);
    }
    return this;
    }

    /**
    *
    * @return The {@link AutoCompleteTextView} that is used in the {@link SearchView} or
    * {@code null} if we failed to find it.
    */
    public AutoCompleteTextView getAutoCompleteTextView() {
    if (mSearchSrcTextId == 0) return null;
    return findById(mSearchSrcTextId);
    }

    /**
    *
    * @return The {@link ImageView} that is used as the close button in the {@link SearchView} or
    * {@code null} if we failed to find it.
    */
    public ImageView getCloseButton() {
    if (mSearchCloseBtnId == 0) return null;
    return findById(mSearchCloseBtnId);
    }

    /**
    *
    * @return The search plate that holds most of the views in the {@link SearchView} or
    * {@code null} if we failed to find it.
    */
    public View getSearchPlate() {
    if (mSearchPlateId == 0) return null;
    return findById(mSearchPlateId);
    }

    @SuppressWarnings("unchecked")
    private <T extends View> T findById(int id) {
    return (T) mSearchView.findViewById(id);
    }

    // ===========================================================
    // Inner and Anonymous Classes and/or Interfaces
    // ===========================================================

    }