How to style the ActionBar SearchView programmatically

I stumbled upon a problem with the styling of a view at work. I had almost everything solved with a custom theme, but it all came down to style a SearchView widget of an ActionBar.

That's where the fun begins.

All of this must happen in your onCreateOptionsMenu method. With a reference to your SearchView object in there, you can start doing things.

For example, if you'd like to get a reference to its EditText, you could do it using reflection like in this snippet.

int searchSrcTextId = getResources().getIdentifier("android:id/search_src_text", null, null);   
EditText searchEditText = (EditText) searchView.findViewById(searchSrcTextId);   
searchEditText.setTextColor(Color.WHITE);   searchEditText.setHintTextColor(Color.LTGRAY);

For changing the close icon, this one.

int closeButtonId = getResources().getIdentifier("android:id/search_close_btn", null, null);   
ImageView closeButtonImage = (ImageView) searchView.findViewById(closeButtonId);   
closeButtonImage.setImageResource(R.drawable.ic_action_cancel);

And so on. You can get all the references in either the Android code for SearchView.java, or in its layout. For the lazy ones, here are some of those references that you can retrieve (and play with).

// This excerpt is from Android source code (SearchView.java) 
mSearchButton = findViewById(R.id.search_button);   
mSearchEditFrame = findViewById(R.id.search_edit_frame);   
mSearchPlate = findViewById(R.id.search_plate);   
mSubmitArea = findViewById(R.id.submit_area);   
mSubmitButton = findViewById(R.id.search_go_btn);   
mCloseButton = (ImageView) findViewById(R.id.search_close_btn);   
mVoiceButton = findViewById(R.id.search_voice_btn);   
mSearchHintIcon = (ImageView) findViewById(R.id.search_mag_icon);  

But as you probably can see in Android's code, you have no direct access to the SearchAutoComplete class. The problem with that is that it's difficult to you to be able to style the magnifying glass next to the hint text.

You can set up with searchView.setQueryHint("your hint") its hint and you can hide it with searchView.setIconifiedByDefault(false) - though that's even worse because then the search icon is at the left of the EditText and it looks ugly.

The thing is, that icon at the left of the hint text is done in Android by using an ImageSpan in a SpannableString, and it depends directly on the theme applied to the control. But what happens if you can't change the theme? (like in my case). Then you have to do more dirty and ugly stuff.

// Accessing the SearchAutoComplete 
int queryTextViewId = getResources().getIdentifier("android:id/search_src_text", null, null);   
View autoComplete = searchView.findViewById(queryTextViewId);  
Class<?> clazz = Class.forName("android.widget.SearchView$SearchAutoComplete");  
SpannableStringBuilder stopHint = new SpannableStringBuilder("   ");   
stopHint.append(getString(R.string.your_new_text));  
// Add the icon as an spannable 
Drawable searchIcon = getResources().getDrawable(R.drawable.ic_action_search);   
Method textSizeMethod = clazz.getMethod("getTextSize");   
Float rawTextSize = (Float)textSizeMethod.invoke(autoComplete);   
int textSize = (int) (rawTextSize * 1.25);   
searchIcon.setBounds(0, 0, textSize, textSize);   
stopHint.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);  
// Set the new hint text 
Method setHintMethod = clazz.getMethod("setHint", CharSequence.class);   
setHintMethod.invoke(autoComplete, stopHint); 

And that's it. Ugly and hacky as promised.

But works!