Android Xamarin C#切换Fragment后合成视图无法更新求助
物品合成Fragment切换后视图不更新问题
我有一个用于处理物品合成的Fragment,它显示合成台、物品成本列表和背包。Fragment首次初始化时合成功能正常,但来回切换该Fragment后,点击合成按钮时视图不再更新。尝试用Handler.Post包装部分函数,但没有效果。
CraftFragment代码
using Android.Graphics; using Android.OS; using Android.Views; using Android.Widget; using AndroidX.Fragment.App; using MineClick.Crafting.Bench; using MineClick.Crafting.Bench.CostList; using MineClick.EventSystem; using MineClick.EventSystem.EventArguments; using MineClick.Materials; using MineClick.Navigation; using MineClick.Pickaxes; using MineClick.Player; using MineClick.Utility; using static MineClick.EventSystem.EventManager; using static MineClick.InventoryView; namespace MineClick.Crafting { internal class CraftFragment : Fragment { CraftAdapter CraftAdapter { get; set; } public override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); } public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.Inflate(Resource.Layout.crafting_activity, container, false); LinearLayout wrraper = view.FindViewById<LinearLayout>(Resource.Id.crafting_wrraper); LinearLayout inventoryWrraper = view.FindViewById<LinearLayout>(Resource.Id.inventory_wrraper); LinearLayout costListWrraper = view.FindViewById<LinearLayout>(Resource.Id.cost_list_wrraper); CraftingBench bench = new CraftingBench(Context); wrraper.AddView(bench); InventoryView inventoryDisplay = new InventoryView(Context, PlayerApplication.Instance.Player.PlayerInventory); inventoryWrraper.AddView(inventoryDisplay); CostListView costList = new CostListView(Context); costListWrraper.AddView(costList); CraftAdapter = new CraftAdapter(inventoryDisplay, bench,costList); return view; } // 新增销毁时解绑事件的方法 public override void OnDestroyView() { base.OnDestroyView(); CraftAdapter?.UnbindEvents(); } } }
CraftAdapter代码
using Android.Widget; using MineClick.Crafting.Bench.CostList; using MineClick.Crafting.Recipes.Handlers; using MineClick.EventSystem; using MineClick.Materials; using MineClick.Player; using static MineClick.EventSystem.EventManager; using static MineClick.InventoryView; namespace MineClick.Crafting { internal class CraftAdapter { InventoryView InvView; RecipeAdapter BenchAdapter; private EventHandler<EA_ITEM_CRAFTED> _craftHandler; // 保存事件委托引用 public CraftAdapter(InventoryView invView, CraftingBench bench, CostListView costList) { InvView = invView; BenchAdapter = new RecipeAdapter(bench,costList); Initialize(); } private void Initialize() { InvView.OnClickActions = OnInvClick; _craftHandler = (sender, args) => OnCraft(args.CraftedItem); EventManager.BindListener(Events.ITEM_CRAFTED, _craftHandler); } public void OnInvClick(InventoryClickEvent e) { if (e.ClickedItem.DisplayedItem == null) return; Item itemToAdd = ItemRegistry.GetItemById(e.ClickedItem.DisplayedItem.GetItem().Id); bool add = BenchAdapter.OnAttemptAddItem(itemToAdd.Clone()); // 补充Show()让Toast正常显示 if(!add) Toast.MakeText(e.ClickedItem.Context, "Cannot Add Item To Bench",ToastLength.Long).Show(); } public void OnCraft(Item item) { PlayerApplication.Instance.Player.PlayerInventory.Add(item); BenchAdapter.Reset(); InvView.Update(); } // 新增解绑事件的方法 public void UnbindEvents() { EventManager.UnbindListener(Events.ITEM_CRAFTED, _craftHandler); } } }
RecipeAdapter代码
using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; using MineClick.Crafting.Bench; using MineClick.Crafting.Bench.CostList; using MineClick.EventSystem; using MineClick.EventSystem.EventArguments; using MineClick.Materials; using MineClick.Player; using System; using System.Collections.Generic; using System.Linq; using System.Text; using static MineClick.EventSystem.EventManager; namespace MineClick.Crafting.Recipes.Handlers { internal class RecipeAdapter { public CraftingBench Bench { get; set; } public RecipeManager Manager { get; set; } public CostListView CostList; public RecipeAdapter(CraftingBench bench, CostListView costList,Item defaultMainItem,Item[] defaultExtras) { Manager = new RecipeManager(defaultMainItem,defaultExtras); Bench = bench; CostList = costList; CostList.CButton.Click += (sender, args) => Craft(); Update(); } public RecipeAdapter(CraftingBench bench, CostListView costList) { Manager = new RecipeManager(); Bench = bench; CostList = costList; CostList.CButton.Click += (sender, args) => Craft(); Update(); } public bool OnAttemptAddItem(Item item) { if (Manager.AttemptAddItem(item)) Update(); else return false; return true; } public void Update() { Bench.UpdateBench(Manager.MainItem, Manager.Extras, Manager.GetResultDisplay(Bench.Context)); UpdateCostList(); } private void UpdateCostList() { Dictionary<Item, bool> costList = new Dictionary<Item, bool>(); Inventory inventory = PlayerApplication.Instance.Player.PlayerInventory; if(Manager.CalculateCost() != null) Manager.CalculateCost().ForEach(item => { bool valid = true; if (!inventory.Contains(item.Id)) valid = false; else if (item is CountableItem) valid = ((CountableItem)item).Amount <= ((CountableItem)inventory.Get(item.Id)).Amount; costList.Add(item, valid); }); CostList.Recycler.Adapter.SetItems(costList); CostList.CButton.Valid = costList.All(entry => entry.Value); CostList.CButton.Visibility = costList.Count == 0 ? ViewStates.Invisible : ViewStates.Visible; } private void Craft() { Item crafted = Manager.CalculateResult(); if (CostList.CButton.Valid) EventManager.InvokeEvent(Events.ITEM_CRAFTED, new EA_ITEM_CRAFTED(crafted)); } public void Reset() { Manager.Reset(); Update(); } } }
RecipeManager代码
using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; using MineClick.Materials; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MineClick.Crafting.Recipes.Handlers { internal class RecipeManager { public static readonly int EXTRAS_AMOUNT = 6; public Item MainItem { get; private set; } public Item[] Extras { get; private set; } IRecipeAcceptor RecipeAcceptor; public RecipeManager(Item mainItem, Item[] extras) { MainItem = mainItem; Extras = extras; } public RecipeManager() : this(null,new Item[EXTRAS_AMOUNT]) { } public void SetRecipeItem(Item item) { if (item != null && RecipeRegistry.GetRecipeBindings().ContainsKey(item.Id)) { MainItem = item.Clone(); RecipeAcceptor = RecipeRegistry.GetRecipeBindings()[MainItem.Id]; } else { MainItem = null; RecipeAcceptor = null; } Array.Clear(Extras, 0, Extras.Length); } public bool AttemptAddItem(Item item) { if (item == null) return false; bool success; if (MainItem == null) success = AttemptAddItemToMainSlot(item); else if (GetFirstEmptyExtraSlot() != -1) success = AttemptAddItemToExtra(item); else success = false; return success; } private bool AttemptAddItemToMainSlot(Item item) { if (!RecipeRegistry.GetRecipeBindings().ContainsKey(item.Id)) return false; SetRecipeItem(item); return true; } private bool AttemptAddItemToExtra(Item item) { if (GetFirstEmptyExtraSlot() == -1) return false; if (!RecipeAcceptor.IsExtraFit(MainItem, item, GetPlainExtras())) return false; if (GetPlainExtras().Any(x => x.Id == item.Id)) return false; Extras[GetFirstEmptyExtraSlot()] = item.Clone(); return true; } public int GetFirstEmptyExtraSlot() { return Array.IndexOf(Extras, null); } public View GetResultDisplay(Context context) { if (RecipeAcceptor == null) return null; return RecipeAcceptor.DisplayResult(MainItem, GetPlainExtras(), context); } public Item CalculateResult() { if (RecipeAcceptor == null) return null; return RecipeAcceptor.CalculateResult(MainItem, GetPlainExtras()); } public List<Item> CalculateCost() { if (RecipeAcceptor == null) return null; return RecipeAcceptor.CalculateCost(MainItem, GetPlainExtras()); } /// <summary> /// Get extras not as slots array but as a plain list of extras (no null) /// </summary> /// <returns></returns> public Item[] GetPlainExtras() { return Extras.Where(x => x != null).ToArray(); } public void Reset() { SetRecipeItem(null); } } }
解决方案
问题根源在于事件重复绑定且未解绑:每次创建CraftFragment时,都会实例化CraftAdapter,而CraftAdapter的Initialize方法会绑定ITEM_CRAFTED事件。当Fragment被切换销毁后,旧的CraftAdapter实例并未被回收(因为事件监听持有它的引用),导致后续触发ITEM_CRAFTED事件时,所有旧实例的OnCraft方法都会被调用,而这些旧实例对应的视图已经被销毁,最终导致新Fragment的视图无法正常更新。
修复步骤:
- 在
CraftAdapter中保存事件委托引用,并添加UnbindEvents方法用于移除事件监听 - 在
CraftFragment的OnDestroyView方法中调用CraftAdapter.UnbindEvents(),确保Fragment销毁时解绑事件,避免内存泄漏和无效回调 - 补充Toast的
Show()方法,让错误提示正常弹出
完成以上修改后,每次Fragment销毁时都会解绑对应的事件监听,确保只有当前活跃的Fragment实例响应合成事件,视图就能正常更新了。
内容的提问来源于stack exchange,提问作者ODINN
相关产品推荐
相关产品推荐

