From 6e74334c90c88f60437686c72b82b64cb770f6b6 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Fri, 5 Feb 2021 16:50:12 -0600 Subject: [PATCH 1/3] Correctly Disable View Recycling Android Flyout --- .../Renderers/ShellFlyoutRecyclerAdapter.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs index 92eb720459a..158b0837f2d 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs @@ -16,7 +16,6 @@ public class ShellFlyoutRecyclerAdapter : RecyclerView.Adapter readonly IShellContext _shellContext; List _listItems; List> _flyoutGroupings; - Dictionary _templateMap = new Dictionary(); Action _selectedCallback; bool _disposed; ElementViewHolder _elementViewHolder; @@ -42,6 +41,11 @@ public ShellFlyoutRecyclerAdapter(IShellContext shellContext, Action se protected virtual DataTemplate DefaultMenuItemTemplate => null; public override int GetItemViewType(int position) + { + return position; + } + + DataTemplate GetDataTemplate(int position) { var item = _listItems[position]; DataTemplate dataTemplate = ShellController.GetFlyoutItemDataTemplate(item.Element); @@ -57,11 +61,8 @@ public override int GetItemViewType(int position) } var template = dataTemplate.SelectDataTemplate(item.Element, Shell); - var id = ((IDataTemplateController)template).Id; - - _templateMap[id] = template; - return id; + return template; } public override void OnViewRecycled(Java.Lang.Object holder) @@ -154,7 +155,7 @@ public override AView FocusSearch([GeneratedEnum] FocusSearchDirection direction public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) { - var template = _templateMap[viewType]; + var template = GetDataTemplate(viewType); var content = (View)template.CreateContent(); From ad0f252916bd1195d28540489a4983e0a44162df Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Fri, 5 Feb 2021 18:40:19 -0600 Subject: [PATCH 2/3] - fix android to not reset element when items are removed --- .../Renderers/ShellFlyoutRecyclerAdapter.cs | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs index 158b0837f2d..0bff0d6eb35 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs @@ -18,10 +18,10 @@ public class ShellFlyoutRecyclerAdapter : RecyclerView.Adapter List> _flyoutGroupings; Action _selectedCallback; bool _disposed; - ElementViewHolder _elementViewHolder; public ShellFlyoutRecyclerAdapter(IShellContext shellContext, Action selectedCallback) { + HasStableIds = true; _shellContext = shellContext; ShellController.FlyoutItemsChanged += OnFlyoutItemsChanged; @@ -42,12 +42,22 @@ public ShellFlyoutRecyclerAdapter(IShellContext shellContext, Action se public override int GetItemViewType(int position) { - return position; + return _listItems[position].Index; } - DataTemplate GetDataTemplate(int position) + DataTemplate GetDataTemplate(int viewTypeId) { - var item = _listItems[position]; + AdapterListItem item = null; + + foreach(var ali in _listItems) + { + if(viewTypeId == ali.Index) + { + item = ali; + break; + } + } + DataTemplate dataTemplate = ShellController.GetFlyoutItemDataTemplate(item.Element); if (item.Element is IMenuItemController) { @@ -61,7 +71,6 @@ DataTemplate GetDataTemplate(int position) } var template = dataTemplate.SelectDataTemplate(item.Element, Shell); - return template; } @@ -69,7 +78,19 @@ public override void OnViewRecycled(Java.Lang.Object holder) { if (holder is ElementViewHolder evh) { - evh.Element = null; + // only clear out the Element if the item has been removed + bool found = false; + foreach(var item in _listItems) + { + if(item.Element == evh.Element) + { + found = true; + break; + } + } + + if(!found) + evh.Element = null; } base.OnViewRecycled(holder); @@ -176,14 +197,13 @@ public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int container.LayoutParameters = new LP(LP.MatchParent, LP.WrapContent); linearLayout.AddView(container); - _elementViewHolder = new ElementViewHolder(content, linearLayout, bar, _selectedCallback, _shellContext.Shell); - - return _elementViewHolder; + return new ElementViewHolder(content, linearLayout, bar, _selectedCallback, _shellContext.Shell); } protected virtual List GenerateItemList() { var result = new List(); + _listItems = _listItems ?? result; List> grouping = ((IShellController)_shellContext.Shell).GenerateFlyoutGrouping(); @@ -199,7 +219,18 @@ protected virtual List GenerateItemList() bool first = !skip; foreach (var element in sublist) { - result.Add(new AdapterListItem(element, first)); + AdapterListItem toAdd = null; + foreach (var existingItem in _listItems) + { + if(existingItem.Element == element) + { + existingItem.DrawTopLine = first; + toAdd = existingItem; + } + } + + toAdd = toAdd ?? new AdapterListItem(element, first); + result.Add(toAdd); first = false; } skip = false; @@ -230,11 +261,8 @@ protected override void Dispose(bool disposing) { ((IShellController)Shell).FlyoutItemsChanged -= OnFlyoutItemsChanged; - _elementViewHolder?.Dispose(); - _listItems = null; _selectedCallback = null; - _elementViewHolder = null; } base.Dispose(disposing); @@ -242,12 +270,18 @@ protected override void Dispose(bool disposing) public class AdapterListItem { + // This ensures that we have a stable id for each element + // if the elements change position + static int IndexCounter = 0; + public AdapterListItem(Element element, bool drawTopLine = false) { DrawTopLine = drawTopLine; Element = element; + Index = IndexCounter++; } + public int Index { get; } public bool DrawTopLine { get; set; } public Element Element { get; set; } } From ddf37bc6d941c628822f481188721a58c9448cf1 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Fri, 5 Feb 2021 19:18:21 -0600 Subject: [PATCH 3/3] - fix iOS to not always recreate cells --- .../Renderers/ShellTableViewController.cs | 2 +- .../Renderers/ShellTableViewSource.cs | 39 ++++++++++++++++++- .../Renderers/UIContainerCell.cs | 23 +++++++---- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewController.cs b/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewController.cs index 4208155548d..37430401dbd 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewController.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewController.cs @@ -62,7 +62,7 @@ protected ShellTableViewSource CreateShellTableViewSource() void OnFlyoutItemsChanged(object sender, EventArgs e) { - _source.ClearCache(); + _source.ReSyncCache(); TableView.ReloadData(); ShellFlyoutContentManager.UpdateVerticalScrollMode(); } diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs b/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs index fd22db2594c..420485d4546 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs @@ -47,6 +47,41 @@ public List> Groups protected virtual DataTemplate DefaultMenuItemTemplate => null; + internal void ReSyncCache() + { + var newGroups = ((IShellController)_context.Shell).GenerateFlyoutGrouping(); + + if (newGroups == _groups) + return; + + _groups = newGroups; + if (_cells == null) + { + _cells = new Dictionary(); + return; + } + + var oldList = _cells; + _cells = new Dictionary(); + + foreach (var group in newGroups) + { + foreach(var element in group) + { + UIContainerCell result; + if(oldList.TryGetValue(element, out result)) + { + _cells.Add(element, result); + oldList.Remove(element); + } + } + } + + foreach (var cell in oldList.Values) + cell.Disconnect(_context.Shell); + } + + public void ClearCache() { var newGroups = ((IShellController)_context.Shell).GenerateFlyoutGrouping(); @@ -125,7 +160,7 @@ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath index else { var view = _cells[context].View; - cell.Disconnect(); + cell.Disconnect(keepRenderer: true); cell = new UIContainerCell(cellId, view, _context.Shell, context); } @@ -148,8 +183,10 @@ public override nfloat GetHeightForFooter(UITableView tableView, nint section) { if (section < Groups.Count - 1) return 1; + return 0; } + public override UIView GetViewForFooter(UITableView tableView, nint section) { return new SeparatorView(); diff --git a/Xamarin.Forms.Platform.iOS/Renderers/UIContainerCell.cs b/Xamarin.Forms.Platform.iOS/Renderers/UIContainerCell.cs index 2d54f0c75e5..b96fe937c41 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/UIContainerCell.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/UIContainerCell.cs @@ -18,9 +18,14 @@ internal UIContainerCell(string cellId, View view, Shell shell, object context) View = view; View.MeasureInvalidated += MeasureInvalidated; SelectionStyle = UITableViewCellSelectionStyle.None; - - _renderer = Platform.CreateRenderer(view); - Platform.SetRenderer(view, _renderer); + + _renderer = Platform.GetRenderer(view); + + if (_renderer == null) + { + _renderer = Platform.CreateRenderer(view); + Platform.SetRenderer(view, _renderer); + } ContentView.AddSubview(_renderer.NativeView); _renderer.NativeView.ClipsToBounds = true; @@ -49,7 +54,7 @@ internal void ReloadRow() TableView.ReloadRows(new[] { IndexPath }, UITableViewRowAnimation.Automatic); } - internal void Disconnect(Shell shell = null) + internal void Disconnect(Shell shell = null, bool keepRenderer = false) { ViewMeasureInvalidated = null; View.MeasureInvalidated -= MeasureInvalidated; @@ -57,7 +62,10 @@ internal void Disconnect(Shell shell = null) baseShell.PropertyChanged -= OnElementPropertyChanged; _bindingContext = null; - Platform.SetRenderer(View, null); + + if (!keepRenderer) + Platform.SetRenderer(View, null); + if (shell != null) shell.RemoveLogicalChild(shell); @@ -70,7 +78,8 @@ internal void Disconnect(Shell shell = null) public object BindingContext { get => _bindingContext; - set { + set + { if (value == _bindingContext) return; @@ -91,7 +100,7 @@ public object BindingContext public override void LayoutSubviews() { base.LayoutSubviews(); - if(View != null) + if (View != null) View.Layout(Bounds.ToRectangle()); }