You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的视图无法正常更新。

修复步骤:

  1. 在CraftAdapter中保存事件委托引用,并添加UnbindEvents方法用于移除事件监听
  2. 在CraftFragment的OnDestroyView方法中调用CraftAdapter.UnbindEvents(),确保Fragment销毁时解绑事件,避免内存泄漏和无效回调
  3. 补充Toast的Show()方法,让错误提示正常弹出

完成以上修改后,每次Fragment销毁时都会解绑对应的事件监听,确保只有当前活跃的Fragment实例响应合成事件,视图就能正常更新了。

内容的提问来源于stack exchange,提问作者ODINN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 00:14:54