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);
}
Related
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();
}
I am using swipelayout from daimajia.I get some data from services.When the service has a date than will visible an icon that will open the left sideBut it do nothing when clicking the icon.I have no error that's the reason why cannot resolve it.The action is when clicking the icon,that visible a textview and start a chronometer.
Here is my swipeListener
private SwipeListener swipeListener = new SwipeListener() {
#Override
public void onStartOpen(SwipeLayout layout, SwipeLayout.DragEdge edge) {
if (edge == SwipeLayout.DragEdge.Left && viewHolder.discountText != null) {
**viewHolder.discountText.setTimeByTag().play();**
LogUtils.LogE("Left Open...");
}
}
#Override
public void onOpen(SwipeLayout layout, SwipeLayout.DragEdge edge) {
super.onOpen(layout, edge);
if (edge == SwipeLayout.DragEdge.Left && viewHolder.discountText != null) {
viewHolder.discountText.setTimeByTag().play();
LogUtils.LogE("Left Open...");
}
}
#Override
public void onClose(SwipeLayout layout, SwipeLayout.DragEdge edge) {
if (edge == SwipeLayout.DragEdge.Left && viewHolder.discountText != null) {
viewHolder.discountText.stop();
LogUtils.LogE("Left Close!");
}
}
};
public void openLeft() {
LogUtils.LogE("onClick openLeft-");
if (getSwipeLayout() == null){
LogUtils.LogE("onClick -null-");
return;
}
if (!getSwipeLayout().isOpen()) {
getSwipeLayout().open(true, SwipeLayout.DragEdge.Left);
LogUtils.LogE("onClick --");
}
}
all the codes working in other my fragments.I need some idea what's the reason that it not give true action that swipe to left.
EDIT:
Here is the Logs:
In this picture I have 2 items there which it's in my favorite page.The last one item has end date that will be visible the icon(ImageView).When pressing the icon,it should swipe to left side but it's not working and give the result like this picture which I'm shared.
EDIT
There is something more.I had looked the codes in debug mode and give here an message:
but put define swipeLayout into my code:
sl.setShowMode(SwipeLayout.ShowMode.LayDown);
sl.setDragEdges(SwipeLayout.DragEdge.Left, SwipeLayout.DragEdge.Right);
sl.setBottomViewIds(R.id.productBottomLeft, R.id.productBottomRight, SwipeLayout.EMPTY_LAYOUT, SwipeLayout.EMPTY_LAYOUT);
TextView button = SwipeLayout.findViewById(R.id.yourButtonId) //the bottom view;
button.setOnClickListener(v -> yourMethod());
I have an app where I handle all the touches myself instead of using touch and gesture detection APIs, because it is a floating window and is the only way that works.
One of the things I do is change the color of the view under the finger.
OnTouch I check which view is under the finger if it is different from the previous I run:
myView.setBackgroundColor(getResources().getColor(R.color.white));
It doesn't work when I go to the view next to the current and return very fast.
I have checked it with logs and the view found is correct. And I have also checked and the line where setBackgroundColor is, is executed.
So I don't know what else to do. Are any cases in which setBackgroundColor doesn't work? Is it that if onTouch takes too much time to execute doesn't finish its tasks?
Any clue of how to fix this?
EDIT:
It only fails when I go to the view next to the current and return fast.
I didn't add the code because I thought it was harder to read than the abstraction I did. I have cleaned it up and posted.
If you think any methods called are relevant I can add them.
EDIT 2:
Code that runs if ACTION is not ACTION_DOWN or ACTION_UP. Those cases are not related.
if ((isPortrait && isPortraitMeasured) || (!isPortrait && isLandscapeMeasured)) {
//Log.d("algor", "Here calculate where it is");
final int lastCol = currentColumn;
final int lastRow = currentRow;
findCell((int) event.getRawX(), (int) event.getRawY());
if ((lastCol == currentColumn && lastRow == currentRow)) {
if (isPortrait && currentRow==-1 || (!isPortrait && currentColumn==-1)
&& !wasPressed && currentTable!=mainTable) {
//is actionBar. Check if finger is over back icon, to go back
//Code not related to the problem...
}
} else {
int currentIndex = getAppsListIndex();
if (currentIndex >= 0) {
View nextApp;
if (isPortrait)
nextApp = cellsP.get(currentTable).get(currentIndex);
else
nextApp = cellsL.get(currentTable).get(currentIndex);
final Object tag = nextApp.getTag();
if (tag instanceof FolderData) {
//Code not related to the problem...
} else {
Log.d("fastf", "time to change color");
//nextApp.setBackgroundColor(getResources().getColor(R.color.white));
nextApp.setBackgroundColor(whiteColor);
//nextApp.setBackground(getResources().getDrawable(R.color.white));
/*final View app = nextApp;
app.post(new Runnable() {
#Override
public void run() {
app.setBackgroundColor(getResources().getColor(R.color.white));
}
});*/
}
} else {
//Code not related to the problem...
}
int lastIndex = getAppsIndexFromInts(lastRow, lastCol);
//lastCol != -2 is because otherwise, on opening the launcher it animates
// the last app launched
if (lastIndex >= 0 && lastCol != -2) {
View lastApp;
if (isPortrait)
lastApp = cellsP.get(currentTable).get(lastIndex);
else
lastApp = cellsL.get(currentTable).get(lastIndex);
ObjectAnimator animator = ObjectAnimator.ofInt(lastApp,
"backgroundColor", getResources().getColor(R.color.clear_gray),
getResources().getColor(R.color.white_overlay_transition));
animator.setDuration(500);
animator.setEvaluator(new ArgbEvaluator());
animator.start();
}
}
}
You should use
nextApp.setBackgroundResource(...) instead of others.
Because of your "R.color.white" is a resource.
Have a nice day.
It was an old question but, I faced same problem and resolved with this change.
try it
myView.setBackground(getResources().getDrawable(R.color.white));
myView.setBackgroundColor(getResources().getColor(R.color.bgcolor));
color.xml
<color name="bgcolor">#ffffff</color>
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...
}
}
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.