总之,情况现在看起来像这样。
我点击B
(android片段)的实例,并期望调用该实例的onContextItemSelected
。确实调用了onContextItemSelected
,但事实证明这是类C
实例的方法。
我被要求展示这个项目。在大约2年后查看此代码之后,我决定首先对它进行更多的澄清,因为上面评论的事件可能不是5%。我认为今天我不能做更多的事情购买我已经对整个项目进行了一些概述,所以你知道在哪里看。它没有完全完成,可能包含一些错误,但更多的是它的样子:
我将尝试明确如何重现它的明确步骤。但是如果你想看它,我还是要包含已经here
的文件 A
是BaseCustomFragment
B
和C
是扩展它的类。
我有这样的事情:
public class A extends Fragment implements OnItemClickListener, OnItemLongClickListener{
protected D loc;
protected void setContext(D l){
Log.d("A", "setContext :" + String.valueOf(l));
loc = l;
Log.d("A", "setContext2 :" + String.valueOf(loc));
}
public boolean onContextItemSelected(MenuItem item) {
Log.d("A", "itemSelected :" + String.valueOf(loc));
}
}
和
public class B extends A{
public boolean onItemLongClick(AdapterView<?> pr, View view,int p, long id) {
D d = (D) pr.getItemAtPosition(p);
Log.d("B", "longClick :" + String.valueOf(d));
setContext(d);
return false;
}
}
并且日志看起来像这样:
B longClick:数据
setContext:data
setContext2:data
itemSelected:null
我在其他任何地方都没有触及loc
或setContex
。我完全不知道发生了什么。怎么可能?
我将A
类设置为ListView
侦听器。我在ViewPager
中使用此片段。在onItemSelected
之后立即调用setContext
。
不知道该怎么说更多。
因为挥发性没有修复但是......
甚至更奇怪的东西 - 在班级A
我得到了:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
lv = (ListView) inflater.inflate(layoutResourceId, container, false);
lv.setOnItemClickListener(this);
lv.setOnItemLongClickListener(this);
lv.setAdapter(adapter);
//this.registerForContextMenu(lv);
return lv;
}
我现在也加入了课程B
:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
this.registerForContextMenu(lv);
return lv;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
//Never called!
Log.d("Loc", "selected :" + name);
return super.onContextItemSelected(item);
}
魔法发生的地方是我还有C
类,它与B
基本相同,只是我没有覆盖上面那些额外的方法:
public class C extends A {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
...
}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,int position, long id) {
...
}
}
日志显示:
B longClick:数据 - 来自
的实例B
setContext:data - 来自
的实例B
setContext2:data - 来自
的实例B
itemSelected:null - 来自
的实例C
!!!
怎么样?我只在课堂上发送registerForContext
B
有什么想法吗?
答案 0 :(得分:1)
如果正在从多个线程访问您的A
对象(看起来可能是您的事件监听器),您可以看到这样的问题(它的一般术语是"stale data")。要快速检查这是否是线程问题,请将您的成员变量声明为protected volatile D loc;
,然后查看问题是否消失(请参阅this resource about volatile)。如果这确实解决了问题,那么您将需要实施更高级的线程保护,以确保您不会遇到任何更恶毒/细微的线程错误。
答案 1 :(得分:1)
更新
出了什么问题?
您将ViewPager
与FragmentPagerAdapter
结合使用。这种组合的一个重要实现是你有几个Fragment
附加到Activity
。这需要ViewPager
才能正常工作,因为它至少需要下一个Fragment
和View
的{{1}} s才能进行交换。
当您仅使用标题发送ViewPager
时,您不会将MenuItem
的这种行为考虑在内,这是错误的。这通常是一个可怕的想法,这仅仅是为什么它是错误的例子。当显示上下文菜单并且用户选择某个项目时,首先MainActivity
有机会处理它,然后事件转到FragmentManager
,该事件将事件发送到 所有 附加Fragment
s(因为如果你考虑的话,没有其他合理的选择)。因此,Fragment
每个onMenuItemSelected
都会收到MenuItem
来电并试图处理它,因为Fragment
标题是相同的。当然,你有一个例外,因为对于其他MenuItem
s&#34; context&#34;当前所选项目未设置。
如何解决?
显然,不要仅使用标题为您的groupId
发送活动。实际上,您可以为每个itemId
生成唯一的Fragment
或menus
。但我更喜欢类似OOP的方式。首先删除所有原始上下文菜单管理代码(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
是所有&#34;内部&#34;的基类。每个MenuItem
独立持有的菜单项。我打电话给他们&#34;内部&#34;因为&#34;真实&#34; onCreateContextMenu
是实施细节,您无法从中继承,因此我们会创建&#34;真实&#34;来自我们&#34;内部&#34;那些。此转换/创建在MenuItemAction
内完成。请注意,startActivity
会调度已提供您的&#34;上下文&#34;数据到需要的地方。它还包含一些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
或addMenuItem
。此外,根据特定片段中的数据,您不必在每个removeMenuItem
中执行奇怪的onItemLongClick
/ MenuItemAction
,因为:
isVisibleImpl
现在都拥有自己的handleActionImpl
和setContext
方法updateContextMenu
来电MenuItemAction
所有菜单操作似乎都是通用的,适合简单的&#34; context&#34;你已经有了。因此,我使Fragment
只是一个非静态内部类,可以使用BaseCustomFragment.this
获取它的父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);
}
};
}
子子类的动作,并且你希望它使用该片段中的数据,你只需要在相应的子代中创建菜单项类。想象一下,你想要添加一个&#34;复制订单&#34;菜单项。你做这样的事情:
MenuItemAction
现在你的&#34;复制订单&#34; currentOrder
可以访问OrdersFragment
内定义的java.util
。
附注
看看你的代码,我发现了一些我认为值得一提的事情:
contains
包。例如,java.util.List
及其子类中有java.sql.Timestamp
方法。ArrayList
作为日期字段(你真的需要纳秒吗?)。或者您在任何地方都使用List
,但有时仅MainActivity
就足够了prepareTabs
adapter.notifyDataSetChanged();
this
中,在您添加标签后,请不要致电Log
。实际上,这会导致新的支持库崩溃。希望这有帮助
旧答案(最可能无关紧要)
为每个Log
电话添加onPause
。我怀疑答案是在
B longClick:数据
setContext:data
setContext2:data
itemSelected:null
不知何故,最后一个onResume
是针对第一个不同的对象完成的3.同样,问题每次都不可重现吗?如果是这样,它可能与设备轮换有关,或者在事件处理过程中触发活动重新创建。因此,您可以将日志添加到{{1}}和{{1}},然后查看是否属实。