ViewPager中的错误片段接收onContextItemSelected调用

时间:2012-03-17 19:57:24

标签: android android-support-library

我有一个应用程序,在ViewPager中显示了一些片段(相同类型),并且我在使用上下文菜单项时遇到了一些问题。 (我正在使用支持库)。

当在其中一个片段的上下文菜单中选择了上下文菜单项时,错误的片段正在接收onContextItemSelected事件调用。

例如,如果我在寻呼机中的片段#3上,则位置#2处的片段接收它。如果我滑回片段#2,片段#3将接收该呼叫。

我有一个示例here

(我目前正在自己​​的应用中解决这个问题,在每个片段中都有一个mHandleContext变量,并在页面更改时启用/禁用它。这样onContextItemSelected调用就会消失到所有片段,直到正确的片段被调用。)

我做错了什么或这是支持库的错误?作为旁注,我在使用ActionBarSherlock 3.5.1时没有发生这种情况,ActionBarSherlock 3.5.1有自己的支持库分支。

5 个答案:

答案 0 :(得分:36)

所以这是Google的某种愚蠢的设计决定,或者刚刚完全没有考虑的事情。解决此问题的最简单方法是使用if语句包装onContextItemSelected调用:

if (getUserVisibleHint()) {
    // Handle menu events and return true
} else
    return false; // Pass the event to the next fragment

ActionBarSherlock 3.5中的兼容性库有这样的黑客攻击。

答案 1 :(得分:14)

这是因为:

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;
}

如您所见,FragmentManager为其所有片段调用Fragment.onContextItemSelected,直到它返回true。在您的示例中,我可以提供此类修复:

    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;
    }

}

答案 2 :(得分:1)

对每个菜单项使用intent都很适合我。

    @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);
    }

答案 3 :(得分:1)

getUserVisibleHint()解决方案对我不起作用 - 即使片段不在屏幕上,它也始终返回true。从XML资源中膨胀菜单时,getGroupId()解决方案无效。这是我的情况。

似乎除非Android发生变化,否则任何解决方案总会有点笨拙。我创建了一个全局变量来存储onCreateView中当前片段的ID引用。然后我将其传递给ContextMenu中的每个MenuItem onCreateContextMenu。选择项目后,我会验证这两个ID是否相同。

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
    }
}

答案 4 :(得分:0)

哦Google,我的意思是WTF?

问题是 onContextItemSelected 非常通用,并且会为每个片段的每个菜单项调用它。

您可以使用 MenuItem.OnMenuItemClickListener 强制片段菜单不使用所有 onContextItemSelected ,但只能使用此片段的相同功能。

使用下一个实现:

@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:
        // ...
    }
}