更新
出了什么问题?
您将ViewPager 与FragmentPagerAdapter 结合使用。这种组合的一个重要实现细节是您有几个Fragments 附加到Activity。这是ViewPager 工作所必需的,因为它至少需要下一个和上一个Fragments 及其Views 才能准备好交换。
问题在于,当您仅使用标题发送MenuItems 时,您没有考虑ViewPager 的这种行为。这通常是一个可怕的想法,这只是为什么它是错误的示例。当显示上下文菜单并且用户选择某个项目时,首先MainActivity 有机会处理它,然后事件转到FragmentManager,后者将事件分派给all 附上Fragments(因为你想想也没有其他合理的选择)。因此,您的每个Fragments 都会收到onMenuItemSelected 调用并尝试处理它,因为MenuItem 标题是相同的。当然你有一个例外,因为对于其他Fragments 当前选定项目的“上下文”没有设置。
如何解决?
显然不要只使用标题来为您的MenuItems 发送事件。您实际上可以为每个Fragment 生成唯一的groupId 或itemId。但我更喜欢类似 OOP 的方式。首先摆脱所有原始的上下文菜单管理代码(menus、mpos、setMenu、addMenuItem、removeItemMenu、onMenuItemSelected 等)并用类似的代码替换它们
public class BaseCustomFragment extends Fragment implements OnItemClickListener, OnItemLongClickListener
{
private List<MenuItemAction> menus = new ArrayList<MenuItemAction>();
// base class for all actions for context-menu in BaseCustomFragment
abstract class MenuItemAction implements MenuItem.OnMenuItemClickListener
{
private final String title;
public MenuItemAction(String title)
{
this.title = title;
}
public String getTitle()
{
return title;
}
public final boolean isVisible()
{
return isVisibleImpl(conCon, locCon);
}
@Override
public final boolean onMenuItemClick(MenuItem item)
{
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
Log.d("MenuItem", "menu item '" + title + "' for #" + info.position + " of " + BaseCustomFragment.this.getClass().getSimpleName());
handleActionImpl(conCon, locCon);
return true;
}
protected final void startActivity(Uri uri)
{
startActivity(Intent.ACTION_VIEW, uri);
}
protected final void startActivity(String action, Uri uri)
{
startActivity(new Intent(action, uri));
}
protected final void startActivity(Class<? extends Activity> activityClass)
{
startActivity(new Intent(getActivity(), activityClass));
}
protected final void startActivity(Intent intent)
{
getActivity().startActivity(intent);
}
protected abstract boolean isVisibleImpl(Customer conCon, Localization locCon);
protected abstract void handleActionImpl(Customer conCon, Localization locCon);
}
protected void setContext(Customer c, Localization l)
{
Log.d("BaseFrag", "setContext class:" + String.valueOf(this.getClass()));
Log.d("BaseFrag", "setContext :" + String.valueOf(l));
conCon = c;
locCon = l;
Log.d("BaseFrag", "setContext2 :" + String.valueOf(locCon));
updateContextMenu();
}
protected void setMenus(MenuItemAction... menuItems)
{
this.menus = Arrays.asList(menuItems);
updateContextMenu();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo info)
{
super.onCreateContextMenu(menu, v, info);
for (MenuItemAction menuItemAction : menus)
{
if (menuItemAction.isVisible())
{
MenuItem menuItem = menu.add(menuItemAction.getTitle());
menuItem.setOnMenuItemClickListener(menuItemAction);
}
}
}
private void updateContextMenu()
{
if (lv == null)
return;
boolean hasVisibleItems = false;
for (MenuItemAction menuItemAction : menus)
{
if (menuItemAction.isVisible())
{
hasVisibleItems = true;
break;
}
}
if (hasVisibleItems)
{
Log.d(getClass().getSimpleName(), "Attaching context menu for " + getClass().getSimpleName());
this.registerForContextMenu(lv);
}
else
{
Log.d(getClass().getSimpleName(), "Detaching context menu for " + getClass().getSimpleName());
this.unregisterForContextMenu(lv);
}
}
这里MenuItemAction 是每个Fragment 独立持有的所有“内部”菜单项的基类。我称它们为“内部”,因为“真实”MenuItems 是实现细节,您不能从它们继承,因此我们将从“内部”的创建“真实”。此转换/创建在 onCreateContextMenu 内部完成。请注意,MenuItemAction 将已经提供“上下文”数据的事件分派到需要的地方。它还包含一些startActivity 帮助方法来简化代码。
现在我们可以向BaseCustomFragment 添加一堆帮助方法来创建您的典型菜单项:
protected MenuItemAction createNavigateToLocationMenuItem()
{
return new MenuItemAction("Navigate to location")
{
@Override
protected boolean isVisibleImpl(Customer conCon, Localization locCon)
{
return (locCon != null);
}
@Override
protected void handleActionImpl(Customer conCon, Localization locCon)
{
startActivity(Uri.parse("google.navigation:q=" + Uri.encode(locCon.noZipString())));
}
};
}
protected MenuItemAction createCallMenuItem()
{
return new MenuItemAction("Call")
{
@Override
protected boolean isVisibleImpl(Customer conCon, Localization locCon)
{
return (conCon != null) && (conCon.getPhoneNumbers().size() > 0);
}
@Override
protected void handleActionImpl(Customer conCon, Localization locCon)
{
startActivity(Intent.ACTION_DIAL, Uri.parse("tel:" + Uri.encode(BaseCustomFragment.this.conCon.getPhoneNumbers().get(0).toString())));
}
};
}
protected MenuItemAction createSmsMenuItem()
{
return new MenuItemAction("SMS")
{
@Override
protected boolean isVisibleImpl(Customer conCon, Localization locCon)
{
return (conCon != null) && (conCon.getPhoneNumbers().size() > 0);
}
@Override
protected void handleActionImpl(Customer conCon, Localization locCon)
{
startActivity(Uri.parse("sms:" + Uri.encode(BaseCustomFragment.this.conCon.getPhoneNumbers().get(0).toString())));
}
};
}
protected MenuItemAction createEmailMenuItem()
{
return new MenuItemAction("Email")
{
@Override
protected boolean isVisibleImpl(Customer conCon, Localization locCon)
{
return (conCon != null) && (conCon.getEmail().length() > 0);
}
@Override
protected void handleActionImpl(Customer conCon, Localization locCon)
{
startActivity(Uri.parse("mailto:" + Uri.encode(conCon.getEmail())));
}
};
}
protected MenuItemAction createNewOrderMenuItem()
{
return new MenuItemAction("New Order")
{
@Override
protected boolean isVisibleImpl(Customer conCon, Localization locCon)
{
return true;
}
@Override
protected void handleActionImpl(Customer conCon, Localization locCon)
{
startActivity(OrderActivity.class);
}
};
}
所以剩下的就是使用这个基础设施特定的片段,例如
public class CustomersFrag extends BaseCustomFragment
{
public CustomersFrag()
{
...
setMenus(createCallMenuItem(),
createSmsMenuItem(),
createEmailMenuItem(),
createNewOrderMenuItem(),
createNavigateToLocationMenuItem());
}
...
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{
Customer c = (Customer) parent.getItemAtPosition(position);
setContext(c, c.getLocalization());
return super.onItemLongClick(parent, view, position, id);
}
或
public class LocalizationsFrag extends BaseCustomFragment
{
public LocalizationsFrag()
{
...
setMenus(
createNavigateToLocationMenuItem(),
createNewOrderMenuItem());
}
...
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{
Localization o = (Localization) parent.getItemAtPosition(position);
setContext(null, o);
return super.onItemLongClick(parent, view, position, id);
}
请注意,您的代码中的菜单项标题不再有 if/else 或 switch。此外,您不必根据特定片段中的数据在每个onItemLongClick 中执行奇怪的addMenuItem/removeMenuItem,因为:
- 现在每个
MenuItemAction 都有自己的isVisibleImpl 和handleActionImpl 方法
-
setContext 致电updateContextMenu
All for you menu 操作似乎是通用的,并且适合您已有的简单“上下文”。因此,我创建了MenuItemAction 只是一个非静态内部类,可以使用BaseCustomFragment.this 获取它的父级Fragment。如果在某些时候这成为一个限制,并且您想创建一个特定于 BaseCustomFragment 的子子类的操作,并且您希望它使用来自该片段的数据,您只需在相应的子类中创建菜单项。想象一下,您想添加一个“复制订单”菜单项。你可以这样做:
public class OrdersFragment extends BaseCustomFragment
{
...
private Order currentOrder;
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{
Order o = (Order) parent.getItemAtPosition(position);
currentOrder = o;
setContext(o.getCustomer(), o.getLocalization());
return super.onItemLongClick(parent, view, position, id);
}
protected MenuItemAction createNewOrderMenuItem()
{
return new MenuItemAction("Copy Order")
{
@Override
protected boolean isVisibleImpl(Customer conCon, Localization locCon)
{
return (OrdersFragment.this.currentOrder != null);
}
@Override
protected void handleActionImpl(Customer conCon, Localization locCon)
{
Intent intent = new Intent(getActivity(), OrderActivity.class);
intent.putExtra("copySrcId", OrdersFragment.this.currentOrder.getId());
startActivity(intent);
}
};
}
现在您的“复制订单”MenuItemAction 可以访问在OrdersFragment 中定义的currentOrder。
旁注
查看您的代码,我发现还有一些我认为值得一提的事情:
- 对电话号码使用整数类型是个坏主意。电话号码可能包含其他符号,例如“+”或“*”或“#”,它们不适合基于整数的类型。电话号码可能以“0”开头,您将无法转换为基于整数的类型。
- 我认为你应该更好地学习
java.util包。例如,java.util.List 及其子类中有一个 contains 方法。
- 当您可以使用超类/接口时,为什么要在声明中使用非常具体的类型尚不清楚。例如,您的所有模型类似乎都使用
java.sql.Timestamp 作为日期字段,没有明显的原因(您真的需要纳秒吗?)。或者您在任何地方都使用ArrayList,尽管有时只需List 就足够了
- 在
prepareTabs 中的MainActivity 中,添加标签后不要调用adapter.notifyDataSetChanged();。实际上,这会导致更新的支持库崩溃。
希望对你有帮助
旧答案(很可能不相关)
将this 添加到每个Log 呼叫。我怀疑答案是在
B 长按:数据
一个 setContext :data
一个 setContext2 :data
A itemSelected :null
不知何故,最后一个 Log 是针对前 3 个不同的对象完成的。同样,问题不是每次都可重现,这是真的吗?如果是这样,它可能与设备旋转或在事件处理过程中触发 Activity 重新创建的其他因素有关。因此,您可以将日志添加到您的 onPause 和 onResume 并查看是否是这种情况。