Change the actionbar homeAsUpIndicator Programmatically - android

I used the following hack to change the homeAsupIndicator programmatically.
int upId = Resources.getSystem().getIdentifier("up", "id", "android");
if (upId > 0) {
ImageView up = (ImageView) findViewById(upId);
up.setImageResource(R.drawable.ic_action_bar_menu);
up.setPadding(0, 0, 20, 0);
}
But this is not working on most new phones (HTC One, Galaxy S3, etc). Is there a way that can be changed uniformly across devices. I need it to be changed only on home screen. Other screens would have the default one. So cannot use the styles.xml

This is what i did to acheive the behavior. I inherited the base theme and created a new theme to use it as a theme for the specific activity.
<style name="CustomActivityTheme" parent="AppTheme">
<item name="android:homeAsUpIndicator">#drawable/custom_home_as_up_icon</item>
</style>
and in the android manifest i made the activity theme as the above.
<activity
android:name="com.example.CustomActivity"
android:theme="#style/CustomActivityTheme" >
</activity>
works great. Will update again when i check on all devices I have. Thanks #faylon for pointing in the right direction

The question was to change dynamically the Up Home Indicator, although this answer was accepting and it is about Themes and Styles. I found a way to do this programmatically, according to Adneal's answer which gives me the clue and specially the right way to do. I used the below snippet code and it works well on (tested) devices with APIs mentioned here.
For lower APIs, I use R.id.up which is not available on higher API. That's why, I retrieve this id by a little workaround which is getting the parent of home button (android.R.id.home) and its first child (android.R.id.up):
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// get the parent view of home (app icon) imageview
ViewGroup home = (ViewGroup) findViewById(android.R.id.home).getParent();
// get the first child (up imageview)
( (ImageView) home.getChildAt(0) )
// change the icon according to your needs
.setImageResource(R.drawable.custom_icon_up));
} else {
// get the up imageview directly with R.id.up
( (ImageView) findViewById(R.id.up) )
.setImageResource(R.drawable.custom_icon_up));
}
Note: If you don't use the SDK condition, you will get some NullPointerException.

API 18 has new methods ActionBar.setHomeAsUpIndicator() - unfortunately these aren't supported in the support library at this moment
http://developer.android.com/reference/android/app/ActionBar.html#setHomeAsUpIndicator(android.graphics.drawable.Drawable)
edit: these are now supported by the support library
http://developer.android.com/reference/android/support/v7/app/ActionBar.html#setHomeAsUpIndicator(android.graphics.drawable.Drawable)

All you need to do is to use this line of code:
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
This will change the icon with the up indicator. To disable it later, just call this function again and pass false as the param.

The solution by checking Resources.getSystem() doesn't work on all devices, A better solution to change the homeAsUpIndicator is to set it #null in style and change the logo resource programmatically.
Below is my code from style.xml
<style name="Theme.HomeScreen" parent="AppBaseTheme">
<item name="displayOptions">showHome|useLogo</item>
<item name="homeAsUpIndicator">#null</item>
<item name="android:homeAsUpIndicator">#null</item>
</style>
In code you can change the logo using setLogo() method.
getSupportActionBar().setLogo(R.drawable.abc_ic_ab_back_holo_light); //for ActionBarCompat
getActionBar().setLogo(R.drawable.abc_ic_ab_back_holo_light); //for default actionbar for post 3.0 devices
Also note that the Android API 18 has methods to edit the homeAsUpIndicator programatically, refer documentation.

You can achieve this in an easier way. Try to can change the homeAsUpIndicator attribute of actionBarStyle in your theme.xml and styles.xml.
If you want some padding, just add some white space in your image.

You can try this:
this.getSupportActionBar().setHomeAsUpIndicator( R.drawable.actionbar_indicator ); //for ActionBarCompat
this.getActionBar().setHomeAsUpIndicator( R.drawable.actionbar_indicator ); //for default actionbar for post 3.0 devices
If you need change the position of the icon, you must create a drawable file containing a "layer-list" like this:
actionbar_indicator.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="#drawable/indicator"
android:right="5dp"
android:left="10dp" />
</layer-list>

use getActionBar().setCustomView(int yourView); because ActionBar haven't method to change homeUp icon!

Adding to Fllo answer Change the actionbar homeAsUpIndicator Programamtically
I was able to use this hack on Android 4+ but could not understand why the up/home indicator was back to the default one when search widget was expanded. Looking at the view hierarchy, turns out that the up/home indicator + icon section of the action bar has 2 implementations and of course the first on is the one for when the search widget is not expanded. So here is the code I used to work around this and get the up/home indicator changed in both cases.
mSearchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
#Override
public boolean onMenuItemActionExpand(MenuItem item) {
// https://stackoverflow.com/questions/17585892/change-the-actionbar-homeasupindicator-programamtically
int actionBarId = getResources().getIdentifier("android:id/action_bar", null, null);
View view = getActivity().getWindow().getDecorView().findViewById(actionBarId);
if (view == null
|| !(view instanceof ViewGroup)) {
return true;
}
final ViewGroup actionBarView = (ViewGroup)view;
// The second home view is only inflated after
// setOnActionExpandListener() is first called
actionBarView.post(new Runnable() {
#Override
public void run() {
//The 2 ActionBarView$HomeView views are always children of the same view group
//However, they are not always children of the ActionBarView itself
//(depends on OS version)
int upId = getResources().getIdentifier("android:id/up", null, null);
View upView = actionBarView.findViewById(upId);
ViewParent viewParent = upView.getParent();
if (viewParent == null) {
return;
}
viewParent = viewParent.getParent();
if (viewParent == null
|| !(viewParent instanceof ViewGroup)) {
return;
}
ViewGroup viewGroup = (ViewGroup) viewParent;
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = viewGroup.getChildAt(i);
if (childView instanceof ViewGroup) {
ViewGroup homeView = (ViewGroup) childView;
upView = homeView.findViewById(upId);
if (upView != null
&& upView instanceof ImageView) {
Drawable upDrawable = getResources().getDrawable(R.drawable.ic_ab_back_holo_dark_am);
upDrawable.setColorFilter(accentColorInt, PorterDuff.Mode.MULTIPLY);
((ImageView) upView).setImageDrawable(upDrawable);
}
}
}
}
});

If someone uses the library support-v7 appcompat, you can directly call this method:
getSupportActionBar().setHomeAsUpIndicator(int redId)
In other case you can use this solution:
https://stackoverflow.com/a/23522910/944630

If you are using DrawerLayout with ActionBarDrawerToggle, then check out this answer.

this.getSupportActionBar().setDisplayUseLogoEnabled(true);
this.getSupportActionBar().setLogo(R.drawable.about_selected);
Also you can define the logo in manifest in attribute android:logo of and tags and set in theme that you want to use logo instead of app icon in the action bar.

Related

Programmatically added buttons not matching theme

Using Theme.AppCompat in Gingerbread (API 10), programmatically added buttons do not match buttons added through XML. It works fine in all newer APIs, its only an issue with Gingerbread. This image shows the issue.
Here is the code that adds the buttons:
for (int i = 0; i < btnFiles.length; i++) {
btnFiles[i] = new Button(this);
btnFiles[i].setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
btnFiles[i].setGravity(Gravity.CENTER);
btnFiles[i].setId(100 + i);
btnFiles[i].setText(fileList.get(i).replace(".xml", ""));
btnFiles[i].setTag(fileList.get(i));
registerForContextMenu(btnFiles[i]);
btnFiles[i].setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Continue(v); //Start next activity when button is pressed
}
});
l.addView(btnFiles[i]);
setTitle(getString(R.string.title_activity_load_menu));
}
Make a layout file with just the Button and use a LayoutInflater to inflate it.
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
... />
LayoutInflater inflater = getLayoutInflater();
for (int i = 0; i < btnFiles.length; i++) {
btnFiles[i] = (Button) inflater.inflate(R.layout.button, l, false);
// everything else, except the LayoutParams stuff because that's in the layout file
}
Just to clarify for anyone passing by, the issue is probably that <Button> tags from XML get replaced by instances of AppCompatButton and <item name="buttonStyle">...</item> (assuming that's what had been used) applies to that. Same thing happens for many other Views.
So, an alternative possibility would be either to put both <item name="buttonStyle">...</item> and <item name="android:buttonStyle">...</item> into the style, so that Button and AppCompatButton can be combined, which would be quite a mess.
Somewhat better option would be to instantiate AppCompatButton for the APIs using AppCompat, but using the XML layout with just a single Button seems like the safest and most portable solution, so go for that, assuming you have no reason not to do that.

Change TapDown Color on ListView

I have a list view and when the user taps down on the item it looks like this.
This is what it looks like noramally:
How do I change this default light blue color?
I need the color to be set programatically in the activity file, as it is not always the same color, and also how do you change the color of the fuzzy stuff that comes up at the bottom of a ListView when you continue to scroll down?
Thanks for the help
EDIT
Also how do you change the tap down color of the action bar back button,
For the row color, you need to create a StateListDrawable and set it as the selector for the ListView.
The states to include should be, at least:
pressed (android.R.attr.state_pressed)
selected (android.R.attr.state_selected)
"normal" (empty state list)
For each state you can set any drawable. If what you need is a plain color, you can programmatically create a ColorDrawable on the spot.
For example:
StateListDrawable selector = new StateListDrawable();
ColorDrawable red = new ColorDrawable(Color.RED);
ColorDrawable transparent = new ColorDrawable(Color.TRANSPARENT);
selector.addState(new int[] { android.R.attr.state_pressed }, red);
selector.addState(new int[] { android.R.attr.state_selected }, red);
selector.addState(new int[] { }, transparent);
listView.setSelector(selector);
As for the "fuzzy stuff that comes up at the bottom", it's slightly more complicated.
Prior to Android 5.0 there was no public API for changing it.
In Lollipop it can be set via the theme, but not programmatically.
For pre-5.0 there is a well-known workaround (described here) which involves tampering with the drawables that are used. However this solution crashes in 5.0, because those resources do not exist anymore.
For Android 5.0, if a static color is acceptable, you can just configure the new theme attribute android:colorEdgeEffect (which is the same as android:colorPrimary by default).
If a programmatic solution for Android 5.0 is necessary, you could alternatively change the EdgeEffect objects that the ListView has internally using reflection. I've tested this and it works, though it's not the prettiest code:
EdgeEffect edgeEffectTop = new EdgeEffect(this);
edgeEffectTop.setColor(Color.RED);
EdgeEffect edgeEffectBottom = new EdgeEffect(this);
edgeEffectBottom.setColor(Color.RED);
try {
Field f1 = AbsListView.class.getDeclaredField("mEdgeGlowTop");
f1.setAccessible(true);
f1.set(listView, edgeEffectTop);
Field f2 = AbsListView.class.getDeclaredField("mEdgeGlowBottom");
f2.setAccessible(true);
f2.set(listView, edgeEffectBottom);
} catch (Exception e) {
e.printStackTrace();
}
Therefore, a full programmatic solution may run something like this:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
// For Android >= 5.0, use setColor() on EdgeEffect
EdgeEffect edgeEffectTop = new EdgeEffect(this);
edgeEffectTop.setColor(Color.RED);
EdgeEffect edgeEffectBottom = new EdgeEffect(this);
edgeEffectBottom.setColor(Color.RED);
try {
Field f1 = AbsListView.class.getDeclaredField("mEdgeGlowTop");
f1.setAccessible(true);
f1.set(listView, edgeEffectTop);
Field f2 = AbsListView.class.getDeclaredField("mEdgeGlowBottom");
f2.setAccessible(true);
f2.set(listView, edgeEffectBottom);
} catch (Exception e) {
e.printStackTrace();
}
}
else
{
// For Android < 5.0, change overscroll_glow and overscroll_edge
int glowDrawableId = getResources().getIdentifier("overscroll_glow", "drawable", "android");
Drawable androidGlow = getResources().getDrawable(glowDrawableId);
androidGlow.setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
int edgeDrawableId = getResources().getIdentifier("overscroll_edge", "drawable", "android");
Drawable androidEdge = getResources().getDrawable(edgeDrawableId);
androidEdge.setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
}
How do I change this default light blue color?
you have to create StateListDrawable and set it as the selector for the ListView. the answer of matiash is good and I do not want to explain it again.
change the color of the fuzzy stuff that comes up at the bottom of a
ListView when you continue to scroll down
setCacheColorHint (int color)
Also how do you change the tap down color of the action bar back
button
<style name="actionbarCustomBackground" parent="#style/Theme.Holo.Light" >
<item name="android:selectableItemBackground">#drawable/actionbar_item_background</item>
</style>
then in the actionbar_item_background:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="#android:integer/config_mediumAnimTime">
<item
android:state_focused="true"
android:state_pressed="true"
android:drawable="#android:color/holo_orange_dark" />
<item
android:state_pressed="true"
android:drawable="#android:color/holo_orange_dark" />
<item
android:drawable="#android:color/transparent" />
</selector>
You can set row's background dynamically in getView(..) method.
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
.......... //your code
view.setBackgroundColor(Color.RED)
return view;
}

How to change home as up resource programmatically?

I know how to change the homeAsUpIndicator in the styles xml file. The question is how to change it programmatically.
The reason I want to do it because in some views I support side navigation (sliding menu) - pressing the up/back title button, shows the side menu.
In other views I support the natural up/back botton.
Thus I would like to different indicator icons to indicate the two different logics - side navigation vs. up/back.
Please, lets not argue on the motivation of doing this. That's the given state. Thanks.
int upId = Resources.getSystem().getIdentifier("up", "id", "android");
if (upId > 0) {
ImageView up = (ImageView) findViewById(upId);
up.setImageResource(R.drawable.ic_drawer_indicator);
}
The solution by #matthias doesn't work on all devices, A better solution to change the homeAsUpIndicator is to set it #null in style and change the logo resource programmatically.
Below is my code from style.xml
<style name="Theme.HomeScreen" parent="AppBaseTheme">
<item name="displayOptions">showHome|useLogo</item>
<item name="homeAsUpIndicator">#null</item>
<item name="android:homeAsUpIndicator">#null</item>
</style>
In code you can change the logo using setLogo() method.
getSupportActionBar().setLogo(R.drawable.abc_ic_ab_back_holo_light); //for ActionBarCompat
getActionBar().setLogo(R.drawable.abc_ic_ab_back_holo_light); //for default actionbar for post 3.0 devices
Also note that the Android API 18 has methods to edit the homeAsUpIndicator programatically, refer documentation.
This worked for me
getSupportActionBar().setHomeAsUpIndicator(R.drawable.my_home_as_up)
Although this answer might achieve the expected behaviour, as you can read in the comments below it: "This hack does not work on some devices". I found another way according to Adneal's answer which gives me the clue and specially the right way to do.
Lower API: use id R.id.up to retrieve the related ImageView.
API >= 14: get the relative Parent of Home ImageView (android.R.id.home) and retrieve the first child which is the UpIndicator (android.R.id.up).
Then, this snippet code changes dynamically the UpIndicator:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// get the parent view of home (app icon) imageview
ViewGroup home = (ViewGroup) findViewById(android.R.id.home).getParent();
// get the first child (up imageview)
( (ImageView) home.getChildAt(0) )
// change the icon according to your needs
.setImageDrawable(getResources().getDrawable(R.drawable.custom_icon_up));
} else {
// get the up imageview directly with R.id.up
( (ImageView) findViewById(R.id.up) )
.setImageDrawable(getResources().getDrawable(R.drawable.custom_icon_up));
}
I have not tested on multiple devices (that's why I'm not sure this above code works for all devices), however this seems to work great in APIs mentioned in the update part here.
Note: If you don't make the difference between higher and lower APIs, you will get a NullPointerException because R.id.up is not available in higher API while android.R.id.up is not available in lower API.
I write solution for this. It's not beautiful but works:
public static ImageView getHomeAndUpIndicator(View decorView) {
ImageView res = null;
int upId = Resources.getSystem().getIdentifier("up", "id", "android");
if (upId > 0) {
res = (ImageView) decorView.findViewById(upId);
} else {
if (android.os.Build.VERSION.SDK_INT <= 10) {
ViewGroup acbOverlay = (ViewGroup)((ViewGroup)decorView).getChildAt(0);
ViewGroup abcFrame = (ViewGroup)acbOverlay.getChildAt(0);
ViewGroup actionBar = (ViewGroup)abcFrame.getChildAt(0);
ViewGroup abLL = (ViewGroup)actionBar.getChildAt(0);
ViewGroup abLL2 = (ViewGroup)abLL.getChildAt(1);
res = (ImageView)abLL2.getChildAt(0);
} else if (android.os.Build.VERSION.SDK_INT > 10 && android.os.Build.VERSION.SDK_INT < 16) {
ViewGroup acbOverlay = (ViewGroup)((ViewGroup)decorView).getChildAt(0);
ViewGroup abcFrame = (ViewGroup)acbOverlay.getChildAt(0);
ViewGroup actionBar = (ViewGroup)abcFrame.getChildAt(0);
ViewGroup abLL = (ViewGroup)actionBar.getChildAt(1);
res = (ImageView)abLL.getChildAt(0);
} else {
ViewGroup acbOverlay = (ViewGroup)((ViewGroup)decorView).getChildAt(0);
ViewGroup abcFrame = (ViewGroup)acbOverlay.getChildAt(1);
ViewGroup actionBar = (ViewGroup)abcFrame.getChildAt(0);
ViewGroup abLL = (ViewGroup)actionBar.getChildAt(0);
ViewGroup abF = (ViewGroup)abLL.getChildAt(0);
res = (ImageView)abF.getChildAt(0);
}
}
return res;
}
As a param put: getWindow().getDecorView()
Test on few devices (nexus 7 (4.4.2), samsung galaxy s+ (2.3.6), yaiu g3 (4.2.2)) and emulators with android 2.3.3 and 4.1.1
Here is working code
final Drawable upArrow = getResources().getDrawable(R.drawable.ic_arrow_back_black_24dp);
upArrow.setColorFilter(Color.parseColor("#000000"), PorterDuff.Mode.SRC_ATOP);
getSupportActionBar().setHomeAsUpIndicator(upArrow);

ActionBarSherlock (ABS): how to customize the text of action mode close item?

I'm using ABS vers. 4 and I need to simply change the default "Done" text that is displayed besides the action mode close icon, but I really can't figure out how to do it.
I think that text needs to be customizable for at least two good reasons:
"Done" is not appropriate for all contexts (e.g. "Cancel" could be more appropriate, and I've seen some apps, such as the "My Files" app on Galaxy Tab, use it)
"Done" needs to be localized according to the user's language
Is it possible to do customize that text? If so can anyone tell me how to do it?
Thanks in advance.
EDIT
I've found a temporary workaround, that I post in the following:
private TextView getActionModeCloseTextView() {
// ABS 4.0 defines action mode close button text only for "large" layouts
if ((getResources().getConfiguration().screenLayout &
Configuration.SCREENLAYOUT_SIZE_MASK) ==
Configuration.SCREENLAYOUT_SIZE_LARGE)
{
// retrieves the LinearLayout containing the action mode close button text
LinearLayout action_mode_close_button =
(LinearLayout) getActivity().findViewById(R.id.abs__action_mode_close_button);
// if found, returns its last child
// (in ABS 4.0 there is no other way to refer to it,
// since it doesn't have an id nor a tag)
if (action_mode_close_button != null) return (TextView)
action_mode_close_button.getChildAt(action_mode_close_button.getChildCount() - 1);
}
return null;
}
That's the method I came up with. Please NOTE that it does heavily rely upon the structure of the abs__action_mode_close_item.xml of ABS 4.0.
This works for my scenario, but, as you can see, it cannot be considered sufficiently satisfying to promote it to a real "answer", that's why I only edited my previous post.
Hope that helps someone else, but I also hope that someone could share a better and cleaner solution.
You can use a theme to override the default icon:
<item name="actionModeCloseDrawable">#drawable/navigation_back</item>
<item name="android:actionModeCloseDrawable">#drawable/navigation_back</item>
I edited the code from PacificSky to be able to customize the color and font size of the close button, both in pre ICS and >ICS.
I created a method named customizeActionModeCloseButton
private void customizeActionModeCloseButton() {
int buttonId = Resources.getSystem().getIdentifier("action_mode_close_button", "id", "android");
View v = getGSActivity().findViewById(buttonId);
if (v == null) {
buttonId = R.id.abs__action_mode_close_button;
v = getGSActivity().findViewById(buttonId);
}
if (v == null)
return;
LinearLayout ll = (LinearLayout) v;
if (ll.getChildCount() > 1 && ll.getChildAt(1) != null) {
TextView tv = (TextView) ll.getChildAt(1);
tv.setText(R.string.close_action_mode);
tv.setTextColor(getResources().getColor(R.color.white));
tv.setTextSize(18);
}
}
and I call it just after calling startActionMode()
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
actionMode = getActivity().startActionMode(this);
customizeActionModeCloseButton();
return true;
}
It's been a while, but here's a slightly less hacky solution - putting it out there for posterity.
For Android versions < ICS
Put the following line in your application's strings.xml:
<string name="abs__action_mode_done">Cancel</string>
This overrides the TextView's (defined in ActionBarSherlock/res/layout-large/abs__action_mode_close_item.xml) android:text attribute.
For Android versions ICS and above
The native ActionBar functionality is used on ICS and up. You need to find and override the string associated with the done button, using the following code:
int buttonId = Resources.getSystem().getIdentifier("action_mode_close_button", "id", "android");
if (buttonId != 0)
{
View v = findViewById(buttonId);
if (v != null)
{
LinearLayout ll = (LinearLayout)v;
View child = ll.getChildAt(1);
if (child != null)
{
TextView tv = (TextView)child;
tv.setText(R.string.cancel);
}
}
}
Thanks for PacificSky's answer. It's useful for my case.
Something needs to be explained here is that findViewById(buttonId) might return null in some cases such as called in onCreateActionMode() function, because the LinearLayout for ActionMode close button not yet initialized at that time I guess.
I want to hide the action mode close button, so i just sendEmptyMessageDelayed in onCreateActionMode() and call PacificSky's 200ms later. It works for me.
Here is my approach with Java code:
private void customizeActionModeCloseButton(String title, int iconID) {
int buttonId = Resources.getSystem().getIdentifier("action_mode_close_button", "id", "android");
View v = findViewById(buttonId);
if (v == null) {
buttonId = R.id.abs__action_mode_close_button;
v = findViewById(buttonId);
}
if (v == null)
return;
LinearLayout ll = (LinearLayout) v;
if (ll.getChildCount() > 1 && ll.getChildAt(1) != null) {
//custom icon
ImageView img = (ImageView) ll.getChildAt(0);
img.setImageResource(iconID);
//custom text
TextView tv = (TextView) ll.getChildAt(1);
tv.setText(title);
tv.setTextColor(Color.WHITE);
}
}
com.actionbarsherlock.view.ActionMode contains method:
setTitle
It is used to change text near Close Icon in the ActionBar.
ActionMode is available in your com.actionbarsherlock.view.ActionMode.Callback interface implementation methods, like onCreateActionMode.
What you can do - is save incoming ActionMode reference and use it later to change title as your like. Or, if it is not dynamic - you can setup at with your constant in onCreateActionMode.

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