I have an app that shows a few fragments (of the same type) in a ViewPager
and I\'m having some trouble with context menu items. (I\'m using the support library
It happens because of this:
public boolean dispatchContextItemSelected(MenuItem item) {
if (mActive != null) {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
if (f != null && !f.mHidden) {
if (f.onContextItemSelected(item)) {
return true;
}
}
}
}
return false;
}
As you can see, FragmentManager calls Fragment.onContextItemSelected for all of his own fragments until it returns true. In your example I can offer such fix:
public static class TestListFragment extends ListFragment {
private int mNumber = 0;
private ArrayList<String> mItems;
public static TestListFragment newInstance(int number) {
Bundle args = new Bundle();
args.putInt("number", number + 1);
TestListFragment fragment = new TestListFragment();
fragment.setArguments(args);
return fragment;
}
public TestListFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNumber = getArguments().getInt("number");
mItems = new ArrayList<String>();
mItems.add("I am list #" + mNumber);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mItems));
registerForContextMenu(getListView());
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(mNumber, 0, 0, "Hello, World!");
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if(item.getGroupId() == mNumber){
Log.d("ViewPagerContextMenuBug", "onContextItemSelected called for number " + mNumber);
Toast.makeText(getActivity(), "onContextItemSelected called for number " + mNumber, Toast.LENGTH_SHORT).show();
return true;
}
return false;
}
}
Using intent for each of the menu items worked well for me.
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextmenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = super.getActivity.getMenuInflater();
inflater.infalte(R.menu.list_item, menu);
for(int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
Intent intent = new Intent();
intent.putExtra(KEY_EXTRA_FRAGMENT_ID, this.fragmentId);
if (item != null) {
item.setIntent(intent);
}
}
}
@Override
public boolean onContextItemSelected(MeniItem item) {
Intent intent = item.getIntent();
if (intent != null) {
if (intent.getIntExtra(KEY_EXTRA_FRAGMENT_ID, -1) == this.fragmentId) {
// Implement code according the item function.
return true;
}
}
return super.onContextItemSelected(item);
}
The getUserVisibleHint()
solution doesn't work for me - it always returns true
even when the fragment isn't on screen. The getGroupId()
solution doesn't work either when inflating the menu from an XML resource, which is my situation.
It seems that unless Android changes, any solution is always going to be a bit hacky. I create a global variable to store an ID reference for the current fragment in onCreateView
. I then pass it to each ContextMenu
MenuItem
in onCreateContextMenu
. When an item is selected, I validate that these two IDs are the same.
private int myFragmentReference;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Initialisation stuff
myFragmentReference = 12345;
}
@Override
public void onCreateContextMenu(ContextMenu contextMenu, View v, ContextMenu.ContextMenuInfo contextMenuInfo) {
// Usual stuff
int size = contextMenu.size();
for (int i = 0; i < size; i++) {
MenuItem menuItem = contextMenu.getItem(i);
Intent intent = new Intent();
intent.putExtra("id", myFragmentReference);
menuItem.setIntent(intent);
}
}
@Override
public boolean onContextItemSelected(MenuItem menuItem) {
int id = 0;
Intent intent = menuItem.getIntent();
if (intent != null) {
id = intent.getIntExtra("id", 0);
}
if (id == myFragmentReference) {
// This is the currently displayed fragment
}
}
So this is some sort of idiotic design decision by Google or something that has just gone totally unconsidered. The simplest way to work around this is to wrap the onContextItemSelected
call with an if statement like this:
if (getUserVisibleHint()) {
// Handle menu events and return true
} else
return false; // Pass the event to the next fragment
The compatibility library in ActionBarSherlock 3.5 had a hack like this.
Oh Google, I mean WTF?
The problem is that onContextItemSelected is very generic, and it is called for each menu item of each fragment.
You can use MenuItem.OnMenuItemClickListener to force fragment menu to not use all onContextItemSelected, but only same function of this fragment.
Use next implementation:
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getActivity().getMenuInflater();
if (v == btnShare) {
inflater.inflate(R.menu.share_menu, menu);
for (int i = 0; i < menu.size(); ++i) {
MenuItem item = menu.getItem(i);
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
onContextItemSelected(item);
return true;
}
});
}
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
switch (item.getItemId()) {
case R.id.print:
// ...
}
}