【问题标题】:Wrong fragment in ViewPager receives onContextItemSelected callViewPager 中的错误片段接收 onContextItemSelected 调用
【发布时间】:2012-04-02 22:29:54
【问题描述】:

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

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

例如,如果我在寻呼机中的片段 #3 上,则位置 #2 的片段会接收它。如果我滑回片段 #2,片段 #3 会接收调用。

我有一个样本here

(我目前正在我自己的应用程序中通过在每个片段中使用mHandleContext 变量并在页面更改时启用/禁用它来解决此问题。这样onContextItemSelected 调用将发送到所有片段直到调用正确的那个。)

我做错了什么还是支持库的错误?附带说明一下,当我使用 ActionBarSherlock 3.5.1 时没有发生这种情况,它有自己的支持库的分支。

【问题讨论】:

标签: android android-support-library


【解决方案1】:

这是因为:

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

}

【讨论】:

  • 我试过了,它似乎不起作用。无论长按什么片段,item.getGroupId() 都会返回 0。
  • 为此目的使用 groupID 是个好主意。这绝对是最好的答案。
【解决方案2】:

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

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

ActionBarSherlock 3.5 中的兼容性库有这样的 hack。

【讨论】:

  • @Seraph 的答案似乎更正确,只要有对此行为的解释。我遇到了同样的问题,并通过对不同片段的上下文菜单使用不同的 groupID(如他的解决方案)来解决它
  • Arnt hints 被认为不可靠?
  • 理论上可能,但 ViewPager 的两个片段适配器都设置了提示。
  • 如果我使用 MenuInflater 从 XML 膨胀菜单,我无法为菜单项设置 groupId。
  • 我可以验证 getUserVisibleHint() 方法不可靠。我有日志证明错误的片段被称为有时,但并非总是如此。我正在使用一个 ViewPager,同一个类的 3 个片段,viewPager.setOffscreenPageLimit(2),我的 FragmentPagerAdapter 扩展了 FragmentStatePagerAdapter。
【解决方案3】:

哦,谷歌,我是说 WTF?

问题是onContextItemSelected非常通用,每个fragment的每个菜单项都会调用它。

您可以使用MenuItem.OnMenuItemClickListener来强制片段菜单不使用allonContextItemSelected,而只使用该片段的相同功能。 p>

使用下一个实现:

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

【讨论】:

    【解决方案4】:

    对每个菜单项使用 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);
        }
    

    【讨论】:

    • 谢谢!在onCreateView() 中设置fragmentId = new Random().nextInt();
    【解决方案5】:

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

    似乎除非 Android 发生变化,否则任何解决方案都会有点老套。我创建了一个全局变量来存储onCreateView 中当前片段的 ID 引用。然后我将它传递给onCreateContextMenu 中的每个ContextMenu MenuItem。选择项目时,我验证了这两个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
        }
    }
    

    【讨论】:

    • 谢谢,但这个解决方案是@technik 的副本。还将myFragmentReference = 12345; 更改为myFragmentReference = new Random().nextInt();
    • 在我的用例中,我需要一个特定的 ID(但显然不是 12345)来对应显示的数据,因此我没有使用随机数,但我可以看到这是怎么回事有用。没有复制@technik 的答案,但我可以看到相似之处,因此感谢他们首先进入。
    • 啊,明白了。打扰一下。很高兴你自己得到了这个解决方案。如果你愿意,我会删除第一条消息。
    • 完全没有问题,在某些情况下会很有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多