I want use a Contextual Action Bar (CAB) in my app but is not compatible with old versions of Android so I'm using this tutorial: http://www.miximum.fr/tutos/849-porting-the-contextual-anction-mode-for-pre-honeycomb-android-apps
My code is:
public class SongsFragment extends SherlockListFragment implements
LoaderManager.LoaderCallbacks<Cursor>, OnLongClickListener{
...
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
activity = this.getActivity();
...
mMode = null;
mListView = getListView();
mListView.setItemsCanFocus(false);
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
mListView.setOnLongClickListener(this);
}
#Override
public boolean onLongClick(View v) {
SparseBooleanArray checked = mListView.getCheckedItemPositions();
boolean hasCheckedElement = false;
for (int i = 0; i < checked.size() && !hasCheckedElement; i++) {
hasCheckedElement = checked.valueAt(i);
}
if (hasCheckedElement) {
if (mMode == null) {
mMode = activity.startActionMode(mActionModeCallback);
}
} else {
if (mMode != null) {
mMode.finish();
}
}
return false;
}
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Create the menu from the xml file
activity.getSupportMenuInflater().inflate(R.menu.cab_songs, menu);
return true;
}
...
I have errors in:
-"activity.startActionMode(mActionModeCallback);": The method startActionMode(ActionMode.Callback) in the type Activity is not
applicable for the arguments (ActionMode.Callback)
-activity.getSupportMenuInflater().inflate(R.menu.cab_songs, menu);": The method getSupportMenuInflater() is undefined for the
type FragmentActivity
Any idea? is there another solution for CAB using sherlock?
Change your imports for ActionMode and MenuInflater to their ActionBarSherlock equivalents (com.actionbarsherlock.view.ActionMode and com.actionbarsherlock.view.MenuInflater).
I solved using:
getSherlockActivity().startActionMode(mActionModeCallback);
...
mode.getMenuInflater().inflate(R.menu.cab_songs, menu);
Now, I want do an action when the user does a "click" and another action when the user does a "long click". I've "onItemLongClick" and "onListItemClick" but sometimes longClick is not called and when it is called if I release the "onListItemClick" is called. How can I do this actions?
Related
I have a ListView and a Toolbar above of it in my Activity. I want to replace the Toolbar default icons (search and settings) with delete and edit icons when clicking an item of the ListView to perform some action.
So what I've understood from your question, you're looking for ActionMode which might serve your purpose.
So here's an implementation guideline.
Declare an ActionMode in your Activity and let your Activity to implement ActionMode.Callback.
public class YourActivity extends AppCompatActivity implements ActionMode.Callback {
// Declare ActionMode here
private ActionMode actionMode;
// Now implement the callback functions for ActionMode
#Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
#Override
public boolean onCreateActionMode(final ActionMode actionMode, Menu menu) {
MenuInflater inflater = actionMode.getMenuInflater();
// Inflate your menu here
inflater.inflate(R.menu.list_item_click_menu, menu);
return true;
}
#Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.action_delete:
// Do something
actionMode.finish();
return true;
case R.id.action_edit:
// Do something
actionMode.finish();
return true;
default:
return false;
}
}
#Override
public void onDestroyActionMode(ActionMode actionMode) {
try {
this.actionMode = null;
// Do something. Reset the views maybe?
} catch (Exception e) {
e.printStackTrace();
}
}
}
Now to initiate your ActionMode you need to have this in your onClick function of the list item.
listItem.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (actionMode != null) {
return true;
}
// Show ActionMode
actionMode = startSupportActionMode(this);
actionMode.invalidate();
}
});
You may need to reset the ActionMode sometimes.
#Override
public void onResume() {
super.onResume();
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
}
I have been following this tutorial: http://developer.android.com/guide/topics/ui/menus.html#CAB. However, when I run the application, I get an error:
java.lang.RuntimeException:
Unable to instantiate activity
ComponentInfo{com.example.ayushi.chaseyourdream/com.example.ayushi.chaseyourdream.MainActivity}:
java.lang.InstantiationException: can't instantiate class
com.example.ayushi.chaseyourdream.MainActivity
Here's my MainActivity. My application was working completely fine before I added code between label 1 and `label 2'.
public abstract class MainActivity extends ActionBarActivity implements AbsListView.MultiChoiceModeListener{
DbHelper db;
ListView myList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = new DbHelper(this);
myList = (ListView) findViewById(R.id.newList);
loadData();
// LABEL 1
myList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
myList.setMultiChoiceModeListener(new AbsListView.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_main, menu);
return true;
}
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.
}
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// Here you can perform updates to the CAB due to
// an invalidate() request
return false;
}
});
// LABEL 2
}
public void onResume()
{
super.onResume();
loadData();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public void loadData()
{
Cursor cursor = null;
try {
cursor = db.fetchData();
}
catch(NullPointerException e)
{
e.printStackTrace();
}
ListAdapter myAdapter = new SimpleCursorAdapter(this, R.layout.tasks,
cursor,
new String[]{db._ID, db.COLUMN_1, db.COLUMN_2},
new int[]{R.id.idnum, R.id.c1, R.id.c2}, 0);
myList.setAdapter(myAdapter);
}
public void addNew(View v)
{
Intent intent = new Intent(this,AddActivity.class);
startActivity(intent);
}
}
EDIT 1: this line
public class MainActivity extends ActionBarActivity implements AbsListView.MultiChoiceModeListener{
showed me an error in Android Studio, and when I clicked Alt + Enter, it showed me two options: Make the class abstract, or implement its methods.
The first option gave me the initial problem, so now I went for option 2. Here are the additional methods implemented :
#Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
}
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return false;
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
#Override
public void onDestroyActionMode(ActionMode mode) {
}
Now, my doubt is: these are exactly the same methods in between LABEL 1 and LABEL 2, and erasing any one set will lead to errors. What should I do?
You are getting this error: Unable to instantiate activity because you made your activity abstract. Remove that and implement the methods of AbsListView.MultiChoiceModeListenerinterface.
When you're implementing an Interface so you have to implement all the methods you have listed. If you don't know what to do with method you don't use just leave them as they are.
Now, my doubt is: these are exactly the same methods in between LABEL 1 and LABEL 2, and erasing any one set will lead to errors. What should I do?
You shouldn't have two of the same. Try using #Override annotation on the methods you implemented.
i implemented a contextual action mode bar in a nested fragement. This fragment is part of a view pager and the view pager is also a fragment and part of a navigation drawer.
My Problem: I want to close the contextual action mode bar if the fragment is no more focused. So, if I swipe through the view pager the action mode bar should close. But if I use the onPause() method of the nested fragment, the method is not called directly. Often it waits until i swiped two or three times forward... Here are some pictures:
In the second picture you can see that the action mode bar is still there. So my question is:
In which method should I call my actionModeBar.finish() method, to close directly the action mode bar if i leave the fragment?
Maybe the code of the fragment helps you:
public class EditorFragment extends Fragment {
private static final String KEY_POSITION="position";
ListView listView;
private boolean isMultipleList = false;
private ActionMode acMode;
private int counterChecked = 0;
private ActionMode.Callback modeCallBack = new ActionMode.Callback() {
public boolean onPrepareActionMode(ActionMode mode, Menu menu){
return false;
}
public void onDestroyActionMode(ActionMode mode) {
listView.clearChoices();
for (int i = 0; i < listView.getChildCount(); i++)
listView.setItemChecked(i, false);
listView.post(new Runnable() {
#Override
public void run() {
listView.setChoiceMode(ListView.CHOICE_MODE_NONE);
}
});
isMultipleList = false;
counterChecked = 0;
mode = null;
}
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.setTitle("1 Aufgabe");
mode.getMenuInflater().inflate(R.menu.actionmode, menu);
return true;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.actionmode_delete:
int choiceCount = listView.getCount();
SparseBooleanArray spBoolArray = listView.getCheckedItemPositions();
DBAufgaben db = new DBAufgaben(MainActivity.getMContext());
db.open();
for (int i = 0; i < choiceCount; i++) {
if(spBoolArray.get(i)){
db.deletContact(listView.getItemIdAtPosition(i));
}
}
Cursor cursor = db.getAllRecords();
AdapterEingang adapterE = new AdapterEingang(MainActivity.getMContext(), cursor, 0);
listView.setAdapter(adapterE);
db.close();
mode.finish();
break;
case R.id.actionmode_cancel:
mode.finish();
break;
}
return false;
}
};
//......//
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = null;
int position = getArguments().getInt(KEY_POSITION, -1);
switch(position){
case 0:
rootView = inflater.inflate(R.layout.pager_list, null);
listView = (ListView) rootView.findViewById(R.id.pager_list);
Context context = MainActivity.getMContext();
DBAufgaben db = new DBAufgaben(context);
db.open();
Cursor cursor = db.getAllRecords();
AdapterEingang adapterE = new AdapterEingang(context, cursor, 0);
listView.setAdapter(adapterE);
db.close();
listView.setOnItemLongClickListener(new OnItemLongClickListener(){
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view,
int position, long id) {
if(!isMultipleList){
acMode = MainActivity.getInstance().startActionMode(modeCallBack);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
listView.setItemChecked(position, true);
isMultipleList = true;
counterChecked++;
setNewTitle();
} else {
listView.setItemChecked(position, true);
counterChecked++;
setNewTitle();
}
return true;
}
});
listView.setOnItemClickListener(new OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position,
long id) {
Log.d(getTag(), "Datensatz: "+String.valueOf(id));
if(isMultipleList){
if(listView.isItemChecked(position)){
listView.setItemChecked(position, true);
counterChecked++;
setNewTitle();
} else {
listView.setItemChecked(position, false);
counterChecked--;
setNewTitle();
}
}
}
});
break;
default:
rootView = inflater.inflate(R.layout.frag_dummy, null);
TextView txt = (TextView) rootView.findViewById(R.id.dummy_txt);
txt.setText(String.valueOf(position));
break;
}
return(rootView);
}
public void setNewTitle(){
if(counterChecked == 1){
acMode.setTitle(counterChecked+" Aufgabe");
} else {
acMode.setTitle(counterChecked+" Aufgaben");
}
}
#Override
public void onPause(){
super.onPause();
if(isMultipleList){
acMode.finish();
}
}
}
ViewPagers keep multiple pages active at any one time (by default, the page before and page after the currently shown page), hence why onPause() is not called until you swipe two pages away.
Your best bet would be to use a ViewPager.OnPageChangeListener, and show and hide the ActionMode in onPageSelected(..) (i.e. if the page selected isn't the one with the ActionMode, hide the ActionMode). You'll likely have to implement this in the Activity which hosts your ViewPager.
Here's what worked for me --
Hold a static reference to the action mode in MyFragment:
public static ActionMode mActionMode;
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mActionMode = mode;
return true;
}
#Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
}
Set ViewPager.OnPageChangeListener in MyViewPagerActivity.
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
....
#Override
public void onPageScrollStateChanged(int state) {
if(MyFragment.mActionMode != null) MyFragment.mActionMode.finish();
}
});
This worked for me:
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!isVisibleToUser && this.listView != null) {
//HACK this is done in order to finish the contextual action bar
int choiceMode = this.listView.getChoiceMode();
this.listView.setChoiceMode(choiceMode);
}
}
I combined code from the OP with the override suggested by user pomber:
I save the ActionMode to a class field when the action mode is created.
I set the field back to null when the action mode is destroyed.
I override setUserVisibleHint in my fragment and call ActionMode#finish() if the fragment isn't visible in the view pager.
I also call ActionMode#finish() in the onPause() method of the fragment to close the fragment if the user navigates elsewhere.
Code:
#Nullable
ActionMode mActionMode = null;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Start out with a progress indicator.
setListShownNoAnimation(false);
setEmptyText(getText(R.string.no_forms_in_progress));
getListView().setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL);
getListView().setMultiChoiceModeListener(new AbsListView.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 onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.context_saved_item, menu);
inflater.inflate(R.menu.context_instance_list, menu);
mActionMode = mode;
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.action_delete:
confirmDelete(getListView().getCheckedItemIds());
return true;
case R.id.action_print:
print(getListView().getCheckedItemIds());
return true;
}
return false;
}
#Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
}
});
}
#Override
public void onPause() {
super.onPause();
if (mActionMode != null) {
mActionMode.finish();
}
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (mActionMode != null && !isVisibleToUser) {
mActionMode.finish();
}
}
You can use the onDestroyOptionsMenu() method inside your Fragment:
public void onDestroyOptionsMenu() {
super.onDestroyOptionsMenu();
if (mActionMode != null) {
mActionMode.finish();
mActionMode = null;
}
}
My app has a ListView and I'm using a Contextual Action Bar for devices above SDK 11 and the old popup contextual actions for older devices. I know there's a way to use a CAB with older devices but I tried to implement it and found it wasn't worth the effort for devices that will eventually be obsolete. I know it's some code duplication, but, in theory, I will be getting rid of the old popup actions (emphasis on "in theory").
Anyhow, when I use the emulator, the CAB works fine, but the old popup actions for older devices seems to hit onContextItemSelected twice when I put a breakpoint in that event. I've just start implement a ViewPager for my app and this wasn't happening before the ViewPager so not sure if that is causing the issue.
This is the code I'm using:
public class MyFragment extends SherlockListFragment
{
private ListView mListView;
private android.view.ActionMode mActionMode;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Override
public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, v, menuInfo);
if (this.mActionMode != null) return;
menu.add(1, 0, 0, "Delete");
menu.add(1, 1, 0, "Save");
}
#Override
public void onActivityCreated(final Bundle icicle)
{
mListView = getListView();
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB)
{
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
#Override
public boolean onCreateActionMode(android.view.ActionMode mode, android.view.Menu menu) {
// Inflate the menu for the CAB
menu.clear();
menu.add(1, 1, 2, "Delete").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(1, 3, 1, "Save").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return true;
}
#Override
public boolean onActionItemClicked(android.view.ActionMode mode, android.view.MenuItem item) {
mActionMode = mode;
if (item.getGroupId() == 1)
{
switch(itemId)
{
case 0:
DeleteItem();
break;
case 1:
SaveItem();
break;
}
}
}
}
}
}
#Override
public boolean onContextItemSelected(final android.view.MenuItem item) {
if (item.getGroupId() == 1) {
final AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
final Integer position = info.position;
final int itemId = item.getItemId();
switch(itemId)
{
case 0:
DeleteItem();
break;
case 1:
SaveItem();
break;
}
}
return super.onContextItemSelected(item);
}
#Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu (menu);
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.my_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
}
ViewPager code
public class Main extends SherlockFragmentActivity
{
private static List<Integer> mIds;
#Override
public void onCreate(final Bundle icicle)
{
super.onCreate(null);
setContentView(R.layout.main);
mViewPager = (ViewPager)findViewById(R.id.viewpager); //view pager exists, so we are using the portait layout
if (mViewPager != null)
{
mIds = new ArrayList<Integer>();
mIds.add(0);
mIds.add(1);
mIds.add(2);
}
else //in landscape
{
ListFragment lf = (ListFragment)getSupportFragmentManager().findFragmentById(R.id.fragmentList);
if (lf == null)
lf = new ListFragment();
DetailFragment df = (DetailFragment)getSupportFragmentManager().findFragmentById(R.id.fragmentDetail);
if (df == null)
{
df = new DetailFragment();
df.setArguments(getIntent().getExtras());
}
getSupportFragmentManager().beginTransaction().add(R.id.fragmentList, lf).commit();
getSupportFragmentManager().beginTransaction().add(R.id.fragmentDetail, df).commit();
}
}
private static class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
//can't use getSupportFragmentManager().findFragmentById() here because I get a "Cannot make a static reference to the non-static method" error
if (index == 0)
return ListFragment.newInstance();
else
return DetailFragment.newInstance(mIds.get(index-1));
}
#Override
public int getCount() {
return 4;
}
}
}
This solution on this question fixed my issue:
How to handle onContextItemSelected in a multi fragment activity?
using getUserVisibleHint() in onContextItemSelected.
Method must return true to consume the selection.
Docs here
#Override
public boolean onContextItemSelected(final android.view.MenuItem item) {
if (item.getGroupId() == 1) {
final AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
final Integer position = info.position;
final int itemId = item.getItemId();
switch(itemId)
{
case 0:
DeleteItem();
return true;
case 1:
SaveItem();
return true;
}
}
return super.onContextItemSelected(item);
}
I'd like to replace the icon used for an ActionBar item at runtime (it appears as an action item on the ActionBar). I'm trying the following:
private Map<Integer, Integer> mPendingReplacements =
new HashMap<Integer, Integer>();
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.my_menu, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
for (Map.Entry<Integer, Integer> it : mPendingReplacements.entrySet()) {
MenuItem item = menu.findItem(it.getKey().intValue());
if (item != null) {
item.setIcon(getResources().getDrawable(it.getValue().intValue()));
}
}
mPendingReplacements.clear();
return true;
}
private void replaceMenuItemIcon(int menuItemId, int drawable) {
mPendingReplacements.put(menuItemId, drawable);
invalidateOptionsMenu();
}
private void onUserClickedSomeButton() {
replaceMenuItemIcon(R.id.foo, R.drawable.grok);
}
------------ Update -----------
Updated the above sample to use mPendingReplacements as suggested by Selvin. I can see it execute as excepted, but the actionbar icon still doesn't change immediately. Will continue digging into this, but can anyone spot if I'm missing something?
Thank you