Android: safe way to hide navigation spinner in ActionBar? - android

I would like to show a spinner in my ActionBar, using ActionBar.NAVIGATION_MODE_LIST, but I would like it to hide/show based on some application context. I have found that I can remove it from the ActionBar with getActionBar().setNavigationMode(-1), however I don't know if this is a good idea.
Any feedback on if this is safe or if there is a safer alternative?

Maybe this is more accepted:
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setDisplayShowTitleEnabled(false);

If you stick to ActionBar.NAVIGATION_MODE_LIST, you will have to set navigation listener every time you want to show your spinner back. That is obviously not the best soluton.
Instead, you may want to use ActionBar.setCustomView() to set spinner navigation (reference).
Here is some sample code where you set the spinner:
Spinner navigationSpinner = new Spinner(this);
navigationSpinner.setAdapter(yourSpinnerAdapter);
// Here you set navigation listener
navigationSpinner.setOnItemSelectedListener(yourSpinnerNavigationListener);
getActionBar().setCustomView(navigationSpinner);
getActionBar().setDisplayShowCustomEnabled(true);
Then, when you want to show/hide it you simply change it's visibility:
getActionBar().getCustomView().setVisibility(View.INVISIBLE);

Just modify your implementation of ActionBarDrawerToggle like this:
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
if (slideOffset == 0) { // 0 = drawer is closed
setActionBarNavigationVisibility(activity, true); //show Tabs when Drawer is closed
}
}
public void onDrawerStateChanged(int newState) {
super.onDrawerStateChanged(newState);
//hides Tabs right after Drawer starts opening
if (DrawerLayout.STATE_DRAGGING == newState || DrawerLayout.STATE_SETTLING == newState) {
setActionBarNavigationVisibility(activity, false);
}
}
Where method setActionBarNavigationVisibility is considering all navigation modes (you can delete code for unnecesarry navigation modes):
public static void setActionBarNavigationVisibility(Activity activity, boolean visible) {
try {
/* 1. --- If the navigation items are showing in ActionBar directly. We have 3 options Spinner, Tabs, and CustomNav ---
(When Tabs are showing BELOW ActionBar, is handled at the end) */
int actionViewResId = Resources.getSystem().getIdentifier("action_bar", "id", "android"); // #see http://stackoverflow.com/questions/20023483/how-to-get-actionbar-view
View actionBarView = activity.findViewById(actionViewResId); // returns instance of com.android.internal.widget.ActionBarView (inaccessible)
if (actionBarView != null) {
int visibility = visible ? View.VISIBLE : View.INVISIBLE; // not GONE, so it still takes space in ActionBar layout
// handle tabs navigation
Field mTabScrollViewField = actionBarView.getClass().getDeclaredField("mTabScrollView");
if (mTabScrollViewField != null) {
mTabScrollViewField.setAccessible(true);
View mTabScrollView = (View) mTabScrollViewField.get(actionBarView); // instance of com.android.internal.widget.ScrollingTabContainerView (inaccessible)
if (mTabScrollView != null)
mTabScrollView.setVisibility(visibility);
}
// handle Spinner navigation
Field mSpinnerField = actionBarView.getClass().getDeclaredField("mSpinner"); // resp. mListNavLayout
if (mSpinnerField != null) {
mSpinnerField.setAccessible(true);
View mSpinner = (View) mSpinnerField.get(actionBarView); // instance of android.widget.Spinner
if (mSpinner != null)
mSpinner.setVisibility(visibility);
}
// handle Custom navigation
Field mCustomNavViewField = actionBarView.getClass().getDeclaredField("mCustomNavView"); // resp. mListNavLayout
if (mCustomNavViewField != null) {
mCustomNavViewField.setAccessible(true);
View mCustomNavView = (View) mCustomNavViewField.get(actionBarView);
if (mCustomNavView != null)
mCustomNavView.setVisibility(visibility);
}
}
// 2. --- If the Tabs are BELOW ActionBar (narrow screens) ---
ViewParent actionBarContainer = actionBarView.getParent(); // parent of ActionBarView is com.android.internal.widget.ActionBarContainer (inaccessible)
Field mTabContainerField = actionBarContainer.getClass().getDeclaredField("mTabContainer");
if (mTabContainerField != null) {
mTabContainerField.setAccessible(true);
View mmTabContainer = (View) mTabContainerField.get(actionBarContainer);
if (mmTabContainer != null)
mmTabContainer.setVisibility(visible ? View.VISIBLE : View.GONE); // now use GONE, so the mTabContainer below Actionbar does not take space in layout
}
} catch (Exception ex) {
// TODO Handle exception...
}
}

Related

Tint menu icons in overflow menu and submenus

I managed to show icons in the toolbar's overflow menu and submenus, but I couldn't find how to tint the icons according to their position. Here the code I'm using:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
// Show icons in overflow menu
if (menu instanceof MenuBuilder) {
MenuBuilder m = (MenuBuilder) menu;
m.setOptionalIconsVisible(true);
}
// Change icons color
changeIconsColor(menu, colorNormal, colorInMenu, false);
return super.onCreateOptionsMenu(menu);
}
public static void changeIconsColor(Menu menu, int colorNormal, int colorInMenu, boolean isInSubMenu) {
// Change icons color
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
Drawable icon = item.getIcon();
if (icon != null) {
int color = (((MenuItemImpl) item).requiresActionButton() ? colorNormal : colorInMenu);
icon.setColorFilter(color, PorterDuff.Mode.SRC_IN);
icon.setAlpha(item.isEnabled() ? 255 : 128);
}
if (item.hasSubMenu()) {
changeIconsColor(item.getSubMenu(), colorNormal, colorInMenu, true);
}
}
}
The use of MenuItem.requiresActionButton() allows to know if an item has the values never or always in the showAsAction attribute in XML, but not if it has the ifRoom value. Because of this, I cannot use the ifRoom value in items if I want proper tinting, it's very restrictive.
Is there a way to tint menu items properly in all cases?
More importantly, is there a built-in way to tint items with themes or styles that would save me from using this complex piece of code? Even if a solution that doesn't cover icons in overflow menu, I would like to know about it.
I am perfectly fine with using reflection if there is no other way.
Unfortunately, there is no way to set the menu item icon color's tint using a theme or style. You need a method to check if the MenuItem is visible on the ActionBar or in the overflow menu. Both the native and support MenuItemImpl class have a method for this but they are either restricted to the library or hidden. This requires reflection. You can use the following method to check if the menu item is visible or not, and then set the color filter:
public static boolean isActionButton(#NonNull MenuItem item) {
if (item instanceof MenuItemImpl) {
return ((MenuItemImpl) item).isActionButton();
} else {
// Not using the support library. This is a native MenuItem. Reflection is needed.
try {
Method m = item.getClass().getDeclaredMethod("isActionButton");
if (!m.isAccessible()) m.setAccessible(true);
return (boolean) m.invoke(item);
} catch (Exception e) {
return false;
}
}
}
You also need to wait until the menu is inflated before tinting the items. To accomplish this you can get a reference to the ActionBar and tint the MenuItem after the ActionBar has been drawn.
Example:
#Override public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
int id = getResources().getIdentifier("action_bar", "id", "android");
ViewGroup actionBar;
if (id != 0) {
actionBar = (ViewGroup) findViewById(id);
} else {
// You must be using a custom Toolbar. Use the toolbar view instead.
// actionBar = yourToolbar
}
actionBar.post(new Runnable() {
#Override public void run() {
// Add code to tint menu items here
}
});
return super.onCreateOptionsMenu(menu);
}
Here is a class I wrote to help with tinting menu item icons: https://gist.github.com/jaredrummler/7816b13fcd5fe1ac61cb0173a1878d4f
Thanks to #JaredRummler, I found a way to determine whether an icon is in the overflow menu or not. I posted the complete code here that gathers the elements of his answer. I also added a helper methods for getting the right colors for tinting icons. Here's what I currently use:
ThemeUtils
public final class ThemeUtils {
/**
* Obtain colors of a context's theme from attributes
* #param context themed context
* #param colorAttrs varargs of color attributes
* #return array of colors in the same order as the array of attributes
*/
public static int[] getColors(Context context, int... colorAttrs) {
TypedArray ta = context.getTheme().obtainStyledAttributes(colorAttrs);
int[] colors = new int[colorAttrs.length];
for (int i = 0; i < colorAttrs.length; i++) {
colors[i] = ta.getColor(i, 0);
}
ta.recycle();
return colors;
}
/**
* Get the two colors needed for tinting toolbar icons
* The colors are obtained from the toolbar's theme and popup theme
* These themes are obtained from {#link R.attr#toolbarTheme} and {#link R.attr#toolbarPopupTheme}
* The two color attributes used are:
* - {#link android.R.attr#textColorPrimary} for the normal color
* - {#link android.R.attr#textColorSecondary} for the color in a menu
* #param context activity context
* #return int[2]{normal color, color in menu}
*/
public static int[] getToolbarColors(Context context) {
// Get the theme and popup theme of a toolbar
TypedArray ta = context.getTheme().obtainStyledAttributes(
new int[]{R.attr.toolbarTheme, R.attr.toolbarPopupTheme});
Context overlayTheme = new ContextThemeWrapper(context, ta.getResourceId(0, 0));
Context popupTheme = new ContextThemeWrapper(context, ta.getResourceId(1, 0));
ta.recycle();
// Get toolbar colors from these themes
int colorNormal = ThemeUtils.getColors(overlayTheme, android.R.attr.textColorPrimary)[0];
int colorInMenu = ThemeUtils.getColors(popupTheme, android.R.attr.textColorSecondary)[0];
return new int[]{colorNormal, colorInMenu};
}
/**
* Change the color of the icons of a menu
* Disabled items are set to 50% alpha
* #param menu targeted menu
* #param colorNormal normal icon color
* #param colorInMenu icon color for popup menu
* #param isInSubMenu whether menu is a sub menu
*/
private static void changeIconsColor(View toolbar, Menu menu, int colorNormal, int colorInMenu, boolean isInSubMenu) {
toolbar.post(() -> {
// Change icons color
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
changeMenuIconColor(item, colorNormal, colorInMenu, isInSubMenu);
if (item.hasSubMenu()) {
changeIconsColor(toolbar, item.getSubMenu(), colorNormal, colorInMenu, true);
}
}
});
}
public static void changeIconsColor(View toolbar, Menu menu, int colorNormal, int colorInMenu) {
changeIconsColor(toolbar, menu, colorNormal, colorInMenu, false);
}
/**
* Change the color of a single menu item icon
* #param item targeted menu item
* #param colorNormal normal icon color
* #param colorInMenu icon color for popup menu
* #param isInSubMenu whether item is in a sub menu
*/
#SuppressLint("RestrictedApi")
public static void changeMenuIconColor(MenuItem item, int colorNormal, int colorInMenu, boolean isInSubMenu) {
if (item.getIcon() != null) {
Drawable icon = item.getIcon().mutate();
int color = (((MenuItemImpl) item).isActionButton() && !isInSubMenu ? colorNormal : colorInMenu);
icon.setColorFilter(color, PorterDuff.Mode.SRC_IN);
icon.setAlpha(item.isEnabled() ? 255 : 128);
item.setIcon(icon);
}
}
}
ActivityUtils
public final class ActivityUtils {
/**
* Force show the icons in the overflow menu and submenus
* #param menu target menu
*/
public static void forceShowMenuIcons(Menu menu) {
if (menu instanceof MenuBuilder) {
MenuBuilder m = (MenuBuilder) menu;
m.setOptionalIconsVisible(true);
}
}
/**
* Get the action bar or toolbar view in activity
* #param activity activity to get from
* #return the toolbar view
*/
public static ViewGroup findActionBar(Activity activity) {
int id = activity.getResources().getIdentifier("action_bar", "id", "android");
ViewGroup actionBar = null;
if (id != 0) {
actionBar = activity.findViewById(id);
}
if (actionBar == null) {
return findToolbar((ViewGroup) activity.findViewById(android.R.id.content).getRootView());
}
return actionBar;
}
private static ViewGroup findToolbar(ViewGroup viewGroup) {
ViewGroup toolbar = null;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View view = viewGroup.getChildAt(i);
if (view.getClass() == android.support.v7.widget.Toolbar.class ||
view.getClass() == android.widget.Toolbar.class) {
toolbar = (ViewGroup) view;
} else if (view instanceof ViewGroup) {
toolbar = findToolbar((ViewGroup) view);
}
if (toolbar != null) {
break;
}
}
return toolbar;
}
}
I also defined two attributes in attrs.xml: toolbarTheme and toolbarPopupTheme that I set on my toolbar layout in XML. Their values are defined in my app theme in themes.xml. These attributes are used by ThemeUtils.getToolbarColors(Context) to obtain the colors to use for tinting icons, because toolbars often use theme overlays. By doing this, I can change every toolbar's theme only by changing the value of these 2 attributes.
All that is left is calling the following in the activity's onCreateOptionsMenu(Menu menu):
ActivityUtils.forceShowMenuIcons(menu); // Optional, show icons in overflow and submenus
View toolbar = ActivityUtils.findActionBar(this); // Get the action bar view
int[] toolbarColors = ThemeUtils.getToolbarColors(this); // Get the icons colors
ThemeUtils.changeIconsColor(toolbar, menu, toolbarColors[0], toolbarColors[1]);
The same can be done in a fragment by replacing this with getActivity().
When updating a MenuItem icon, another method can be called, ThemeUtils.changeMenuIconColor(). In this case, toolbar colors can be obtained in onCreate and stored globally to reuse them.
Here's a solution that works with the material components MaterialToolbar:
Explanation
The code checks all sub views of the toolbar => those are the visible items
it iterates all menu items recursively and checks if the menu id is part of the visible view ids, if so, this means the menu item is on the toolbar, otherwise it is inside the overflow menu
it then tints the icons based on its position
it also tints the overflow icon
to tint the sub menu arrow indicator correctly, check out following issue: https://github.com/material-components/material-components-android/issues/553
Code
fun View.getAllChildrenRecursively(): List<View> {
val result = ArrayList<View>()
if (this !is ViewGroup) {
result.add(this)
} else {
for (index in 0 until this.childCount) {
val child = this.getChildAt(index)
result.addAll(child.getAllChildrenRecursively())
}
}
return result
}
#SuppressLint("RestrictedApi")
fun MaterialToolbar.tintAndShowIcons(colorOnToolbar: Int, colorInOverflow: Int) {
(menu as? MenuBuilder)?.setOptionalIconsVisible(true)
val c1 = ColorStateList.valueOf(colorOnToolbar)
val c2 = PorterDuffColorFilter(colorInOverflow, PorterDuff.Mode.SRC_IN)
val idsShowing = ArrayList<Int>()
getAllChildrenRecursively().forEach {
// Icon in Toolbar
(it as? ActionMenuItemView)?.let {
idsShowing.add(it.id)
}
// Overflow Icon
(it as? ImageView)?.imageTintList = c1
}
menu.forEach {
checkOverflowMenuItem(it, c2, idsShowing)
}
}
private fun checkOverflowMenuItem(menuItem: MenuItem, iconColor: ColorFilter, idsShowing: ArrayList<Int>) {
// Only change Icons inside the overflow
if (!idsShowing.contains(menuItem.itemId)) {
menuItem.icon?.colorFilter = iconColor
}
menuItem.subMenu?.forEach {
checkOverflowMenuItem(it, iconColor, idsShowing)
}
}

Android Toolbar overflow menu view id

What I am trying to do is showing a PopupWindow pointing to the overflow icon (the three dots) on the Toolbar. So I need to get a reference to the View object with the id of the icon. But what is the id?
The PopupWindow is used to tell the users that there are new entries added to the overflow menu. And suggest users to check it out.
You should create the button id
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="overflowActionButton"/>
</resources>
then create the button style
<style name="Widget.ActionButton.Overflow" parent="Widget.AppCompat.ActionButton.Overflow">
<item name="android:id">#id/overflowActionButton</item>
</style>
and add this style in the theme
<style name="Theme.App" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="actionOverflowButtonStyle">#style/Widget.ActionButton.Overflow</item>
</style>
finally you should find the button view by id
activity.findViewById(R.id.overflowActionButton)
and do what you want
The overflow menu item doesn't have a resource id. I found the overflow view by traversing the toolbar. The debugger showed an id of -1 and the Hierarchy Viewer showed no resource-id.
Here is how I found the overflow view without a resource id:
/**
* Get the OverflowMenuButton.
*
* #param activity
* the Activity
* #return the OverflowMenuButton or {#code null} if it doesn't exist.
*/
public static ImageView getOverflowMenuButton(Activity activity) {
return findOverflowMenuButton(activity, findActionBar(activity));
}
static ImageView findOverflowMenuButton(Activity activity, ViewGroup viewGroup) {
if (viewGroup == null) {
return null;
}
ImageView overflow = null;
for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {
View v = viewGroup.getChildAt(i);
if (v instanceof ImageView && (v.getClass().getSimpleName().equals("OverflowMenuButton") ||
v instanceof ActionMenuView.ActionMenuChildView)) {
overflow = (ImageView) v;
} else if (v instanceof ViewGroup) {
overflow = findOverflowMenuButton(activity, (ViewGroup) v);
}
if (overflow != null) {
break;
}
}
return overflow;
}
static ViewGroup findActionBar(Activity activity) {
try {
int id = activity.getResources().getIdentifier("action_bar", "id", "android");
ViewGroup actionBar = null;
if (id != 0) {
actionBar = (ViewGroup) activity.findViewById(id);
}
if (actionBar == null) {
return findToolbar((ViewGroup) activity.findViewById(android.R.id.content).getRootView());
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
static ViewGroup findToolbar(ViewGroup viewGroup) {
ViewGroup toolbar = null;
for (int i = 0, len = viewGroup.getChildCount(); i < len; i++) {
View view = viewGroup.getChildAt(i);
if (view.getClass() == android.support.v7.widget.Toolbar.class ||
view.getClass().getName().equals("android.widget.Toolbar")) {
toolbar = (ViewGroup) view;
} else if (view instanceof ViewGroup) {
toolbar = findToolbar((ViewGroup) view);
}
if (toolbar != null) {
break;
}
}
return toolbar;
}
Calling getOverflowMenuButton(activity) will return null in onCreate because the overflow menu isn't laid out yet. To get the overflow menu in onCreate I did the following:
findViewById(android.R.id.content).post(new Runnable() {
#Override public void run() {
ImageView overflow = getOverflowMenuButton(MainActivity.this);
}
});
I found a library called TapTarget and a function TapTarget.forToolbarOverflow(). It presents a solution: https://github.com/KeepSafe/TapTargetView/blob/master/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTarget.java#L96
The way how it finds the overflow view is not neat but should be stable.
you want to create custom DropDown menu? consider this "native" way
or use android:showAsAction="never" in your menu.xml. doc of showAsAction attribute HERE. when one of MenuItems have set never value then you will get overflow three-dot icon automatically and these MenuItems will be hidding there
also you may try to use Hierarchy Viewer to investigate this id if really needed
Instead of using expensive and complicated layout traversal to find the overflow menu, I have achieved showing the PopupWindow under the overflow menu by using the Toolbar view as anchor and setting gravity to Gravity.END:
/**
* Sets the anchor view and shows the popup. In case of narrow display the menu items may be hidden in an overflow
* menu, in that case anchorView may be null and the popup will be anchored to the end of the toolbar.
*/
public void show(#Nullable View anchorView, #NonNull View toolbarView) {
if (anchorView == null) {
setDropDownGravity(Gravity.END);
setAnchorView(toolbarView);
} else {
setAnchorView(anchorView);
}
show();
}

Android View.post(Runnable) - Running but not affecting UI

I'm trying to add a view to the toolbar in my Android Activity. The below "decorView" is a RelativeLayout child view of the actionbar/toolbar.
Defining the Container:
ActionBar actionBar = getSupportActionBar();
if (actionBar == null) {
Toolbar toolBar = (Toolbar) findViewById(R.id.action_bar);
setSupportActionBar(toolBar);
}
actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setCustomView(getActionBarLayoutResourceId());
this.mActionBarView = (RelativeLayout) actionBar.getCustomView()
.findViewById(R.id.relative_layout_action_bar);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setHomeButtonEnabled(false);
actionBar.setDisplayUseLogoEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
actionBar.setDisplayHomeAsUpEnabled(false);
} else {
log("ActionBar is null");
}
Adding the view:
final ViewGroup decorView = this.mActionBarView;
Runnable postDecorHeaderView = new Runnable() {
#Override
public void run() {
if (decorView.getWindowToken() != null) {
// The Decor dView has a Window Token, so we can add the
// HeaderView!
decorView.addView(mHeaderView);
} else {
try {
Thread.sleep(1000); // 1 second
} catch (InterruptedException e) {
log(e.getClass() + ": " + e.getMessage());
}
// The Decor View doesn't have a Window Token yet, post
// ourselves again...
decorView.post(this);
}
}
};
decorView.post(postDecorHeaderView);
I have verified that decorView's child count increases after this method completes. Unfortunately, my "mHeaderView" does not show up, nor does it appear in view hierarchy analysis. I have tried other UI updates like changing a TextView's text to no avail.
I have verified the identity of the decorView from within the runnable. If I log the runnable, I see it running, I verify that it's running on the main UI thread... but no visual updates occur.
By grabbing the toolbar from the existing view, the custom layout was already present in the view. So I did not need to additionally "setCustomView". I'm not sure where this second custom view layout ended up on the screen (even using debug tools, I couldn't find the layout), but trying to post to and add a child view were not working.
Ultimately, all I did was change the mActionBarView setter to:
this.mActionBarView = (RelativeLayout) findViewById(R.id.relative_layout_action_bar);

Set OnClick Listener on Action Bar Title in Android

I am working on android application where I am using ActionBar so there one is navigation drawer icon to open it and title of ActionBar in ActionBar. I want to set a click listener on title of ActionBar such that it start a new Activity and set click listener different on navigation drawer icon to open navigation drawer menu.
I achieved a click on navigation drawer icon but when I click on title of ActionBar title also then it open the navigation drawer menu. Is there any way to set different click listener on title of ActionBar.
Thanks in advance.
Try adding this code under the onCreate() function. This will grab the resource the action bar title is under, and assign it an id you can use to add an OnClickListener to. Let me know how it goes!
final int abTitleId = getResources().getIdentifier("action_bar_title", "id", "android");
findViewById(abTitleId).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Do something
}
});
You could use a custom layout for the title and assign a listener to it:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar actionBar = getActionBar();
if (actionBar != null) {
// Disable the default and enable the custom
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowCustomEnabled(true);
View customView = getLayoutInflater().inflate(R.layout.actionbar_title, null);
// Get the textview of the title
TextView customTitle = (TextView) customView.findViewById(R.id.actionbarTitle);
// Change the font family (optional)
customTitle.setTypeface(Typeface.MONOSPACE);
// Set the on click listener for the title
customTitle.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.w("MainActivity", "ActionBar's title clicked.");
}
});
// Apply the custom view
actionBar.setCustomView(customView);
}
}
actionbar_title.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<TextView
android:id="#+id/actionbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"
android:text="#string/app_name"/>
</LinearLayout>
I think Simas's answer is the best one, but here's a hacky version in case you prefer that.
ViewTools.findActionBarTitle(getWindow().getDecorView()).setOnClickListener(...);
This one should be universal in that it works with:
stock Android ActionBar
Theme.AppCompat support ActionBar
v21-style setActionBar
use <Toolbar android:id="#+id/action_bar"
or pass in the inflated Toolbar as root
v21-style setSupportActionBar
use <android.support.v7.widget.Toolbar android:id="#id/action_bar"
or pass in the inflated Toolbar as root
custom Toolbar implementations may need a little adjustment,
but then you could encapsulate this in that custom class.
Though I only tested with support:v22.
/** #param root usually Activity.getWindow().getDecorView() or your custom Toolbar */
public static #Nullable View findActionBarTitle(#NonNull View root) {
return findActionBarItem(root, "action_bar_title", "mTitleTextView");
}
/** #param root usually Activity.getWindow().getDecorView() or your custom Toolbar */
public static #Nullable View findActionBarSubTitle(#NonNull View root) {
return findActionBarItem(root, "action_bar_subtitle", "mSubtitleTextView");
}
private static #Nullable View findActionBarItem(#NonNull View root,
#NonNull String resourceName, #NonNull String toolbarFieldName) {
View result = findViewSupportOrAndroid(root, resourceName);
if (result == null) {
View actionBar = findViewSupportOrAndroid(root, "action_bar");
if (actionBar != null) {
result = reflectiveRead(actionBar, toolbarFieldName);
}
}
if (result == null && root.getClass().getName().endsWith("widget.Toolbar")) {
result = reflectiveRead(root, toolbarFieldName);
}
return result;
}
#SuppressWarnings("ConstantConditions")
private static #Nullable View findViewSupportOrAndroid(#NonNull View root, #NonNull String resourceName) {
Context context = root.getContext();
View result = null;
if (result == null) {
int supportID = context.getResources().getIdentifier(resourceName, "id", context.getPackageName());
result = root.findViewById(supportID);
}
if (result == null) {
int androidID = context.getResources().getIdentifier(resourceName, "id", "android");
result = root.findViewById(androidID);
}
return result;
}
#SuppressWarnings("unchecked")
public static <T> #Nullable T reflectiveRead(#NonNull Object object, #NonNull String fieldName) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (T)field.get(object);
} catch (Exception ex) {
Log.w("HACK", "Cannot read " + fieldName + " in " + object, ex);
}
return null;
}
If you are using Toolbar with support v7:21.
Check out the following code:
Field titleField = Toolbar.class.getDeclaredField("mTitleTextView");
titleField.setAccessible(true);
TextView barTitleView = (TextView) titleField.get(mToolbar);
barTitleView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
You can do this easily using Toolbar. Define toolbar in layout xml file as given below:
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="?colorPrimary"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<TextView
android:id="#+id/toolbarTitle"
style="#style/TextAppearance.Widget.AppCompat.Toolbar.Title"
android:background="?attr/selectableItemBackground"
android:layout_width="wrap_content"
android:gravity="center_vertical"
android:layout_height="match_parent" />
</android.support.v7.widget.Toolbar>
Then you can set the listener in Activity using this code:
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
TextView toolbarTitle= (TextView) findViewById(R.id.toolbarTitle);
toolbarTitle.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// DO SOMETHING HERE
}
});
If you want to use the currently existing ActionBar and not the Toolbar, use the following:
ActionBar actBar = getSupportActionBar();
if(actBar != null) {
actBar.setTitle(R.string.your_ab_title);
}
//Set actions to take when the AB is clicked
Toolbar ab = findViewById(R.id.action_bar);
if(ab != null){
for (int i= 0; i < ab.getChildCount(); i++){
View child = ab.getChildAt(i);
if(child instanceof TextView || child instanceof ImageView) {
child.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String url = "http://www.HoverDroids.com";
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
}
});
}
}
}
If you know the actual text that is in your Title, and you are reasonably sure that no other TextView on the screen shares that title, you can use a recursive View tree search to find it.
This is a great solution because it doesn't require reflection of internal knowledge of how to Toolbar is constructed, and gives you direct access to the TextView.
#Nullable
public static TextView findTextViewWithText(#Nullable View toCheck, String toFind) {
if (toCheck instanceof TextView) {
String foundText = ((TextView) toCheck).getText().toString();
if (foundText.equals(toFind)) {
return (TextView) toCheck;
}
} else if (toCheck instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) toCheck).getChildCount(); i++) {
TextView found = findTextViewWithText(((ViewGroup) toCheck).getChildAt(i), toFind);
if (found != null) {
return found;
}
}
}
return null;
}
The most reliable view to call this on is the decor view but feel free to experiment what works best for your purposes, your mileage may vary.
View found = findTextViewWithText(
getActivity().getWindow().getDecorView(), "My Title");
if (found != null) {
// Do something, like set a click listener
}
I know its too late, but for or those who use SupportActionBar like this and still have not found a clean solution:
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
For the default configuration without logo and custom views, 1st item (index 0) will be the Home/Back ImageView, 2nd item will be our Title TextView and 3rd item will be the OptionMenu Imageview.
Getting child at index 1 would return title. Adding an OnClickListener to the child will make it work like a chram:
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.getChildAt(1).setOnClickListener(v -> {
// title is clicked, call ur function here
// can also verify that the view is title itself by converting it to textview
try {
String title = ((TextView)v).getText().toString();
// title will be your activity title
} catch (Exception e) {
e.printStackTrace();
// if you got an exception, the view is not title.
// Check changing the index, in case you have custom views in the toolbar.
}
});
You can do this easily using Toolbar. Define toolbar in layout xml file as given below:
<android.support.v7.widget.Toolbar
android:id="#+id/MainActivityToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#string/app_name"
android:textSize="30sp"
tools:ignore="RelativeOverlap"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
/>
<Button
android:id="#+id/LogOutButton"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
android:text="#string/logout" />
</RelativeLayout>
</android.support.v7.widget.Toolbar>
Then you can set the listener in Activity using this code:
setSupportActionBar((Toolbar) findViewById(R.id.MainActivityToolbar));
logOutButton = findViewById(R.id.LogOutButton);
logOutButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//define your function for logout or something else
LogOut();
}
});
I know it's very late to comment here but I came across this question when I searched for how to add OnClick for Action bar title.
Below is what I found and worked for me, hope it will help someone like me.
I wrote it for a fragment in my app.
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
actionBar.setTitle("");
((AppCompatActivity) getActivity()).setSupportActionBar((Toolbar) getActivity().findViewById(R.id.toolbar));
TextView toolbarTitle = (TextView) getActivity().findViewById(R.id.toolbarTitle);
toolbarTitle.setText("New title");
toolbarTitle.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Action bar title clicked
}
});
actionBar.show();

Find position of item in Android ActionBar

Is there a way to find the position of an icon in the action bar?
I have used the code below:
final View actionBarView = getActivity().getWindow().getDecorView().findViewById(getResources().getIdentifier("action_bar_container", "id", "android"));
if (actionBarView != null) {
final View buttonInActionBar = actionBarView.findViewById(R.id.menu_item);
if (buttonInActionBar != null) {
If, I set action_bar_menu_layout like this:
<item
android:id="#+id/menu_item"
MyApp:actionProviderClass="my_provider"
MyApp:showAsAction="always|withText|collapseActionView"
android:orderInCategory="0"
android:title="#string/item_name"/>
so by using the collapseActionView flag, everything works. But without that flag the view is not found. It looks like only in that case the menu is build by using that id as the view id.
Is there a way to do it?
This is the way I did it.
I used:
mItemView = MenuItemCompat.getActionView(menu.findItem(R.id.menu_item));
then I registered a GlobalLayoutListener and in the listener:
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
mItemView.getLocationInWindow(mMyItemLocation);
if (mItemView == null || mMyItemLocation[0] == 0 || mItemView.getRight() == 0 || mItemView.getWidth() == 0) {
return;
}
mLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}

Categories

Resources