I've been away from Java for some time --functional programming has been my muse-- and recently decided to jump back in with an android application. Things are going well. Javas syntax is mostly back in my brain, OO design principles are a little rusty, but I'm not afraid of re-factoring.
One problem I hit has been with the option menus in the platform. I load them from an XML file through a menu-inflator in my main activity (below), and I can see them! But, when I press them things get weird --but not like seeing your grandmother make-out with your best friend, much less weird.
For some reason, when I press the first button, I get the friendly default message in the code sample below, "That's not an option, moron!". And when I press the second, the message is "Adding One". I'm off by one somehow! But, but how!? but why!?
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/add_single_id"
android:title="#string/add_one" />
<item android:id="#+id/add_multi_id"
android:title="#string/add_multi" />
</menu>
... which is loaded by the menu inflator...
public boolean onCreateOptionsMenu( Menu menu ){
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.option_menu, menu);
return true;
}
... and finally the listener for items selected.
public boolean onOptionsItemSelected( MenuItem item ){
switch( item.getItemId() ){
case R.id.add_single_id:
Toast.makeText(this, "Adding One", Toast.LENGTH_LONG).show();
add_single();
break;
case R.id.add_multi_id:
Toast.makeText(this, "Adding n", Toast.LENGTH_LONG).show();
Intent i = new Intent(this, SelectMulti.class);
startActivityForResult(i, ACTIVITY_LOADMULTI);
break;
default:
Toast.makeText(this, "That's not an option, moron!", Toast.LENGTH_LONG).show();
return false;
}
return true;
}
This happen to me many times when I am developing android on eclipse, and clean and rebuilding a project fixes it as it will recreate android Resource file and correctly map to your UI id's.
In your XML you have ID = add_one_id but in the code you use R.id.add_single_id
Related
I have made an app OB Nyt that works well on Froyo and below - but on ICS and above nothing happens when I click the menu buttons (the link to update, the link to images activity and the link to search activity). When I click a button in ICS the buttons on my phone light - but the activities don't open as they do on Froyo. I have modified ActionBarSherlock very little. I know from a toast message that the click is registered in ICS. But the activity does not start. In the LogCat I get a
window already focused ignoring focus gain of com.android.internal.view.iinputmethodclient
every time I click one of the buttons. I have a guess that maybe it's around here that the problem might be:
public boolean onCreateOptionsMenu(Menu menu) {
//Used to put dark icons on light action bar
boolean isLight = true;
menu.add("Save")
.setIcon(isLight ? R.drawable.ic_stilling1 : R.drawable.ic_stilling1)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add("Search")
.setIcon(isLight ? R.drawable.ic_search_inverse : R.drawable.ic_search)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add("Opdatér")
.setIcon(isLight ? R.drawable.ic_refresh_inverse : R.drawable.ic_refresh)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
//This uses the imported MenuItem from ActionBarSherlock
Toast.makeText(this, "Got click: " + item.toString(), Toast.LENGTH_SHORT).show();
if (item.toString()=="Save"){
startActivity (new Intent(getApplicationContext(), F3Activity.class));
return true;
}
if (item.toString()=="Search"){
startActivity (new Intent(getApplicationContext(), SearchActivity.class));
return true;
}
if (item.toString()=="Opdatér"){
startActivity (new Intent(getApplicationContext(), ABSTabsViewPagerActivity.class));
return true;
}
return true;
}
As you can see I'm a noob in programming :-) Does anyone have a clue on how I can get the buttons to react in ICS? I've tested on Jelly Bean on my own old HTC Desire and ICS on my friends' Samsung Galaxy II and III with the exact same result.
[EDIT] : This made it work:
if (item.getTitle()=="Save")
instead of
if (item.toString()=="Save")
A beginner's mistake, I knew it ;-)
Use String.equals(String) for string comparisons. The right approach would be
if ("Save".equals(item.getTitle()))
Consider giving your menu items an ID, and using that for figuring out which is clicked instead.
This made it work:
if (item.getTitle()=="Save")
instead of
if (item.toString()=="Save")
I'm facing a problem when actions intended to be shown in overflow menu in action bar are not there on Galaxy S3. As a consequence the UX is somewhat confusing - my action bar on Galaxy S3 is there to only display app logo and name but offering no extra functionality.
I'd like to have an identical UX on all devices running on Android 4.x with actions in the overflow menu. Is this possible without using third-party components such as ActionBarSherlock?
Thanls
This is a decision made by some manufacturers that requires some "bad" solutions if you really want to do this. The overflow menu is just the "regular" old menu button that all android devices used to have. When the menu button got removed by Google in Honeycomb and ICS some manufacturers decided to keep the menu button. This has lead to great confusion about what the menu button is and does.
You should keep in mind though that the user using a S3 would expect to have a functional menu button as they would not be used to seeing a 3-dot menu. All apps using the built in menu system should appear in a way to the user that they expect. Therefor I would strongly recommend against the urge to have your app look exactly the same on all devices in this matter since it would most likely confuse users more then help them. It should be possible, to both implement the "proper" menu system and a "custom/fake" 3-dot menu if you wish however.
This post seems to have some good guidelines:
https://stackoverflow.com/a/10713860/1068167
There is a quick and dirty way to fake the absence of a hardware menu button using reflection to set a field in your app's ViewConfiguration instance.
The following code snip can be added to your activity and called during onCreate().
private void enableActionBarOverflow() {
try {
ViewConfiguration config = ViewConfiguration.get(this);
Field menuKeyField = ViewConfiguration.class
.getDeclaredField("sHasPermanentMenuKey");
if(menuKeyField != null) {
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
}
} catch (Exception e) {
e.printStackTrace();
}
}
Not a clean solution as the implementation of ViewConfiguration could change at some point in the future, and since the sHasPermanentMenuKey field is private, there's no guarantee that the field will always be there.
However, I would only use this as a last resort if you absolutely must have an overflow menu on devices that have a menu key.
Assuming you're minimum API is 11 (Honeycomb) or greater, a better solution would be to make your own overflow menu like so:
Add a menu item for the overflow in your menu.xml, setting it to always show and inflate in your onCreateOptionsMenu()
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
...
<item
android:id="#+id/action_overflow"
android:icon="#drawable/ic_action_settings"
android:title="#string/settings"
android:showAsAction="always">
</item>
</menu>
,
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater mi = getMenuInflater();
mi.inflate(R.menu.menu, menu);
return super.onCreateOptionsMenu(menu);
}
Create a separate overflow_menu.xml resource for your choices you want in the overflow menu
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/overflow_action1"
android:title="#string/overflow_action1">
</item>
<item
android:id="#+id/overflow_action2"
android:title="#string/overflow_action2">
</item>
</menu>
In your onOptionsItemSelected() method, handle the selection of your overflow menu
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
...
case R.id.action_overflow:
PopupMenu popup = new PopupMenu(
this, findViewById(R.id.action_overflow));
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(R.menu.overflow_menu, popup.getMenu());
popup.setOnMenuItemClickListener(this);
popup.show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Implement the PopupMenu.OnMenuItemClickListener interface in your activity to handle the clicks of the overflow items
#Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.overflow_action1:
//do stuff
return true;
case R.id.overflow_action2:
//do stuff
return true;
default:
return false;
}
}
In trying to follow the Android Design Guidelines, I'm running into a small quandary.
I want to have a list of items that I can long-press several of (multi-select), and then perform bulk actions on them.
The Design Guidelines suggest using the Contextual Action Bar for this, and it sounds perfectly like what I had in mind. Problem is, I'm trying to maintain compatibility backwards to API 7 (due to my phone being 2.3.3 currently).
I'm using ActionBarSherlock to get other actionbar stuff, but I can't seem to figure out how to get it to either fire up a contextual action bar, nor have I figured out how to add buttons arbitrarily to the ActionBar in ABS. I see you can do tabs, so maybe that's the answer, but since I'm trying to allow multi-select, I don't want to have the normal modal context menu.
This is a late answer, but I think would help people stuck.
Opening the contextual action bar is actually pretty simple, at any point in your activity you just have to call:
startActionMode(mActionModeCallback);
If you are not in your main activity, like in fragments, you can get a reference with
getSherlockActivity().startActionMode(mActionModeCallback);
and this is the callback
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback(){
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.actionbar_context_menu, menu);
return true;
}
#Override
public void onDestroyActionMode(ActionMode mode) {
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item1:
return true;
case R.id.menu_item2:
//close the action mode
//mode.finish();
return true;
default:
mode.finish();
return false;
}
}
};
The xml is a simple menu like the actionbar one:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/menu_item1"
android:icon="#drawable/ic_item1"
android:title="#string/ITEM1"
android:showAsAction="always|withText" />
<item android:id="#+id/menu_item2"
android:icon="#drawable/ic_item2"
android:title="#string/ITEM2"
android:showAsAction="always|withText" />
Setting up contextual actionbar is the same to setting up the 'regular' ActionBar items as far as the XML is concerned. This example in the developer's guide explains it all.
In order to use ActionBarSherlock, replace the default Android-callbacks to the ActionBarSherlock-edited callbacks (e.g. instead of Android.View.ActionMode, use com.actionbarsherlock.view.ActionMode).
ActionBarSherlock has its own implementation of ActionMode, but you'll have to manualy controll its lifesycle, I wrote a tutorial about this.
For long click sample please refer to below links. First one is java code required for sample. And second one is how to define the layout;
Java source
Layout xml
I will answer second part of your question. Here is an example how to add any View instance (button in the code below) actionbar with ActionBarSherlock library:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
refreshButton = (RotatingButton) LayoutInflater.from(this).inflate(R.layout.actionbar_customview_refresh, null);
refreshButton.setOnClickListener(refreshButtonListener);
MenuItem item = menu.add(0, android.R.id.copy, 0, getString(R.string.actionbar_refresh));
item.setActionView(refreshButton);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_activity_action_bar, menu);
}
I was facing the same issue. It was solved when I found this link. Basically, you have to create a callback class that implements ActionMode.Callback. In this class, you inflate the Action Bar with your contextual Action Bar. At each selection (or long click), you start the callback using the startActionMode method. See the link for an working code =]
EDIT: There is also an example on Sherlock's samples under /samples/demos/src/com/actionbarsherlock/sample/demos/ActionModes.java
I'd like to have all of the menu items that don't fit into the ActionBar go into the overflow menu (the one that is reached from the Action Bar not the menu button) even on devices that do have a Menu button. This seems much more intuitive for users than throwing them into a separate menu list that requires the user to jump from a touch(screen) interaction to a button based interaction simply because the layout of the ActionBar can't fit them on the bar.
On the emulator I can set the "Hardware Back/Home Keys" value to "no" and get this effect.
I've searched for a way to do this in code for an actual device that has a menu button but can't fine one. Can anyone help me?
You can also use this little hack here:
try {
ViewConfiguration config = ViewConfiguration.get(this);
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
if (menuKeyField != null) {
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
}
} catch (Exception ignored) {
}
Good place to put it would be the onCreate-Method of your Application class.
It will force the App to show the overflow menu. The menu button will still work, but it will open the menu in the top right corner.
[Edit] Since it has come up several times now: This hack only works for the native ActionBar introduced in Android 3.0, not ActionBarSherlock. The latter uses its own internal logic to decide whether to show the overflow menu. If you use ABS, all platforms < 4.0 are handled by ABS and are thus subjected to its logic. The hack will still work for all devices with Android 4.0 or greater (you can safely ignore Android 3.x, since there aren't really any tablets out there with a menu button).
There exists a special ForceOverflow-Theme that will force the menu in ABS, but apperently it is going to be removed in future versions due to complications.
EDIT: Modified to answer for the situation of physical menu button.
This is actually prevented by design. According to the Compatibility Section of the Android Design Guide,
"...the action overflow is available from the menu hardware key. The resulting actions popup... is displayed at the bottom of the screen."
You'll note in the screenshots, phones with a physical menu button don't have an overflow menu in the ActionBar. This avoids ambiguity for the user, essentially having two buttons available to open the exact same menu.
To address the issue of consistency across devices: Ultimately it's more important to the user experience that your app behave consistently with every other app on the same device, than that it behave consistently with itself across all devices.
I use to workaround it by defining my menu like this (also with ActionBarSherlock icon used in my example):
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/menu_overflow"
android:icon="#drawable/abs__ic_menu_moreoverflow_normal_holo_light"
android:orderInCategory="11111"
android:showAsAction="always">
<menu>
<item
android:id="#+id/menu_overflow_item1"
android:showAsAction="never"
android:title="#string/overflow_item1_title"/>
<item
android:id="#+id/menu_overflow_item2"
android:showAsAction="never"
android:title="#string/overflow_item2_title"/>
</menu>
</item>
</menu>
I admit that this may require manual "overflow-management" in your xml, but I found this solution useful.
You can also force device to use HW button to open the overflow menu, in your activity:
private Menu mainMenu;
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO: init menu here...
// then:
mainMenu=menu;
return true;
}
#Override
public boolean onKeyUp(int keycode, KeyEvent e) {
switch(keycode) {
case KeyEvent.KEYCODE_MENU:
if (mainMenu !=null) {
mainMenu.performIdentifierAction(R.id.menu_overflow, 0);
}
}
return super.onKeyUp(keycode, e);
}
:-)
If you are using the action bar from the support library (android.support.v7.app.ActionBar), use the following:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:yorapp="http://schemas.android.com/apk/res-auto" >
<item
android:id="#+id/menu_overflow"
android:icon="#drawable/icon"
yourapp:showAsAction="always"
android:title="">
<menu>
<item
android:id="#+id/item1"
android:title="item1"/>
<item
android:id="#+id/item2"
android:title="item2"/>
</menu>
</item>
</menu>
This kind of method is prevented by the Android Developers Design System, but I found a way to pass it:
Add this to your XML menu file:
<item android:id="#+id/pick_action_provider"
android:showAsAction="always"
android:title="More"
android:icon="#drawable/ic_action_overflow"
android:actionProviderClass="com.example.AppPickActionProvider" />
Next, create a class named 'AppPickActionProvider', and copy the following code to it:
package com.example;
import android.content.Context;
import android.util.Log;
import android.view.ActionProvider;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.SubMenu;
import android.view.View;
public class AppPickActionProvider extends ActionProvider implements
OnMenuItemClickListener {
static final int LIST_LENGTH = 3;
Context mContext;
public AppPickActionProvider(Context context) {
super(context);
mContext = context;
}
#Override
public View onCreateActionView() {
Log.d(this.getClass().getSimpleName(), "onCreateActionView");
return null;
}
#Override
public boolean onPerformDefaultAction() {
Log.d(this.getClass().getSimpleName(), "onPerformDefaultAction");
return super.onPerformDefaultAction();
}
#Override
public boolean hasSubMenu() {
Log.d(this.getClass().getSimpleName(), "hasSubMenu");
return true;
}
#Override
public void onPrepareSubMenu(SubMenu subMenu) {
Log.d(this.getClass().getSimpleName(), "onPrepareSubMenu");
subMenu.clear();
subMenu.add(0, 1, 1, "Item1")
.setIcon(R.drawable.ic_action_home).setOnMenuItemClickListener(this);
subMenu.add(0, 2, 1, "Item2")
.setIcon(R.drawable.ic_action_downloads).setOnMenuItemClickListener(this);
}
#Override
public boolean onMenuItemClick(MenuItem item) {
switch(item.getItemId())
{
case 1:
// What will happen when the user presses the first menu item ( 'Item1' )
break;
case 2:
// What will happen when the user presses the second menu item ( 'Item2' )
break;
}
return true;
}
}
Well I think that Alexander Lucas has provided the (unfortunately) correct answer so I'm marking it as the "correct" one. The alternative answer I'm adding here is simply to point any new readers to this post in the Android Developers blog as a rather complete discussion of the topic with some specific suggestions as to how to deal with your code when transitioning from pre-level 11 to the new Action Bar.
I still believe it was a design mistake not have the menu button behave as a redundant "Action Overflow" button in menu button enabled devices as a better way to transition the user experience but its water under the bridge at this point.
I'm not sure if this is what you're looking for, but I built a Submenu within the ActionBar's Menu and set its icon to match the Overflow Menu's Icon. Although it wont have items automatically sent to it, (IE you have to choose what's always visible and what's always overflowed) it seems to me that this approach may help you.
In the gmail app that comes with ICS pre-installed, the menu button is disabled when you have multiple items selected. The overflow menu is here "forced" to be triggered by the use of the overflow button instead of the physical menu button. Theres a 3rd-party lib called ActionBarSherlock which lets you "force" the overflow menu. But this will only work on API level 14 or lower(pre-ICS)
If you use Toolbar, you can show the overflow on all versions and all devices, I've tried on some 2.x devices, it works.
Sorry if this problem is dead.
Here is what I did to resolve the error. I went to layouts and created two ones containing toolbars. One was a layout for sdk version 8 and the other was for sdk version 21. On version 8, I used the android.support.v7.widget.Toolbar while I used android.widget.Toolbar on the sdk 21 layout.
Then I inflate the toolbar in my activity. I check the sdk to see if it was 21 or higher. I then inflate the corresponding layout. This forces the hardware button to map onto the toolbar you actually designed.
For anyone using the new Toolbar:
private Toolbar mToolbar;
#Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
...
}
#Override
public boolean onKeyUp(int keycode, KeyEvent e) {
switch(keycode) {
case KeyEvent.KEYCODE_MENU:
mToolbar.showOverflowMenu();
return true;
}
return super.onKeyUp(keycode, e);
}
Each time my Optionsmenu opens (onCreateOptionsMenu(..) is called),
I get these warnings:
"No keyboard for id 0"
and
"Using default keyMap:
/system/usr/keychars/qwerty.kcm.bin"
I couldn't find out what they mean, does anyone know ? I didn't like this answer.
Here's the simple code I'm using:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.optmenu_start, menu);
return true;
}
and the optmenu_start.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/optmenu_prefs"
android:title="#string/optmenu_prefs"
android:icon="#drawable/icon_menu_prefs"
/>
<item
android:id="#+id/optmenu_help"
android:title="#string/optmenu_help"
android:icon="#drawable/icon_menu_help"
/>
</menu>
Google's answer is
" The log is normal; it happens the first time a keymap is needed for a device
in a process. "
http://groups.google.com/group/android-developers/browse_thread/thread/477caf755085b108
So therefore if you started Activity2 before Activity1 you would see the warning in Activity2 and not Activity1!
As I said I think a lot of developers get this message and it's just a warning not an error; it can be ignored.
As #Blundell said, it's normal.
But it always consumes the first keyPressed event.
Finally, I add this on init:
setFocusableInTouchMode(true);
I don't know why, but this warning message never annoys me again.