How to add a clickable "events" action item to actionBarSherlock? - android

Background
many apps (including google plus and facebook) have an action bar item showing the number of in-app-events (or "notifications").
This action item has a number within it and you can click it in order to show the events the app has for the user.
something like that (taken from here) :
The problem
I wish to make it work on old android versions, so i use actionBarSherlock.
Sadly, each solution i use has its disadvantages, and i couldn't find any solution here (on stackOverflow) that handles this with actionBarSherlock (found other solutions, but not with this library).
I've also found a post about it (here) , claiming it's an issue on this library, but it's very old and seems to be closed and marked as fixed, but I can't find out how to use it now.
What I've tried
i've tried the next solutions:
actionLayout . it showed fine, but clicking on it didn't show the clicking effect.
actionViewClass - it didn't even work for some reason.
adding the menu item and its view programmatically.
The question
What's the best way to achieve this ?
EDIT: this is what i've tried using actionLayout :
"action_item_notification.xml" - for now it's the same as "abs__action_menu_item_layout.xml" (here). later i will add a textView to hold the number of notifications.
in the menu xml file, i have this as one of the items:
<item
android:id="#+id/activity_main__menuItem_notifications"
android:actionLayout="#layout/action_item_notification"
android:icon="#drawable/notification_button"
android:showAsAction="always"
android:title="#string/notifications"/>
not only it doesn't show the icon, but long clicking on the item will crash the app, with a NPE on the ActionMenuItemView.java file.
EDIT:ok, so i've found a solution that is almost perfect.
it shows the action item nicely and it even reacts to clicking as the other action items.
I've sadly had one missing feature - long clicking on action item to show the toast of its title. sadly, i couldn't find a way to overcome this so what i did (that works) is handling the long clicking on the view itself, and call a similar code that is used for ActionMenuItemView::onLongClick .
if anyone has a better and nicer solution, please write it down.
i've written this solution in a new answer here.

here's my solution, but it's a bit messy and calls the same code of showing a toast for the action item as the one of actionBarSherlock.
if anyone has a better, cleaner solution, please write it down.
menu file (activity_main.xml) :
...
<item
android:id="#+id/activity_main__menuItem_notifications"
android:showAsAction="always"
android:title="#string/notifications"/>
...
MainActivity.java :
public boolean onCreateOptionsMenu(...){
...
getSupportMenuInflater().inflate(R.menu.activity_main, menu);
//
final MenuItem notificationsMenuItem = menu.findItem(R.id.activity_main__menuItem_notifications);
notificationsMenuItem.setActionView(R.layout.action_item_notification);
setEnableLongClickOnCustomActionItem(notificationsMenuItem,true);
...
public static void setEnableLongClickOnCustomActionItem(final MenuItem menuItem, final boolean enable) {
final View actionView = menuItem.getActionView();
if (actionView == null)
return;
final CharSequence title = menuItem.getTitle();
if (!enable || Strings.isEmpty(title))
actionView.setOnLongClickListener(null);
actionView.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(final View v) {
final int[] screenPos = new int[2];
final Rect displayFrame = new Rect();
actionView.getLocationOnScreen(screenPos);
actionView.getWindowVisibleDisplayFrame(displayFrame);
final Context context = actionView.getContext();
final int width = actionView.getWidth();
final int height = actionView.getHeight();
final int midy = screenPos[1] + height / 2;
final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
final Toast cheatSheet = Toast.makeText(context, title, Toast.LENGTH_SHORT);
if (midy < displayFrame.height()) {
// Show along the top; follow action buttons
cheatSheet.setGravity(Gravity.TOP | Gravity.RIGHT, screenWidth - screenPos[0] - width / 2, height);
} else {
// Show along the bottom center
cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
}
cheatSheet.show();
return true;
}
});
layout file of the action item ( action_item_notification.xml) :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/actionButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:addStatesFromChildren="true"
android:focusable="true" >
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:background="#null"
android:focusable="false"
android:scaleType="fitCenter"
android:src="#drawable/notification_button" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="#+id/imageView1"
android:layout_alignRight="#+id/imageView1"
android:background="#drawable/action_item_notification_counter_background"
android:paddingLeft="1dp"
android:paddingRight="1dp"
android:text="88"
android:textColor="#FFffffff"
tools:ignore="HardcodedText" />
</RelativeLayout>
and a nice drawable for the background of the textView ("action_item_notification_counter_background.xml") :
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" >
<solid android:color="#FFff0000" />
</shape>

I found an easier way to solve your problem - which I also had, with my final struggle being also the toast.
So, to solve the toast problem as simply as possible, just do as follow :
final Menu m = menu;
final MenuItem item = m.findItem(R.id.item);
final View actionView = item.getActionView();
actionView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
Toast toast = Toast.makeText(CWalletOffersFragment.this.getContext(), item.getTitle(), Toast.LENGTH_SHORT);
toast.setGravity(Gravity.TOP, v.getRight(),v.getBottom());
toast.show();
return true;
}
});
The position is a bit off, but not by much (you could still tweak it with an offset if you like). I really did not want to write lines upon lines of code for something so simple, so I think this is a worthy tradeoff :) !

Related

How to add ripple animation for double click and not for single click?

Context of the Problem
I was currently trying to build a functionality to fast forward videos in ExoPlayer exactly like how Youtube does it. I thought that I could add a double click listener and I was able to do that using the answer given here.
Problem
The problem I am facing is that I was getting the ripple animation even when I do a single click which is not what I want. I want to be able to get a ripple only when a double click is triggered. So, for this, I tried to get the number of clicks and then accordingly set the ripple visibility on the view but it did not seem to work for me and instead giving a weird behaviour. The issue can be seen in the gif given below* :
It can be clearly seen that on single click, it shows ripple. On double click, it shows one ripple right but the other one comes out from nowhere!
Code
Kotlin
var doubleClickLastTime = 0L
var clicksDone = 0
binding.forward.setOnClickListener {
if((System.currentTimeMillis() - doubleClickLastTimeF) < 350){
doubleClickLastTime = 0
clicksDone = 0
exoPlayer.seekTo(exoPlayer.contentPosition + 10000)
}else{
if (clicksDoneF == 0){
binding.forward.foreground = null
}else{
binding.forward.foreground = with(TypedValue()) {
this#VideoPlayer.theme.resolveAttribute(
android.R.attr.selectableItemBackground, this, true)
ContextCompat.getDrawable(this#VideoPlayerFromUrl, resourceId)
}
}
clicksDone++
doubleClickLastTime = System.currentTimeMillis()
}
}
XML
<androidx.cardview.widget.CardView
android:id="#+id/forward"
android:layout_width="550dp"
android:layout_height="match_parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginVertical="50dp"
android:foreground="?selectableItemBackgroundBorderless"
app:cardCornerRadius="16px"
app:cardElevation="0dp"
android:backgroundTint="#android:color/transparent"/>
*Sorry for the bad gif quality
If you convert it in kotlin you can use this
GestureDetector gestureDetector=new GestureDetector(getApplicationContext(),new GestureDetector.SimpleOnGestureListener(){
#Override
public boolean onDoubleTap(MotionEvent e) {
// you can put your code here
return super.onDoubleTap(e);
}
});

Remove status bar and Navigation Bar in AOSP

I am building AOSP 11 for emulator x86_64 and want to remove Status bar and Navigation bar.
I have found frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java file where navigation buttons are placed.
By putting following 3 lines in comment, I was able to disable navigation button.
private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
View v = null;
String button = extractButton(buttonSpec);
if (LEFT.equals(button)) {
button = extractButton(NAVSPACE);
} else if (RIGHT.equals(button)) {
button = extractButton(MENU_IME_ROTATE);
}
if (HOME.equals(button)) {
// v = inflater.inflate(R.layout.home, parent, false);
} else if (BACK.equals(button)) {
// v = inflater.inflate(R.layout.back, parent, false);
} else if (RECENT.equals(button)) {
// v = inflater.inflate(R.layout.recent_apps, parent, true);
} else if (MENU_IME_ROTATE.equals(button)) {
v = inflater.inflate(R.layout.menu_ime, parent, false);
} else if (NAVSPACE.equals(button)) {
But space is reversed by system, means if I run any application it is not use navigation bar's space.
Also to disable status bar I have Added android:visibility="gone" in frameworks/base/packages/SystemUI/res/layout/status_bar.xml
<com.android.systemui.statusbar.phone.PhoneStatusBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:layout_width="match_parent"
android:layout_height="#dimen/status_bar_height"
android:id="#+id/status_bar"
android:orientation="vertical"
android:focusable="false"
android:descendantFocusability="afterDescendants"
android:accessibilityPaneTitle="#string/status_bar"
android:visibility="gone"
>
But also in this status bar disabled. but space is reserved by system.
can any one please help in this ?
Have you tried this in your device mk file?
PRODUCT_PROPERTY_OVERRIDES += \
qemu.hw.mainkeys=1
This is how we remove the navigation bar on Android 11.
It works because if you look in DisplayLayout.java under SystemUI/src/.../wm, you'll see it checks this property and if it's 1, hasNavigationBar(...) will return false. You could also just edit this method to always return false and you would likely see the same effect.
static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
// Allow a system property to override this. Used by the emulator.
final String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
if ("1".equals(navBarOverride)) {
return false;
...
For the status bar, in the past I've added a resource overlay to the device and it has worked. I'm not sure about Android 11 though. The basic idea is you create a file in your device directory with the path overlay/frameworks/base/core/res/res/values/dimens.xml and it would contain:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Height of the status bar -->
<dimen name="status_bar_height">0dip</dimen>
</resources>
If you don't want to deal with an overlay, you could also edit the value in frameworks/base/core/res/res/values/config.xml directly:
<!-- Height of the status bar -->
<dimen name="status_bar_height">#dimen/status_bar_height_portrait</dimen>
Change this value to 0dip and the status bar should be gone.
You may consider lock task mode https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode#customize-ui to make a solution within Google's concept for such tasks.

Prevent Spinner dropdown dismiss when focus changed

I have a dropdown spinner which is showed when click on a button looks like this:
<Button
android:id="#+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Spinner
android:id="#+id/spinMenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:spinnerMode="dropdown"
android:visibility="invisible" />
<ListView
android:id="#+id/lvWall"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Here is snippet showing dropdown popup:
findViewById(R.id.btn).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
spinMenu.performClick();
}
});
My spinner can show dropdown popup correctly. The problem is my layout has a listview which getting data from web service in background. When data is loading completely, all list items will be showed or refreshed, and the spinner's dropdown popup is dismiss (I even don't touch anything on screen). I think the problem is window has changed focus on other view. So how can I prevent it?
Update:
Here is my list after load data from background, it's very simple:
List<Feed> data = result;
FeedAdapter adapter = new FeedAdapter (this, data);
ListView lvWall = (ListView)findViewById(R.id.lvWall);
lvWall.setAdapter(adapter);
And data for spinner:
List<String> list = getMenus();
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, list);
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinMenu.setAdapter(dataAdapter);
If I understand correctly, you have a Spinner view which you set as invisible with the only purpose of showing the popup menu, but not the Spinner view itself. In that case, the problem is probably related to this snippet in Spinner.java, more precisely in DropdownPopup.show():
public void show(int textDirection, int textAlignment) {
...
super.show();
...
// Make sure we hide if our anchor goes away.
// TODO: This might be appropriate to push all the way down to PopupWindow,
// but it may have other side effects to investigate first. (Text editing handles, etc.)
final ViewTreeObserver vto = getViewTreeObserver();
if (vto != null) {
final OnGlobalLayoutListener layoutListener = new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (!Spinner.this.isVisibleToUser()) {
dismiss();
} else {
computeContentWidth();
...
What does this mean? Basically that the Spinner is set up with a ViewTreeObserver to be notified whenever a layout pass changes the views in the screen. And if the Spinner is not visible after that happens, the popup is dismissed. Loading the ListView evidently causes a change in the view hierarchy, and it's being fired when the data arrives from the server.
For general usage this is completely logical: if the Spinner is hidden, or it goes off screen, or something like that, it would be reasonable to make the popup go away. However, it's interferring with what you're attempting to do. It would be nice if you could somehow override isVisibleToUser(), but unfortunately it's marked as #hide, so that's not possible.
Might I suggest a workaround, like setting the Spinner visible but really small? Like, with a height of 1px? I believe that should be enough to fool this method.
Another option, and probably a more sensible one, would be to forgo the Spinner altogether and use a PopupMenu instead. You can anchor it to the Button, load it dynamically, and show it when the button is pressed. The visual effect should be the same.
If you think the problem is due to the change of focus . You can set it with multiple ways.
first create a focuschangeListener and onfocuschange do whatever you like
yourView.setOnFocusChangeListener(testListener);
#Override
public void onFocusChange(View arg0,
boolean isFocused)
{
if(isFocused)
{
//do your work here
}
else
{
}
}
And second way to prevent view from focus..
<!-- Dummy item to prevent AutoCompleteTextView from receiving focus -->
<LinearLayout
android:focusable="true" android:focusableInTouchMode="true"
android:layout_width="0px" android:layout_height="0px"/>
<!-- :nextFocusUp and :nextFocusLeft have been set to the id of this component
to prevent the dummy from receiving focus again -->
<AutoCompleteTextView android:id="#+id/autotext"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:nextFocusUp="#id/autotext" android:nextFocusLeft="#id/autotext"/>

How to show Indeterminate Progress bar when Refresh button is pressed in ActionBarSherlock?

How to show Indeterminate ProgressBar when Refresh button is pressed in ActionBarSherlock and again show Refresh Button when ViewGroup on refreshed?
Update 1:
I have a answer here which is incomplete. I am placing a bounty on question so that more developers can help build a good answer which can useful to others in future.
How can we show a Indeterminate ProgressBar which looks like the one shown in the image below
It seems like ActionBarSherlock doesn't provide specific method to animate a refresh MenuItem.
What you can do (by using classic android API) is to use the setActionView(int resId) method and give the id of a layout with a ProgressBar in it.
At the beginning of your refresh action just call :
item.setActionView(R.layout.refresh_menuitem);
And when your refresh action is finished call :
item.setActionView(null);
Here is a sample of what your layout file refresh_menuitem.xml can have :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:addStatesFromChildren="true"
android:focusable="true"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:gravity="center"
style="?attr/actionButtonStyle">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
style="#android:style/Widget.ProgressBar.Small"/>
</LinearLayout>
Here is how you add this kind of indeterminate ProgressBar with a ActionBarSherlock object : (actually it's easier the other one but the progressBar is shown alone and not above a MenuItem)
1 - Put this line in the onCreate() method before the setContentView() call :
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-> This line specify that you will use the indeterminate ProgressBar function.
2 - Enable the indeterminate ProgressBar by calling :
setSupportProgressBarIndeterminateVisibility(true);
3 - Disable the indeterminate ProgressBar by calling :
setSupportProgressBarIndeterminateVisibility(false);
Remark : Have a look in the sample folder of the ActionBarSherlock folder. I found this code in the following file :
JakeWharton-ActionBarSherlock-9598f2b\samples\demos\src\com\actionbarsherlock\sample\demos\IndeterminateProgress.java
Here is a complete code:
private static final int menuItemIdRefresh = 10; //class constant
private boolean refresh;
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItem refreshItem = menu.add(0, menuItemIdRefresh, 0,
getString(R.string.action_refresh)).setShowAsActionFlags(
MenuItem.SHOW_AS_ACTION_ALWAYS);
if (isRefreshing) {
refreshItem.setActionView(R.layout.indeterminate_progress);
} else {
refreshItem.setActionView(null);
refreshItem.setIcon(R.drawable.ic_refresh);
}
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case menuItemIdRefresh: {
//the user has pressed the refresh button
if (!isRefreshing) {
isRefreshing = true;
new RefreshMyViewAsyncTask().execute("");
}
}
break;
default:
break;
}
return false;
}
One last note, in order to get the above code working you´ll need also call supportInvalidateOptionsMenu(). You can add that to the RefreshMyViewAsyncTask's onPreExecute() method.
Hope this helps to somebody.

android 4.0, text on the action bar NEVER shows

I am trying to use the new api's from google, specifically the action bar.
When the build was set at api 10, if I pressed the menu button, I got nice looking menu options, each with a picture and icon. When using api 14, No matter what I try, it always puts the icon in the action bar with NO text. I have tried everything I can think of. I gave it the "with text" property, changed the text to a single character (in case it was a room issue), but nothing.
I have seen this done before, even in the developer guide at android.developer, but I can't seem to find an answer as to HOW to get it to show up.
I suspect that it was a conscious decision by the Android developers to never display a single menu item's text and icon on a narrow action bar. But if you really want to do so, you can use android:actionLayout in your menu.xml file. The Android ActionBar documentation has a slightly better explanation.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/menu_foo"
android:title="#string/menu_foo"
android:icon="#drawable/ic_menu_foo"
android:showAsAction="always"
android:actionLayout="#layout/action_button_foo" />
</menu>
Then create your action_button_foo.xml layout:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:gravity="center"
android:text="#string/menu_foo"
android:drawableLeft="#drawable/ic_menu_foo"
android:background="#drawable/bg_btn_action_bar"
android:clickable="true" />
and use a selector for its background bg_btn_action_bar.xml, so it changes color when you tap it:
<?xml version="1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="#drawable/bg_action_bar_pressed" />
<item
android:drawable="#color/transparent" />
</selector>
Now you'll need to make your custom view handle click events. In your Activity, I like to do this, so that I can handle the click in onOptionsItemSelected along with all my other, non-custom items.
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.my_menu, menu);
final MenuItem item = menu.findItem(R.id.menu_foo);
item.getActionView().setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
onOptionsItemSelected(item);
}
});
return super.onCreateOptionsMenu(menu);
}
This is definitely the same thing I've observed on my Nexus S running 4.0.4. My app uses an action bar with several tabs that are implemented as fragments. My various fragments make adjustments to the menu options displayed on the action bar while the their tab is visible.
This appears to be a bug in ICS, because it performs consistently as follows, both on my Nexus S and in the emulator (both HVGA and WVGA800):
In portrait mode, my logo/up button appears on the top row of the action bar, tabs appear on the second row, and any actions appear as icons only (no text) in the right side of the top row.
But if when I rotate to landscape, the action bar collapses to a single row, and tabs move up to the top bar as a spinner (drop-down list) next to my up button. But notably, then the text appears next to my action icons.
I noticed some other glitches with the tab spinner that lead me to believe that this little corner of ICS is a bit messy/buggy. If I tell the application to split the action bar on narrow displays (by adding android:uiOptions="splitActionBarWhenNarrow" in the manifest, ICS always pushes those items to the bottom bar, even though there's still plenty of room at the top. And even with the extra bar, it still doesn't display the text, just the icon.
On my Xoom running 4.0.4, tabs and action items always appear the way you'd expect them to appear because there's plenty of room.
Workaround: if you really want text on the action bar in portrait mode, you need to give up the icon. Remove the icon from your menu item and the text will appear. This isn't exactly what we're after though.
I've posted a bug report here: https://code.google.com/p/android/issues/detail?id=30180.
If you want your Options Menu to show up in your action bar with Honeycomb, I did this:
In your activity, override this function:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.actionbar_universe, menu);
return true;
}
where R.menu.actionbar_universe define your menu item like this:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/crossholdings" android:showAsAction="always|withText"
android:title="Cross Holdings" android:icon="#drawable/actionbar_cross"/>
</menu>
Note the showAsAction="always|withText" and specify android:title.
If you have that and its not working please copy|paste your menu resource here.
EDIT: This answers the wrong question, but it is the original text.
I use this bit of code to set the title of the action bar, and paint it red with my companies logo. It works well in 3.0.
public ActionBar setActionBarStyle(String title) {
ActionBar actionBar = setActionBarStyle();
actionBar.setTitle(title);
return actionBar;
}
public ActionBar setActionBarStyle() {
ActionBar actionBar = getActionBar();
ShapeDrawable actionBackground = new ShapeDrawable();
actionBackground.getPaint().setColor(Color.RED);
actionBackground.setBounds(0, 0, 5, 5);
actionBar.setBackgroundDrawable(actionBackground);
actionBar.setDisplayUseLogoEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
return actionBar;
}
The "withText" property works with most tablets, but an easy way to get icons and text on smaller devices is to add the text next to the icon as one image (PNG file).
That way, both the text and icon will be seen as one icon and the whole thing will display.
You can use the original icon for tablets by using the withText property.
You have to create an extra menu folder in the res directory titled "menu-w600dp".
The optionmenu.xml in this folder will only apply to screen widths bigger than 600dp
(the ones that will show the icons and text with no problems).
Fixed this issue by reading "If your app is using the Support Library" section under Specify the Actions in XML.
...for compatibility on versions as low as Android 2.1, the showAsAction attribute is not available from the android: namespace. Instead this attribute is provided by the Support Library and you must define your own XML namespace and use that namespace as the attribute prefix. (A custom XML namespace should be based on your app name, but it can be any name you want and is only accessible within the scope of the file in which you declare it.) For example:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:yourapp="http://schemas.android.com/apk/res-auto" >
<!-- Search, should appear as action button -->
<item android:id="#+id/action_search"
android:icon="#drawable/ic_action_search"
android:title="#string/action_search"
yourapp:showAsAction="ifRoom" />
...
</menu>
If none of these other things work for you, this may.
I tried many options and I came up with a simple "trick" without any weird line of code, without images. And first solution with custom actionLayout simply did not work for me with API level 10 compatibility.
If you want to display text AND icon on a small action bar it means you know you have the space, right? So you can use 2 menu items:
First with the icon ONLY (ignore warning, if you set a title tablets will show it twice)
Second with the text ONLY
And choose the text action to 'ifRoom' if needed so that if you do need space, the text will go away. It WILL take some more space on the action bar though but was a good compromise for me.
I end up with the following code:
<item
android:id="#+id/menu_save"
android:icon="#drawable/save"
pelmel:showAsAction="always">
</item>
<item
android:id="#+id/menu_save_text"
android:title="#string/profileSave"
pelmel:showAsAction="ifRoom">
</item>
(EDIT Where "pelmel" is your app name END EDIT)
And then your selection handler just has to catch both IDs :
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_save:
case R.id.menu_save_text:
// Your code here
return true;
}
}
Here's another option, based roughly on dgmltn's. The advantages:
More control - e.g. I've swapped the text and image over in my layout.
Easier to use - only requires two extra lines in your activities/fragments.
Only requires two extra files.
Possibly slightly more correct, but it's still a bit of a hack IMO.
I've assumed you're using ActionBarSherlock in this example. First, create the view layout you want. This one is based on ActionBarSherlock's. All I changed was swapping the image/view over, reducing the shared margin/padding to 0 so they are closer, and resolving all the ABS styles.
<com.example.views.ActionMenuTextItemView xmlns:android="http://schemas.android.com/apk/res/android"
style="#android:style/Widget.Holo.ActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:addStatesFromChildren="true"
android:focusable="true"
android:gravity="center"
android:clickable="true"
android:paddingLeft="4dip"
android:paddingRight="4dip" >
<com.actionbarsherlock.internal.widget.CapitalizingButton
android:id="#+id/abs__textButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#null"
android:ellipsize="none"
android:focusable="false"
android:minHeight="48dip"
android:minWidth="48dip"
android:paddingBottom="4dip"
android:paddingLeft="4dip"
android:paddingRight="0dip"
android:paddingTop="4dip"
android:singleLine="true"
android:textAppearance="#android:style/TextAppearance.Holo.Widget.ActionBar.Menu"
android:textColor="#fff3f3f3" />
<ImageButton
android:id="#+id/abs__imageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="4dip"
android:layout_marginLeft="0dip"
android:layout_marginRight="4dip"
android:layout_marginTop="4dip"
android:adjustViewBounds="true"
android:background="#null"
android:focusable="false"
android:scaleType="fitCenter"
android:visibility="gone" />
</com.example.views.ActionMenuTextItemView>
Then create the corresponding View class. You may want to copy CapitalizingButton if you are worried about using internal things. Oh, also I never fixed the minimum width stuff. Don't think it really matters though.
package com.example.views;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import com.actionbarsherlock.R;
import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.app.SherlockFragment;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.app.SherlockListActivity;
import com.actionbarsherlock.app.SherlockListFragment;
import com.actionbarsherlock.internal.widget.CapitalizingButton;
import com.actionbarsherlock.view.MenuItem;
#SuppressLint({ "NewApi" })
public class ActionMenuTextItemView extends LinearLayout implements OnClickListener
{
private ImageButton mImageButton;
private CapitalizingButton mTextButton;
private Object mTarget;
private MenuItem mItem;
// Set up all the data. Object must be a sherlock activity or fragment with an onMenuItemSelected().
public void initialise(MenuItem item, Object target)
{
mItem = item;
mTarget = target;
setIcon(mItem.getIcon());
setTitle(mItem.getTitle());
}
public ActionMenuTextItemView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ActionMenuTextItemView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
#Override
public void onFinishInflate()
{
super.onFinishInflate();
mImageButton = (ImageButton) findViewById(R.id.abs__imageButton);
mTextButton = (CapitalizingButton) findViewById(R.id.abs__textButton);
mImageButton.setOnClickListener(this);
mTextButton.setOnClickListener(this);
setOnClickListener(this);
}
#Override
public void setEnabled(boolean enabled)
{
super.setEnabled(enabled);
mImageButton.setEnabled(enabled);
mTextButton.setEnabled(enabled);
}
public void setIcon(Drawable icon)
{
mImageButton.setImageDrawable(icon);
if (icon != null)
mImageButton.setVisibility(VISIBLE);
else
mImageButton.setVisibility(GONE);
}
public void setTitle(CharSequence title)
{
mTextButton.setTextCompat(title);
setContentDescription(title);
}
#Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event)
{
onPopulateAccessibilityEvent(event);
return true;
}
#Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
super.onPopulateAccessibilityEvent(event);
final CharSequence cdesc = getContentDescription();
if (!TextUtils.isEmpty(cdesc))
event.getText().add(cdesc);
}
#Override
public boolean dispatchHoverEvent(MotionEvent event)
{
// Don't allow children to hover; we want this to be treated as a single component.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
return onHoverEvent(event);
return false;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int minWidth = 0;
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int specSize = MeasureSpec.getSize(widthMeasureSpec);
final int oldMeasuredWidth = getMeasuredWidth();
final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(specSize, minWidth) : minWidth;
if (widthMode != MeasureSpec.EXACTLY && minWidth > 0 && oldMeasuredWidth < targetWidth)
{
// Remeasure at exactly the minimum width.
super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY), heightMeasureSpec);
}
}
#Override
public void onClick(View v)
{
if (mTarget == null)
return;
else if (mTarget instanceof SherlockActivity)
((SherlockActivity)mTarget).onOptionsItemSelected(mItem);
else if (mTarget instanceof SherlockFragmentActivity)
((SherlockFragmentActivity)mTarget).onOptionsItemSelected(mItem);
else if (mTarget instanceof SherlockListActivity)
((SherlockListActivity)mTarget).onOptionsItemSelected(mItem);
else if (mTarget instanceof SherlockListFragment)
((SherlockListFragment)mTarget).onOptionsItemSelected(mItem);
else if (mTarget instanceof SherlockFragment)
((SherlockFragment)mTarget).onOptionsItemSelected(mItem);
else
throw new IllegalArgumentException("Target must be a sherlock activity or fragment.");
}
}
Ok now you're ready to use it. In your menu items that you want to have text, you do the same as what dgmltn said:
<item
android:id="#+id/menu_foo"
android:icon="#drawable/..."
android:showAsAction="always|withText" // Doesn't do anything really.
android:title="Sell"
android:titleCondensed="Sell"
android:actionLayout="#layout/view_action_menu_text_item"/> // Or whatever you called it.
And finally, just add this code to your activity/fragment:
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
super.onCreateOptionsMenu(menu);
getSupportMenuInflater().inflate(R.menu.activity_main, menu);
// The magic lines.
MenuItem it = menu.findItem(R.id.menu_foo);
((ActionMenuTextItemView)it.getActionView()).initialise(it, this);
And that's it!

Categories

Resources