I've discovered a strange problem while try to implement ListView with multichoice mode. I use CHOICE_MODE_MULTIPLE_MODAL to turn on multichoice mode, and MultiChoiceModeListener to provide action mode menu and handle events. Here is my code:
ListView listView = (ListView) findViewById(R.id.list);
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.item,
new String[] {"111", "222", "333", "444", "555"});
listView.setAdapter(adapter);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
#Override
public void onItemCheckedStateChanged(ActionMode actionMode, int i, long l, boolean b) {
}
#Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
MenuInflater inflater = actionMode.getMenuInflater();
inflater.inflate(R.menu.my_menu, menu); // my_menu is just stub empty menu
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
return false;
}
#Override
public void onDestroyActionMode(ActionMode actionMode) {
}
});
It works fine, I can select my list items and action mode appears: screenshot. The problem is that when I rotate my device and then close action mode by pressing back button, sometimes (not every time) the action bar becomes ugly, with strange light background: screenshot
It is reproducing on my device with Android 5.0. The code is quite easy, and I don't think that it is bug in my code. Maybe, it is internal bug in Android system? Did anyone face this problem? Thanks in advance!
You should force the ActionBar to redraw itself. Check my answer here:
https://stackoverflow.com/a/53997778/3185295
Related
I extend AbsListView.MultiChoiceModeListener for multi selection in ListView, I want to change the action menu dynamically (when more than one ListView item selected).
private class ModeCallback implements ListView.MultiChoiceModeListener {
//inflate menu
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.compose_multi_select_menu, menu);
}
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
//want to remove some menu here, but not work
if (getListView().getCheckedItemCount() > 1) {
MenuItem item = menu.getItem(5);
menu.removeItem();
}
}
}
I try to remove MenuItem in onPrepareActionMode(), but not work. Also tried mode.invalidate() in onItemCheckedStateChanged().
Actually, I find in onPrepareActionMode() the menu passed in have no MenuItem at all.
Anyone can help on this ?
You can modify menu in your onItemCheckedStateChanged() by showing or hiding items like this:
Menu menu = mode.getMenu();
menu.findItem(R.id.some_item_id).setVisible(false);
where mode is ActionMode passed to onItemCheckedStateChanged
I'm a Java programmer with experience. I started working on a small Android project and i got soooo frustrated by how much Android has complicated programming just to be more flexible.
Exactly, the problem i have is as follows: i have a menu item that i need to use in several menus inside mu app. Is there a way to create a menu item object (possible associate some function to it) and just pass it around so any activity can add it to it's own contex menu?
I have tried this:
Created a "res.menu.mymenu.xml" file that contains a menu item with the same id as menu item from another "res.menu.mymenu2.xml" file. Then i have tried this:
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(getMenuToInflate(), menu);
menu.add(0, R.id.mnhidefrom, 0, ((MenuItem)findViewById(R.id.mnhidefrom)).getTitle());
HideMenuAction.prepareAndGetHideMeMenuItem(menu, menuInfo); //creating an intent for "hide" menu item that will hold some data needed when user clicks the menu item.
}
But no luck... I got "null pointer exception" on "menu.add" line...
Is there a normal, object oriented way, to create a menu item that will know what it should do regardless of anything outside (like,say, menu it belongs to) and just pass it around like any other object and add it to any menu i like (just like i can do with Swing menu and JMenuItems)?
Ahmad has half the story. This will give you a base of menu options that are always available. The way to add menu options specific to each extending activity is to write a Fragment for it. The fragment will included in the activity layout, but you can make it take up no room or use it for padding. When a Fragment implements onCreateOptionsMenu, the choices it adds are merged with the ones that the Activity created.
Read everything to do with the options menu in this guide for more details:
http://developer.android.com/guide/topics/ui/menus.html
Is there a normal, object oriented way
Yes there is. It's called inheritance!
Create a base Activityin which you inflate the OptionsMenu once and extend this Activity for all your Activities.
public class BaseActivity extends Activity {
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.blabla:
// do something
break;
case R.id.blablab:
// do something
break;
}
return true;
}
}
Now you can do this:
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
return true;
}
}
I used a BaseActivity as Ahmad described. It works fine.
Now I want to show a special menu item in one specific Activity (which extends the BaseActivity).
#Override
public boolean onOptionsItemSelected(android.view.MenuItem item) {
switch (item.getItemId()) {
case 0:
Intent intent = new Intent(this, BlaActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItem item = menu.add(Menu.NONE, 0, 0, "Edit");
item.setIcon(R.drawable.content_edit);
return super.onCreateOptionsMenu(menu);
}
This works as I expected, but I can't get the new inserted menuitem to get in the first position.
I need it to be in first position, so it is displayed directly in the ActionBar and the user sees that there is a new option without clicking on the options button.
Edit: I just found the solution myself.
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItem item = menu.add(Menu.NONE, 0, 0, "Bearbeiten");
item.setIcon(R.drawable.content_edit);
if (android.os.Build.VERSION.SDK_INT >= 11) {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); //<-----
}
return super.onCreateOptionsMenu(menu);
}
I'm trying to implement the contextual action bar in my app. I'm extending a ListFragment and I have a custom ArrayAdapter and list item xml defined. Clicking on items works fine and the background color changes on the list item. Where I'm running into trouble is when selecting multiple items. The contextual action bar comes up and I can tell I'm actually selecting items when I touch them because I'm having it log which items are selected, but the background highlight color on the list item does not change! It seems like some other people were running into this problem when using the Fragments API as well and they had come up with a sort-of hack to get it to work properly. However, I was wondering if anyone has a definitive answer as to why my list doesn't show selected items.
For good measure, here's my code:
ListView list = getListView();
list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
list.setSelector(R.drawable.list_selector);
// configure contextual action bar
list.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
#Override
public void onItemCheckedStateChanged(ActionMode actionMode, int position, long id, boolean b) {
Log.i("debug", "item " + position + " changed state");
}
#Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
MenuInflater inflater = actionMode.getMenuInflater();
inflater.inflate(R.menu.context_menu, menu);
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem item) {
switch (item.getItemId()) {
case R.id.delete:
Log.i("debug", "delete stuff");
return true;
default:
return false;
}
}
#Override
public void onDestroyActionMode(ActionMode actionMode) {
}
});
I provide my custom list item view with a background selector.
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="#android:integer/config_mediumAnimTime">
<item android:state_activated="true" android:drawable="#drawable/number_bg_pressed" />
<item android:drawable="#android:color/transparent" />
I refer to APIdemos View/List/List16 example.
I use android.R.layout.simple_list_item_activated_1 for layout when I set up SimpleCursorAdapter. Custom theme may be a better solution. In my case I have a static method to retrieve the layout depending on android api version.
In Android 3.0, when you select some text for example, the ActionBar switches to a ContextMenu-like mode, which enables you to do actions with the selected text: copy/share/etc, and a "Done" button appears on the left side to enable the user to leave this mode.
How can I switch the ActionBar into this mode in my app (with my menu items of course)? I just couldn't find this in the docs.
To use the new contextual action bar, see "Enabling the contextual action mode for individual views".
It states:
If you want to invoke the contextual action mode only when the user selects specific
views, you should:
Implement the ActionMode.Callback interface. In its callback methods, you
can specify the actions for the contextual action bar, respond to click events on action items, and handle other lifecycle events for the action mode.
Call startActionMode() when you want to show the
bar (such as when the user long-clicks the view).
For example:
Implement the ActionMode.Callback interface:
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
// Called when the action mode is created; startActionMode() was called
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate a menu resource providing context menu items
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.context_menu, menu);
return true;
}
// Called each time the action mode is shown. Always called after onCreateActionMode, but
// may be called multiple times if the mode is invalidated.
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false; // Return false if nothing is done
}
// Called when the user selects a contextual menu item
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_share:
shareCurrentItem();
mode.finish(); // Action picked, so close the CAB
return true;
default:
return false;
}
}
// Called when the user exits the action mode
#Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
}
};
Notice that these event callbacks are almost exactly the same as the callbacks for the options menu, except each of these also pass the ActionMode object associated with the event. You can use ActionMode APIs to make various changes to the CAB, such as revise the title and
subtitle with setTitle() and setSubtitle() (useful to indicate how many items are
selected).
Also notice that the above sample sets the mActionMode variable null when the
action mode is destroyed. In the next step, you'll see how it's initialized and how saving
the member variable in your activity or fragment can be useful.
Call startActionMode() to enable the contextual
action mode when appropriate, such as in response to a long-click on a View:
someView.setOnLongClickListener(new View.OnLongClickListener() {
// Called when the user long-clicks on someView
public boolean onLongClick(View view) {
if (mActionMode != null) {
return false;
}
// Start the CAB using the ActionMode.Callback defined above
mActionMode = getActivity().startActionMode(mActionModeCallback);
view.setSelected(true);
return true;
}
});
When you call startActionMode(), the system returns
the ActionMode created. By saving this in a member variable, you can
make changes to the contextual action bar in response to other events. In the above sample, the
ActionMode is used to ensure that the ActionMode instance
is not recreated if it's already active, by checking whether the member is null before starting the
action mode.
Enabling batch contextual actions in a ListView or GridView
If you have a collection of items in a ListView or GridView (or another extension of AbsListView) and want to
allow users to perform batch actions, you should:
Implement the AbsListView.MultiChoiceModeListener interface and set it
for the view group with setMultiChoiceModeListener(). In the listener's callback methods, you can specify the actions
for the contextual action bar, respond to click events on action items, and handle other callbacks
inherited from the ActionMode.Callback interface.
Call setChoiceMode() with the CHOICE_MODE_MULTIPLE_MODAL argument.
For example:
ListView listView = getListView();
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
#Override
public void onItemCheckedStateChanged(ActionMode mode, int position,
long id, boolean checked) {
// Here you can do something when items are selected/de-selected,
// such as update the title in the CAB
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Respond to clicks on the actions in the CAB
switch (item.getItemId()) {
case R.id.menu_delete:
deleteSelectedItems();
mode.finish(); // Action picked, so close the CAB
return true;
default:
return false;
}
}
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate the menu for the CAB
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.context, menu);
return true;
}
#Override
public void onDestroyActionMode(ActionMode mode) {
// Here you can make any necessary updates to the activity when
// the CAB is removed. By default, selected items are deselected/unchecked.
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// Here you can perform updates to the CAB due to
// an invalidate() request
return false;
}
});
That's it. Now when the user selects an item with a long-click, the system calls the onCreateActionMode()
method and displays the contextual action bar with the specified actions. While the contextual
action bar is visible, users can select additional items.
In some cases in which the contextual actions provide common action items, you might
want to add a checkbox or a similar UI element that allows users to select items, because they
might not discover the long-click behavior. When a user selects the checkbox, you
can invoke the contextual action mode by setting the respective list item to the checked
state with setItemChecked().
Yeah, I couldn't find it either -- I had to ask at Google I|O.
Use startActionMode(). Here is one of their samples that demonstrates it. I need to do more work in this area myself.
Maybe a bit late but here's a tutorial for the actionmode:
http://www.vogella.com/articles/AndroidListView/article.html#listview_actionbar
The problem is that the following method gets called one time when the Menu button is pressed:
public boolean onCreateOptionsMenu(Menu menu)
How can I recreate the menu at a later time in order to change some options, disable some options, etc?
Override this
onPrepareOptionsMenu(Menu menu)
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(R.id.refresh);
if (item != null) {
item.setVisible (shouldIShowThisItem)
}
}
invalidateOptionsMenu();
if you want to push the menu changes use this.