Correct implementation of SearchView in android toolbar - android

I have a problem with searchview implementation in android toolbar.
The empty space padding is too big.
I don't want to hide other actions, but these actions are
overlapped by SearchView.
SearchView's underline is not visible
How do i fix issues mentioned above ?
menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/action_search"
android:title="#string/car_num"
android:icon="#drawable/ic_search_white_24dp"
app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="always" />
<item
android:id="#+id/action_add_client"
android:icon="#drawable/ic_account_multiple_plus"
android:title="#string/action_add_client"
app:showAsAction="always" />
</menu>
fragment
#Override
public void onCreateOptionsMenu(final Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_fragment_reg_vehicles, menu);
final MenuItem item = menu.findItem(R.id.action_search);
searchView = (SearchView) MenuItemCompat.getActionView(item);
searchView.setQueryHint("Search");
searchView.setMaxWidth(Integer.MAX_VALUE);
searchView.setIconifiedByDefault(false);
searchView.setOnQueryTextListener(this);
searchView.setOnCloseListener(new SearchView.OnCloseListener() {
#Override
public boolean onClose() {
setItemsVisibility(menu, item, true);
return false;
}
});
searchView.setOnSearchClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
setItemsVisibility(menu, item, false);
searchView.requestFocus();
}
});
}

Regarding your posted code, this is the output:
As you can see, there is two left margins: the widget's container and the magnify icon. This is why you have an empty space bigger than an another window with a title. And the menu items are pushed outside the toolbar which, I think, it's the default SearchView ActionView when it's not a CollapseActionView so it fills the parent.
From the source of SearchView widget and its layout abc_search_view.xml, I tried to remove the extra margins and avoid pushing the other items outside the toolbar.
But after many manipulations, my guess is you have to use a custom widget and/or a custom layout. Or to play with setIconifiedByDefault(true) which removes the magnify icon and its extra margin and to use setMaxWidth(MAX_SIZE) where MAX_SIZE is calculated dynamically by Integer.MAX_VALUE - (SIZE_OF_A_MENU_ITEM * NB_OF_MENU_ITEMS)... But it requires a lot of work for nothing. So using a custom layout could be the solution.
However, there is a possible way to keep the appcompat widget, some little workarounds. First, to avoid puhsing out the other items, you can use the CollapseActionView.
<item
...
app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="always|collapseActionView"/>
And to maintain your requirements, you have to expand it when you initialize it:
final SearchView searchView =
(SearchView) MenuItemCompat.getActionView(item);
MenuItemCompat.expandActionView(item);
Be aware that you have to use setOnActionExpandListener() in order to close the window if you don't want to collapse the item. This suggestion will give you this result:
Still the extra margins, right? Therefore, you have to retrieve the container and the magnify icon by their ids (which you can find in abc_search_view.xml... but let's save some time: they are R.id.search_edit_frame and R.id.search_mag_icon). You can remove their margins by using this method:
private void changeSearchViewElements(View view) {
if (view == null)
return;
if (view.getId() == R.id.search_edit_frame
|| view.getId() == R.id.search_mag_icon) {
LinearLayout.LayoutParams p =
(LinearLayout.LayoutParams) view.getLayoutParams();
p.leftMargin = 0; // set no left margin
view.setLayoutParams(p);
}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
changeSearchViewElements(viewGroup.getChildAt(i));
}
}
}
By calling it in a thread:
final SearchView searchView =
(SearchView) MenuItemCompat.getActionView(item);
...
searchView.post(new Runnable() {
#Override
public void run() {
changeSearchViewElements(searchView);
}
});
Here's the output:
Finally, to get the line under the field, there is a possible workaround as using a 9-patch drawable and set it as a background. You can easily find how-to on Google. So the condition will be:
private void changeSearchViewElements(View view) {
...
if (view.getId() == R.id.search_edit_frame
|| view.getId() == R.id.search_mag_icon) {
LinearLayout.LayoutParams p =
(LinearLayout.LayoutParams) view.getLayoutParams();
p.leftMargin = 0; // set no left margin
view.setLayoutParams(p);
} else if (view.getId() == R.id.search_src_text) {
AutoCompleteTextView searchEdit = (AutoCompleteTextView) view;
searchEdit.setBackgroundResource(R.drawable.rect_underline_white);
}
...
}
From the OP's comment below, the underline's field can also be done with the following statement:
searchView.findViewById(android.support.v7.appcompat.R.id.se‌​arch_src_text)
.setBa‌​ckgroundResource(R.d‌​rawable.abc_textfiel‌​d_search_default_mtr‌​l_alpha);
After these workarounds, as I said, it might be easier to use a custom layout. But if you want to keep the default SearchView widget, this might help.

Related

Change MenuItem text color programmatically

So I have a menu item, that's defined as:
<item
android:id="#+id/action_live"
android:title="#string/action_live"
android:orderInCategory="1"
app:showAsAction="ifRoom|withText" />
It shows as text, as you can see below:
And I want to programmatically change the "LIVE" text color. I've searched for a while and I found a method:
With globally defined:
private Menu mOptionsMenu;
and:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
mOptionsMenu = menu;
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
I do:
MenuItem liveitem = mOptionsMenu.findItem(R.id.action_live);
SpannableString s = new SpannableString(liveitem.getTitle().toString());
s.setSpan(new ForegroundColorSpan(Color.RED), 0, s.length(), 0);
liveitem.setTitle(s);
But nothing happens!
If I do the same for an item of the overflow menu, it works:
Is there some limitation for app:showAsAction="ifRoom|withText" items? Is there any workaround?
Thanks in advance.
Bit late to the party with this one, but I spent a while working on this and found a solution, which may be of use to anyone else trying to do the same thing. Some credit goes to Harish Sridharan for steering me in the right direction.
You can use findViewById(R.id.MY_MENU_ITEM_ID) to locate the menu item (provided that the menu had been created and prepared), and cast it to a TextView instance as suggested by Harish, which can then be styled as required.
public class MyAwesomeActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
super.onCreate(savedInstanceState);
// Force invalidatation of the menu to cause onPrepareOptionMenu to be called
invalidateOptionsMenu();
}
private void styleMenuButton() {
// Find the menu item you want to style
View view = findViewById(R.id.YOUR_MENU_ITEM_ID_HERE);
// Cast to a TextView instance if the menu item was found
if (view != null && view instanceof TextView) {
((TextView) view).setTextColor( Color.BLUE ); // Make text colour blue
((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_SP, 24); // Increase font size
}
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean result = super.onPrepareOptionsMenu(menu);
styleMenuButton();
return result;
}
}
The trick here is to force the menu to be invalidated in the activity's onCreate event (thereby causing the onPrepareMenuOptions to be called sooner than it would normally). Inside this method, we can locate the menu item and style as required.
#RRP give me a clue ,but his solution does not work for me. And #Box give a another, but his answer looks a little not so cleaner. Thanks them. So according to them, I have a total solution.
private static void setMenuTextColor(final Context context, final Toolbar toolbar, final int menuResId, final int colorRes) {
toolbar.post(new Runnable() {
#Override
public void run() {
View settingsMenuItem = toolbar.findViewById(menuResId);
if (settingsMenuItem instanceof TextView) {
if (DEBUG) {
Log.i(TAG, "setMenuTextColor textview");
}
TextView tv = (TextView) settingsMenuItem;
tv.setTextColor(ContextCompat.getColor(context, colorRes));
} else { // you can ignore this branch, because usually there is not the situation
Menu menu = toolbar.getMenu();
MenuItem item = menu.findItem(menuResId);
SpannableString s = new SpannableString(item.getTitle());
s.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, colorRes)), 0, s.length(), 0);
item.setTitle(s);
}
}
});
}
In order to change the colour of menu item you can find that item, extract the title from it, put it in a Spannable String and set the foreground colour to it. Try out this code piece
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
MenuItem mColorFullMenuBtn = menu.findItem(R.id.action_submit); // extract the menu item here
String title = mColorFullMenuBtn.getTitle().toString();
if (title != null) {
SpannableString s = new SpannableString(title);
s.setSpan(new ForegroundColorSpan(Color.parseColor("#FFFFFF")), 0, s.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); // provide whatever color you want here.
mColorFullMenuBtn.setTitle(s);
}
return super.onCreateOptionsMenu(menu);
}
It only becomes a text view after inspection, its real class is ActionMenuItemView, on which we can further set the text color like this:
public static void setToolbarMenuItemTextColor(final Toolbar toolbar,
final #ColorRes int color,
#IdRes final int resId) {
if (toolbar != null) {
for (int i = 0; i < toolbar.getChildCount(); i++) {
final View view = toolbar.getChildAt(i);
if (view instanceof ActionMenuView) {
final ActionMenuView actionMenuView = (ActionMenuView) view;
// view children are accessible only after layout-ing
actionMenuView.post(new Runnable() {
#Override
public void run() {
for (int j = 0; j < actionMenuView.getChildCount(); j++) {
final View innerView = actionMenuView.getChildAt(j);
if (innerView instanceof ActionMenuItemView) {
final ActionMenuItemView itemView = (ActionMenuItemView) innerView;
if (resId == itemView.getId()) {
itemView.setTextColor(ContextCompat.getColor(toolbar.getContext(), color));
}
}
}
}
});
}
}
}
}
You could put the change of the color in the onPrepareOptionsMenu:
override fun onPrepareOptionsMenu(menu: Menu?): Boolean
{
val signInMenuItem = menu?.findItem(R.id.menu_main_sign_in)
val title = signInMenuItem?.title.toString()
val spannable = SpannableString(title)
spannable.setSpan(
ForegroundColorSpan(Color.GREEN),
0,
spannable.length,
Spannable.SPAN_INCLUSIVE_INCLUSIVE)
SgnInMenuItem?.title = spannable
return super.onPrepareOptionsMenu(menu)
}
of course you can make it shorter above...
now you can change the color appearance upon other (ie. viewmodel) values...
RG
I spent a lot of hours on this and finally got it into work. There is easy solusion for Android 6 and 7 but it doesn't work on Android 5. This code works on all of them. So, if you are doing it in Kotlin this is my suggestion:
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.my_menu, menu)
setToolbarActionTextColor(menu, R.color.black)
this.menu = menu
return true
}
private fun setToolbarActionTextColor(menu: Menu, color: Int) {
val tb = findViewById<Toolbar>(R.id.toolbar)
tb?.let { toolbar ->
toolbar.post {
val view = findViewById<View>(R.id.my_tag)
if (view is TextView) {
view.setTextColor(ContextCompat.getColor(this, color))
} else {
val mi = menu.findItem(R.id.my_tag)
mi?.let {
val newTitle: Spannable = SpannableString(it.title.toString())
val newColor = ContextCompat.getColor(this, color)
newTitle.setSpan(ForegroundColorSpan(newColor),
0, newTitle.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
it.title = newTitle
}
}
}
}
}
It's complicated, but you can use the app:actionLayout attribute. For example,
my_menu.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/englishList"
android:orderInCategory="1"
app:showAsAction="ifRoom|withText"
app:actionLayout="#layout/custom_menu_item_english_list"
android:title=""/>
</menu>
custom_menu_item_english_list.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/englishListWhiteText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:lineHeight="16dp"
android:textSize="16sp"
android:text="英文"
android:layout_marginRight="8dp"/>
</LinearLayout>
MainActivity.java:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.my_menu, menu);
MenuItem item = menu.findItem(R.id.englishList);
item.getActionView().findViewById(R.id.englishListWhiteText)
.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v){
//Handle button click.
}
});
return true;
}
Result:
More Detailed Example=
https://medium.com/#info.anikdey003/custom-menu-item-using-action-layout-7a25118b9d5
if you are using popup menu function to show the menu items in the application and trying to change the design or color of your text items in the menu list, first create a style item in your style.xml file:
<style name="PopupMenuStyle" parent="Widget.AppCompat.PopupMenu">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_gravity">center</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">#color/ColorPrimary</item>
<item name="android:textSize">#dimen/textsize</item>
<item name="android:fontFamily">#font/myfonts</item></style>
and use this style in your code as:
val popupWrapper = ContextThemeWrapper(this, R.style.PopupMenuStyle)
val popup = PopupMenu(popupWrapper, your_menu_view)
MenuItem as defined by documentation is an interface. It will definitely be implemented with a view widget before being portrayed as an menu. Most cases these menu items are implemented as TextView. You can use UiAutomatorViewer to see the view hierarchy or even use hierarchyviewer which will be found in [sdk-home]/tools/. Attached one sample uiautomatorviewer screenshot for a MenuItem
So you can always typecast your MenuItem and set the color.
TextView liveitem = (TextView)mOptionsMenu.findItem(R.id.action_live);
liveitem.setTextColor(Color.RED);
EDIT:
Since there was request to see how to use this tool, I'm adding a few more contents.
Make sure you have set environment variable $ANDROID_HOME pointing to your SDK HOME.
In your terminal:
cd $ANDROID_HOME
./tools/uiautomatorviewer
This tool will open up.
The second or third button (refer screenshot) in the menu will capture the screenshot of your attached device or emulator and you can inspect the view and their hierarchy. Clicking on the view will describe the view and their information. It is tool purposely designed for testing and you can inspect any application.
Refer developer site for more info: uiautomatorviewer

ActionBar SearchView not fully expanding in landscape mode

When using the action bar search interface, the widget expands to occupy the full width of the screen in portrait mode, but stops short in landscape mode.
Is there a way to set the expansion layout params on the SearchView to fully fill the action bar when the user is typing a search?
See image:
Note: I'm not currently using ActionBar Sherlock
Edit:
Here's what I did to get it to extend the full width.
searchView.setOnSearchClickListener(new OnClickListener() {
private boolean extended = false;
#Override
public void onClick(View v) {
if (!extended) {
extended = true;
LayoutParams lp = v.getLayoutParams();
lp.width = LayoutParams.MATCH_PARENT;
}
}
});
MenuItemCompat's SearchView has a property named maxWidth.
final MenuItem searchItem = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setMaxWidth(xxx);
use screen width instead of xxx offcourse
Did you try something like that?
editView.setOnKeyListener(new OnKeyListener() {
private boolean extended = false;
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (!extended && event.getAction() == KeyEvent.ACTION_DOWN) {
extended = true;
LayoutParams lp = v.getLayoutParams();
lp.width = LayoutParams.MATCH_PARENT;
}
return false;
}
});
I figured out a solution for fully expanding the search view in landscape and to also have the action view already expanded when the activity is created. Here how it works:
1.First create an xml file in your res-menu folder called for example : searchview_in_menu.xml. Here you would have the following code:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="#+id/action_search"
android:title="#string/search"
android:icon="#android:drawable/ic_menu_search"
android:actionLayout="#layout/searchview_layout" />
</menu>
Note: "#string/search" - looks something like this in the res-strings.xml:
<string name="search">Search</string>
2.Second create the layout referred above ("#layout/searchview_layout") in res-layout folder. The new layout: searchview_layout.xml will look like this:
<?xml version="1.0" encoding="utf-8"?>
<SearchView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/search_view_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
Note: Here we are setting the search view width to match the width of its parent( android:layout_width="match_parent")
3.In your MainActivity class or in the activity that has to implement the Search View write in the onCreateOptionsMenu() method the following code:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.searchview_in_menu, menu);
//find the search view item and inflate it in the menu layout
MenuItem searchItem = menu.findItem(R.id.action_search);
mSearchView = (SearchView) searchItem.getActionView();
//set a hint on the search view (optional)
mSearchView.setQueryHint(getString(R.string.search));
//these flags together with the search view layout expand the search view in the landscape mode
searchItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
| MenuItem.SHOW_AS_ACTION_ALWAYS);
//expand the search view when entering the activity(optional)
searchItem.expandActionView();
return true;
}
Adjust the width layout param to MATCH_PARENT, that way the SearchView will fully expand in either portrait or landscape
ViewGroup.LayoutParams layoutParams = mSearchView.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
For ActionBarCompat use this:
MenuItemCompat.setShowAsAction(searchItem, MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
| MenuItem.SHOW_AS_ACTION_ALWAYS);
MenuItemCompat.expandActionView(searchItem);
Use this (100%):
mSearchView = (SearchView) searchItem.getActionView();
mSearchView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));

SearchView setOnClickListener NOT WORkING

Can anyone see why this is not working..
My SearchView is in the ActionBar and is always shown. I want to know when a user PRESSES the searchview... not when it expands or gains focus.
This code sits within onCreateOptionsMenu
SearchView = _searchView;
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
_searchView = (SearchView) menu.findItem(R.id.menu_finder_text_search).getActionView();
_searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
_searchView.setIconifiedByDefault(false); // Do not iconify the widget, we want to keep it open!
_searchView.setFocusable(false);
_searchView.setClickable(true);
_searchView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//DO SOMETHING!
}
});
Anyone?
SearchView is inherited from LinearLayout, so we can setOnClickListener for each child, like this:
public static void setSearchViewOnClickListener(View v, OnClickListener listener) {
if (v instanceof ViewGroup) {
ViewGroup group = (ViewGroup)v;
int count = group.getChildCount();
for (int i = 0; i < count; i++) {
View child = group.getChildAt(i);
if (child instanceof LinearLayout || child instanceof RelativeLayout) {
setSearchViewOnClickListener(child, listener);
}
if (child instanceof TextView) {
TextView text = (TextView)child;
text.setFocusable(false);
}
child.setOnClickListener(listener);
}
}
}
from: http://www.trinea.cn/android/searchview-setonclicklistener-not-working/
Ok, it does not answer the problem it only avoids it.
I have used this link to create a listener for when the keyboard is shown. This gives me an event at the right time for me.
https://stackoverflow.com/a/7423586/1312937
Try this:
1) Bind the view
#BindView(R.id.search) SearchView search;
2) In your onCreate(), write the following code.
search.setIconifiedByDefault(false);
search.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
search.setIconified(false);
}
});
3) And your SearchView should have this following attributes.
<SearchView
android:id="#+id/search"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:background="#drawable/rounded_corners_box"
android:drawableLeft="#android:drawable/ic_menu_search"
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
android:queryHint="Search your item.."
android:textColor="#android:color/black"
android:textColorHint="#color/colorPrimary"
app:defaultQueryHint="Select locality"/>
NOTE:
android:background="#drawable/rounded_corners_box" -- your custom border xml file.
android:drawableLeft="#android:drawable/ic_menu_search" -- search icon from drawable file.
Bind the Searchviews button to a custom ImageView and add the onClickListener there
ImageView searchButton = this.searchView.findViewById(android.support.v7.appcompat.R.id.search_button);
searchButton.setOnClickListener(v -> {
// Your code here
//This is needed since you are overwriting the default click behaviour
searchView.setIconified(false);
});
Recently stuck with this problem and found a simple solution.
searchView.setOnQueryTextFocusChangeListener(object : View.OnFocusChangeListener{
override fun onFocusChange(p0: View?, p1: Boolean) {
// Enter your code here
}
})
This method will be called when you will tap on search field and soft keyboard will appear.
Use the interface OnTouchListener: http://developer.android.com/reference/android/view/View.OnTouchListener.html
This requires a tiny bit more implementation code, but gives superior control over the UI. This solution assumes the user will be using a touch screen to interact with the View.
int search_button_id = context.getResources().getIdentifier("android:id/search_button", null, null);
ImageView search_button_view = (ImageView) mSearchView.findViewById(search_button_id);
search_button_view.setOnTouchListener((view, motionEvent) -> {
mSearchView.setIconified(false);
return true;
});

How to change the default icon on the SearchView, to be use in the action bar on Android?

I'm having a bit of trouble customizing the search icon in the SearchView. On my point of view, the icon can be changed in the Item attributes, right? Just check the code bellow..
Can someone tell me what I'm doing wrong?
This is the menu I'm using, with my custom search icon icn_lupa. But when I run the app, I always get the default search icon...
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/menu_search"
android:title="#string/menu_search"
android:icon="#drawable/icn_lupa"
android:showAsAction="always"
android:actionViewClass="android.widget.SearchView" />
</menu>
I've found another way to change the search icon which goes in the same line as Diego Pino's answer but straight in onPrepareOptionsMenu.
In your menu.xml (same as before)
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/action_search"
android:icon="#drawable/ic_action_fav"
android:title="#string/action_websearch"
android:showAsAction="always|never"
android:actionViewClass="android.widget.SearchView" />
</menu>
In your activity:
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem searchViewMenuItem = menu.findItem(R.id.action_search);
mSearchView = (SearchView) searchViewMenuItem.getActionView();
int searchImgId = getResources().getIdentifier("android:id/search_button", null, null);
ImageView v = (ImageView) mSearchView.findViewById(searchImgId);
v.setImageResource(R.drawable.your_new_icon);
mSearchView.setOnQueryTextListener(this);
return super.onPrepareOptionsMenu(menu);
}
I followed the example for changing the edittext in this example.
You should be able to do this for all icons/backgrounds in your SearchView, to find the right ID you can check here.
UPDATE November 2017:
Since this answer android has been updated with the possibility of changing the search icon through the XML.
If you target anything below android v21 you can use:
<android.support.v7.widget.SearchView
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:searchIcon="#drawable/ic_search_white_24dp"
app:closeIcon="#drawable/ic_clear_white_24dp" />
Or v21 and later:
<SearchView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:searchIcon="#drawable/ic_search_white_24dp"
android:closeIcon="#drawable/ic_clear_white_24dp" />
And there are even more options:
closeIcon
commitIcon
goIcon
searchHintIcon
searchIcon
voiceIcon
Nice answer from #just_user
For my case, since I am using the appcompat v7 library for the SearchView + ActionBar, i modified his solution a bit to make it compatible to my project, it should work so as long as you did not modify anything when you added appcompat v7 as library
XML:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:metrodeal="http://schemas.android.com/apk/res-auto" >
<item
android:id="#+id/main_menu_action_search"
android:orderInCategory="100"
android:title="#string/search"
metrodeal:showAsAction="always"
metrodeal:actionViewClass="android.support.v7.widget.SearchView"
android:icon="#drawable/search_btn"/>
</menu>
Java code:
#Override
public void onPrepareOptionsMenu(Menu menu) {
MenuItem searchViewMenuItem = menu.findItem(R.id.main_menu_action_search);
SearchView mSearchView = (SearchView) MenuItemCompat.getActionView(searchViewMenuItem);
int searchImgId = android.support.v7.appcompat.R.id.search_button; // I used the explicit layout ID of searchview's ImageView
ImageView v = (ImageView) mSearchView.findViewById(searchImgId);
v.setImageResource(R.drawable.search_btn);
super.onPrepareOptionsMenu(menu);
}
Excuse for the very big icon (I have not resized the icon just yet), but it should work as it is.
I was struggling with this too but then I accidentaly used 'collapseActionView' and that fixed it!
My menu.xml looks like this now:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/menu_search"
android:title="#string/menu_search"
android:showAsAction="always|withText|collapseActionView"
android:actionViewClass="android.widget.SearchView"
android:icon="#android:drawable/ic_menu_search" />
</menu>
The downside of this is that on tablets the SearchView will appear on the left side of the ActionBar instead of where the searchicon is, but I don't mind that.
I defined a style to do it .
here is my xml:
<android.support.v7.widget.SearchView
android:id="#+id/sv_search"
android:icon="#android:drawable/ic_menu_search"
android:layout_width="fill_parent"
**style="#style/CitySearchView"**
android:layout_height="wrap_content"/>
and this is my style:
<style name="CitySearchView" parent="Base.Widget.AppCompat.SearchView">
<item name="searchIcon">#drawable/ic_more_search</item>
</style>
That it!
After finish that,just take a look at Base.Widget.AppCompat.SearchView.
<style name="Base.Widget.AppCompat.SearchView" parent="android:Widget">
<item name="layout">#layout/abc_search_view</item>
<item name="queryBackground">#drawable/abc_textfield_search_material</item>
<item name="submitBackground">#drawable/abc_textfield_search_material</item>
<item name="closeIcon">#drawable/abc_ic_clear_mtrl_alpha</item>
<item name="searchIcon">#drawable/abc_ic_search_api_mtrl_alpha</item>
<item name="goIcon">#drawable/abc_ic_go_search_api_mtrl_alpha</item>
<item name="voiceIcon">#drawable/abc_ic_voice_search_api_mtrl_alpha</item>
<item name="commitIcon">#drawable/abc_ic_commit_search_api_mtrl_alpha</item>
<item name="suggestionRowLayout">#layout/abc_search_dropdown_item_icons_2line</item>
</style>
every item can be override by define a new style .
Hope it helps!
There's a way to do this. The trick is to recover the ImageView using its identifier and setting a new image with setImageResource(). This solution is inspired on Changing the background drawable of the searchview widget.
private SearchView searchbox;
private void customizeSearchbox() {
setSearchHintIcon(R.drawable.new_search_icon);
}
private void setSearchHintIcon(int resourceId) {
ImageView searchHintIcon = (ImageView) findViewById(searchbox,
"android:id/search_mag_icon");
searchHintIcon.setImageResource(resourceId);
}
private View findViewById(View v, String id) {
return v.findViewById(v.getContext().getResources().
getIdentifier(id, null, null));
}
SearchView searchView = (SearchView) findViewById(R.id.address_search);
try {
Field searchField = SearchView.class
.getDeclaredField("mSearchButton");
searchField.setAccessible(true);
ImageView searchBtn = (ImageView) searchField.get(searchView);
searchBtn.setImageResource(R.drawable.search_glass);
} catch (NoSuchFieldException e) {
} catch (IllegalAccessException e) {
}
After some research I found the solution here. The trick is that the icon is not in an ImageView but in the Spannable object.
// 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);
In menu xml:
<item
android:id="#+id/menu_filter"
android:actionLayout="#layout/menu_filter"
android:icon="#drawable/ic_menu_filter"
android:orderInCategory="10"
android:showAsAction="always"
android:title="#string/menu_filter"/>
and create the layout/menu_filter:
<?xml version="1.0" encoding="utf-8"?>
<SearchView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:searchIcon="#drawable/ic_menu_filter"/>
then in activity's onCreateOptionsMenu or onPrepareOptionsMenu:
SearchView searchView = (SearchView) menu.findItem(R.id.menu_filter).getActionView();
searchView.setOnQueryTextListener(this);
It looks like the actionViewClass overides the icon and it doesn't look like you can change it from this class.
You got two solutions:
Live with it and I think it's the best option in terms of user experience and platform conformity.
Define your own actionViewClass
<SearchView
android:searchIcon="#drawable/ic_action_search"
..../>
use the searchIcon xml tag
This works with Material Design (MaterialComponents theme) and BottomAppBar.
If you are using androidx library, for example:
<item
android:id="#+id/sv"
android:title="#string/search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always" />
You can create a method and invoke it from wherever you want:
/**
* Set SearchView Icon
* #param i Drawable icon
*/
private void setSVIcon(int i) {
ImageView iv = searchView.findViewById(androidx.appcompat.R.id.search_button);
iv.setImageDrawable(ResourcesCompat.getDrawable(getResources(), i, null));
}
Usage example:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(m, menu);
MenuItem mn = menu.findItem(R.id.sv);
if (mn != null) {
searchview = (SearchView) mn.getActionView();
setSVIcon(R.drawable.ic_sr);
}
}
Update hint of AutocompleteTextView for updating search icon in the expanded mode, copied from android source,
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
mSearchMenuItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) mSearchMenuItem.getActionView();
int searchImgId = getResources().getIdentifier("android:id/search_button", null, null);
ImageView v = (ImageView) searchView.findViewById(searchImgId);
v.setImageResource(R.drawable.search_or);
int searchTextViewId = searchView.getContext().getResources().getIdentifier("android:id/search_src_text", null, null);
AutoCompleteTextView searchTextView = (AutoCompleteTextView) searchView.findViewById(searchTextViewId);
searchTextView.setHintTextColor(getResources().getColor(R.color.hint_color_white));
searchTextView.setTextColor(getResources().getColor(android.R.color.white));
searchTextView.setTextSize(18.0f);
SpannableStringBuilder ssb = new SpannableStringBuilder(" "); // for the icon
ssb.append(hintText);
Drawable searchIcon = getResources().getDrawable(R.drawable.search_or);
int textSize = (int) (searchTextView.getTextSize() * 1.25);
searchIcon.setBounds(0, 0, textSize, textSize);
ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
searchTextView.setHint(ssb);
return super.onPrepareOptionsMenu(menu);
}
From API 21 you can change it in xml:
android:searchIcon="#drawable/loupe"
android:closeIcon="#drawable/x_white"
for api level < 21, i did this:
int searchImgId = getResources().getIdentifier("android:id/search_mag_icon", null, null);
ImageView ivIcon = (ImageView) searchView.findViewById(searchImgId);
if(ivIcon!=null)
ivIcon.setImageResource(R.drawable.ic_search);
from this
to this
There are three magnifying glass icons. two of them are shown when IconizedByDefault is true(one which is shown before pressing and one is shown in the "hint") and one is shown all the time when IconizedByDefault is false. all the fields are private so the way to get them is by their xml id. (most of the code is mentioned separately in other answers in this post already)
when IconizedByDefault is true change the icon in the hint (which is seen only after pressing the icon) by :
mSearchSrcTextView = (SearchAutoComplete)findViewById(R.id.search_src_text);
then do the same as in the android source code:
final int textSize = (int) (mSearchSrcTextView.getTextSize() * 1.25);
newSearchIconDrawable.setBounds(0, 0, textSize, textSize);
final SpannableStringBuilder ssb = new SpannableStringBuilder(" ");
ssb.setSpan(new ImageSpan(newSearchIconDrawable), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.append(hintText);
mSearchHintIcon was replaced with newSearchIconDrawable which is your new search icon.
Then set the hint with
mSearchSrcTextView.setHint(ssb);
The other 2 icons are in an ImageView, which can be found by their Id.
for the icon when searchview is closed (when iconizedByDefault is true) do:
mSearchButton = (ImageView) findViewById(R.id.search_button);
and for the one that always appears (if iconizedByDefault is false)
mCollapsedIcon = (ImageView) findViewById(R.id.search_mag_icon);
Desperate solution using Kotlin
val s = (searchView.getAllChildren().firstOrNull() as? LinearLayout)?.getAllChildren()?.filter { it is AppCompatImageView }?.firstOrNull() as? AppCompatImageView
s?.setImageResource(R.drawable.search)
getAllChildren:
fun ViewGroup.getAllChildren() : ArrayList<View> {
val views = ArrayList<View>()
for (i in 0..(childCount-1)) {
views.add(getChildAt(i))
}
return views
}
Hope it helps someone.
My solution:
Use two menu xml files. In one of the xmls the menu item has an actionView and in the other one no. Initially inflate the collapsed menu and when the menu item is clicked, invalidate the menu and inflate the expanded menu xml and make sure you call setIconified(false);
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
if(!mShowSearchView)
{
inflater.inflate(R.menu.menu_collapsed, menu);
}
else
{
inflater.inflate(R.menu.menu_expanded, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setIconified(false);
searchView.setOnCloseListener(new OnCloseListener()
{
#Override
public boolean onClose()
{
mShowSearchView = false;
ActivityCompat.invalidateOptionsMenu(getActivity());
return false;
}
});
}
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (item.getItemId() == R.id.action_filter)
{
menu.showMenu();
}
else if (item.getItemId() == R.id.action_search)
{
mShowSearchView = true;
ActivityCompat.invalidateOptionsMenu(getActivity());
}
return super.onOptionsItemSelected(item);
}
Just name your icon the same name as the icon that is used by the search view. When it compiles it takes the resource in the project over the icon in the library.
I use the AppCompat library. Yes, specifying android:icon="#drawable/search_icon_png" doesnt work.
So i looked into the source code of #style/Theme.AppCompat and found the icon that android uses.
<item name="searchViewSearchIcon">#drawable/abc_ic_search</item>
So if you rename your search icon inside your drawables to abc_ic_search.png, this icon is rendered as its found in your app drawable first, rather than the appcompat drawable folder.
Works for me :)
Using this approach you can customize the close and clear icons for the search widget as well.

Honeycomb - customize SearchView inside the action bar

I have a field where the user can type a search query in the action bar of the application. This is declared in the action bar using a menu inflate in the Activity:
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
>
<item
android:id="#+id/action_search"
android:showAsAction="ifRoom"
android:actionViewClass="android.widget.SearchView"
android:title="#string/search"
></item>
</menu>
I need to customize the appearance of the SearchView (for instance background and text color). So far I could not find a way to do it using XML (using styles or themes).
Is my only option to do it in the code when inflating the menu?
Edit #1: I have tried programmatically but I cannot get a simple way to set the text color. Plus when I do searchView.setBackgroundResource(...) The background is set on the global widget, (also when the SearchView is iconified).
Edit #2: Not much information on the Search Developer Reference either
Seibelj had an answer that is good if you want to change the icons. But you'll need to
do it for every API version. I was using ICS with ActionBarSherlock and it didn't do justice for me but it did push me in the correct direction.
Below I change the text color and hint color. I showed how you might go about changing the
icons too, though I have no interest in that for now (and you probably want to use the default icons anyways to be consistent)
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
// Set up the search menu
SearchView searchView = (SearchView)menu.findItem(R.id.action_search).getActionView();
traverseView(searchView, 0);
return true;
}
private void traverseView(View view, int index) {
if (view instanceof SearchView) {
SearchView v = (SearchView) view;
for(int i = 0; i < v.getChildCount(); i++) {
traverseView(v.getChildAt(i), i);
}
} else if (view instanceof LinearLayout) {
LinearLayout ll = (LinearLayout) view;
for(int i = 0; i < ll.getChildCount(); i++) {
traverseView(ll.getChildAt(i), i);
}
} else if (view instanceof EditText) {
((EditText) view).setTextColor(Color.WHITE);
((EditText) view).setHintTextColor(R.color.blue_trans);
} else if (view instanceof TextView) {
((TextView) view).setTextColor(Color.WHITE);
} else if (view instanceof ImageView) {
// TODO dissect images and replace with custom images
} else {
Log.v("View Scout", "Undefined view type here...");
}
}
adding my take on things which is probably a little more efficient and safe across different android versions.
you can actually get a numeric ID value from a string ID name. using android's hierarchyviewer tool, you can actually find the string IDs of the things you are interested in, and then just use findViewById(...) to look them up.
the code below sets the hint and text color for the edit field itself. you could apply the same pattern for other aspects that you wish to style.
private static synchronized int getSearchSrcTextId(View view) {
if (searchSrcTextId == -1) {
searchSrcTextId = getId(view, "android:id/search_src_text");
}
return searchSrcTextId;
}
private static int getId(View view, String name) {
return view.getContext().getResources().getIdentifier(name, null, null);
}
#TargetApi(11)
private void style(View view) {
ImageView iv;
AutoCompleteTextView actv = (AutoCompleteTextView) view.findViewById(getSearchSrcTextId(view));
if (actv != null) {
actv.setHint(getDecoratedHint(actv,
searchView.getContext().getResources().getString(R.string.titleApplicationSearchHint),
R.drawable.ic_ab_search));
actv.setTextColor(view.getContext().getResources().getColor(R.color.ab_text));
actv.setHintTextColor(view.getContext().getResources().getColor(R.color.hint_text));
}
}
You can use the attribute android:actionLayout instead which lets you specify a layout to be inflated. Just have a layout with your SearchView and you won't have to modify anything really.
As to changing text style on the SearchView that is probably not possible as the SearchView is a ViewGroup. You should probably try changing text color via themes instead.
In case anyone wants to modify the views directly, here is how you can change the colors/fonts/images and customize the search box to your pleasure. It is wrapped in a try/catch in case there are differences between versions or distributions, so it won't crash the app if this fails.
// SearchView structure as we currently understand it:
// 0 => linearlayout
// 0 => textview (not sure what this does)
// 1 => image view (the search icon before it's pressed)
// 2 => linearlayout
// 0 => linearlayout
// 0 => ImageView (Search icon on the left of the search box)
// 1 => SearchView$SearchAutoComplete (Object that controls the text, subclass of TextView)
// 2 => ImageView (Cancel icon to the right of the text entry)
// 1 => linearlayout
// 0 => ImageView ('Go' icon to the right of cancel)
// 1 => ImageView (not sure what this does)
try {
LinearLayout ll = (LinearLayout) searchView.getChildAt(0);
LinearLayout ll2 = (LinearLayout) ll.getChildAt(2);
LinearLayout ll3 = (LinearLayout) ll2.getChildAt(0);
LinearLayout ll4 = (LinearLayout) ll2.getChildAt(1);
TextView search_text = (TextView) ll3.getChildAt(1);
search_text.setTextColor(R.color.search_text);
ImageView cancel_icon = (ImageView)ll3.getChildAt(2);
ImageView accept_icon = (ImageView)ll4.getChildAt(0);
cancel_icon.setBackgroundDrawable(d);
accept_icon.setBackgroundDrawable(d);
} catch (Throwable e) {
Log.e("SearchBoxConstructor", "Unable to set the custom look of the search box");
}
This example shows changing the text color and the background colors of the cancel/accept images. searchView is a SearchView object already instantiated with it's background color:
Drawable d = getResources().getDrawable(R.drawable.search_widget_background);
searchView.setBackgroundDrawable(d);
Here is the drawable code:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#color/white" />
</shape>
Obviously, this is hacky, but it will work for now.
From ICS this is doable using themes and styles. I'm using ActionBarSherlock which makes it applicable also for HC and below.
Add a style to define "android:textColorHint":
<style name="Theme.MyHolo.widget" parent="#style/Theme.Holo">
<item name="android:textColorHint">#color/text_hint_corp_dark</item>
</style>
Apply this as "actionBarWidgetTheme" to your theme:
<style name="Theme.MyApp" parent="#style/Theme.Holo.Light.DarkActionBar">
...
<item name="android:actionBarWidgetTheme">#style/Theme.MyHolo.widget</item>
</style>
Presto! Make sure that you use getSupportActionBar().getThemedContext() (or getSupportActionBar() for ActionBarSherlock) if any widgets are initiated where you might have other themes in effect.
How do you inflate the menu xml in your Activity? if you inflate the menu by using getMenuInflator() in your Activity, then the menu and also the searchView get the themed context, that have attached to the activity.
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater.inflate(R.menu.search_action_menu, menu);
}
if you check the source code of Activity.getMenuInflator() at API-15, you can see the themed context codes. Here it is.
*/
public MenuInflater getMenuInflater() {
// Make sure that action views can get an appropriate theme.
if (mMenuInflater == null) {
initActionBar();
if (mActionBar != null) {
mMenuInflater = new MenuInflater(mActionBar.getThemedContext());
} else {
mMenuInflater = new MenuInflater(this);
}
}
return mMenuInflater;
}

Categories

Resources