Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CollectionView - Recycle DataTemplates when using template selector #12011

Merged
merged 24 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
22443a6
[Android] Use different view types per item template
Redth Dec 10, 2022
2e0bfba
[iOS] Distinct Cell reuse id's for different DataTemplates
Redth Dec 10, 2022
4c411fe
Merge branch 'main' into pr/12011
Redth Jan 26, 2023
21ec9a8
Switch up some xaml controls in the sample
Redth Jan 26, 2023
3401e73
Expose Id internally to use for android view types
Redth Jan 27, 2023
c3b3643
Cache datatemplates for unique item view types
Redth Jan 27, 2023
44a9196
Api changes (overrides)
Redth Jan 27, 2023
5874aa0
Improve sample
Redth Jan 27, 2023
846db59
Remove api txt change that shouldn't have been added
Redth Jan 27, 2023
cfe3cc1
Merge branch 'main' into cv-recycle-templateselector-templates
rmarinho Feb 10, 2023
d518d52
Update src/Controls/src/Core/Handlers/Items/Android/Adapters/ItemsVie…
Redth Feb 14, 2023
6b7efed
Update src/Controls/src/Core/Handlers/Items/Android/Adapters/ItemsVie…
Redth Feb 14, 2023
354f909
Update src/Controls/src/Core/Handlers/Items/Android/Adapters/ItemsVie…
Redth Feb 14, 2023
7447e65
Merge branch 'main' into pr/12011
Redth Feb 14, 2023
2e24c68
Use DataTemplate Id for cellReuseId
Redth Feb 14, 2023
81f9e10
Merge branch 'main' into pr/12011
Redth Feb 15, 2023
847ae79
Merge branch 'main' into cv-recycle-templateselector-templates
rmarinho Feb 16, 2023
42b34e6
Merge branch 'main' into pr/12011
Redth Feb 16, 2023
f3e27f7
Merge branch 'main' into cv-recycle-templateselector-templates
rmarinho Feb 28, 2023
9448f74
Merge branch 'main' into cv-recycle-templateselector-templates
rmarinho Mar 20, 2023
9038032
Merge branch 'main' into pr/12011
Redth Mar 23, 2023
63cdae2
Merge branch 'main' into cv-recycle-templateselector-templates
PureWeen May 10, 2023
8fc33f7
Merge branch 'main' into pr/12011
Redth May 12, 2023
c67d36f
Calculate correct position for carousel adapter
Redth May 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,35 @@
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="MilkTemplate">
<Frame BorderColor="Red" BackgroundColor="Wheat" HeightRequest="100">
<StackLayout HeightRequest="100">
<Border Stroke="Red" Background="Wheat" HeightRequest="100">
<Grid HeightRequest="100">
<Label Text="{Binding Name}" />
</StackLayout>
</Frame>
</Grid>
</Border>
</DataTemplate>

<DataTemplate x:Key="CoffeeTemplate">
<Frame BorderColor="Red" BackgroundColor="SaddleBrown" HeightRequest="50">
<StackLayout HeightRequest="50">
<Border Stroke="Red" Background="SaddleBrown" HeightRequest="50">
<Grid HeightRequest="50">
<Label Text="{Binding Name}" />
</StackLayout>
</Frame>
</Grid>
</Border>
</DataTemplate>

<DataTemplate x:Key="LatteTemplate" x:DataType="datatemplateselectorgalleries:Latte">
<Frame BorderColor="Red" BackgroundColor="BurlyWood" >
<StackLayout>
<Border Stroke="Red" Background="BurlyWood" >
<VerticalStackLayout>
<Label Text="{Binding Name, StringFormat='Drink name is: {0}'}"/>
<Label Text=" The ingredients are: " Margin="0,10,0,0"/>
<StackLayout BindableLayout.ItemsSource="{Binding Ingredients}" >
<VerticalStackLayout BindableLayout.ItemsSource="{Binding Ingredients}" >
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="datatemplateselectorgalleries:DrinkBase">
<Label Text="{Binding Name, StringFormat=' {0}'}" />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</Frame>
</VerticalStackLayout>
</VerticalStackLayout>
</Border>
</DataTemplate>

<datatemplateselectorgalleries:DrinkTemplateSelector x:Key="VehicleTemplateSelector"
Expand All @@ -45,11 +45,7 @@
</ContentPage.Resources>

<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid RowDefinitions="*,Auto">

<CollectionView ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource VehicleTemplateSelector}"
Expand All @@ -76,7 +72,6 @@

<Picker x:Name="TemplatePicker"
Title="Select a template"
TitleColor="Red"
SelectedItem="{Binding SelectedTemplate}">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,28 @@
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="DefaultTemplate">
<Grid HeightRequest="50">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>

<Grid HeightRequest="50" RowDefinitions="Auto,Auto">

<Image Source="coffee.png" AutomationId="weekday"/>

<Label Grid.Row="1" Text="{Binding Date, StringFormat='{}{0:dddd}'}"></Label>
</Grid>
</DataTemplate>
<DataTemplate x:Key="WeekendTemplate">
<Grid HeightRequest="50">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>

<Grid HeightRequest="50" RowDefinitions="Auto,Auto">

<Image Source="oasis.jpg" AutomationId="weekend"/>

<Label Grid.Row="1" Text="It's the weekend! Woot!"></Label>
</Grid>
</DataTemplate>

<DataTemplate x:Key="EmptyTemplate">
<StackLayout>
<Label Text="{Binding ., StringFormat='({0}) does not match any day of the week.'}"></Label>
</StackLayout>
<Label Text="{Binding ., StringFormat='({0}) does not match any day of the week.'}"></Label>
</DataTemplate>

<DataTemplate x:Key="SymbolsTemplate">
<StackLayout BackgroundColor="Red">
<Label Text="{Binding ., StringFormat='({0}) _definitely_ does not match any day of the week.'}"></Label>
</StackLayout>
<Label Background="Red" Text="{Binding ., StringFormat='({0}) _definitely_ does not match any day of the week.'}"></Label>
</DataTemplate>

<local:WeekendSelector x:Key="WeekendSelector"
Expand All @@ -56,13 +44,13 @@

<ContentPage.Content>

<StackLayout>
<Grid RowDefinitions="Auto,*">

<SearchBar x:Name="SearchBar" Placeholder="Day of Week Filter" />
<SearchBar x:Name="SearchBar" Placeholder="Day of Week Filter" Grid.Row="0" />

<CollectionView x:Name="CollectionView" ItemTemplate="{StaticResource WeekendSelector}"
<CollectionView Grid.Row="1" x:Name="CollectionView" ItemTemplate="{StaticResource WeekendSelector}"
EmptyViewTemplate="{StaticResource SearchTermSelector}"/>

</StackLayout>
</Grid>
</ContentPage.Content>
</ContentPage>
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public DataTemplateSelectorGallery()
{
InitializeComponent();

_demoFilteredItemSource = new DemoFilteredItemSource(filter: ItemMatches);
_demoFilteredItemSource = new DemoFilteredItemSource(count: 200, filter: ItemMatches);

CollectionView.ItemsSource = _demoFilteredItemSource.Items;

Expand Down
2 changes: 2 additions & 0 deletions src/Controls/src/Core/DataTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public DataTemplate(Func<object> loadTemplate) : base(loadTemplate)

string IDataTemplateController.IdString => _idString;

internal int Id => _id;

int IDataTemplateController.Id => _id;

/// <include file="../../docs/Microsoft.Maui.Controls/DataTemplate.xml" path="//Member[@MemberName='SetBinding']/Docs/*" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,22 @@ internal CarouselViewAdapter(CarouselView itemsView, Func<View, Context, ItemCon
public override int ItemCount => CarouselView.Loop && !(ItemsSource is EmptySource)
&& ItemsSource.Count > 0 ? CarouselViewLoopManager.LoopScale : ItemsSource.Count;

public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
public override int GetItemViewType(int position)
{
if (CarouselView == null || ItemsSource == null)
return;
int positionInList = GetPositionInList(position);

bool hasItems = ItemsSource != null && ItemsSource.Count > 0;
if (positionInList < 0)
return ItemViewType.TextItem;

if (!hasItems)
return;
return base.GetItemViewType(positionInList);
}

public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
int positionInList = GetPositionInList(position);

int positionInList = (CarouselView.Loop && hasItems) ? position % ItemsSource.Count : position;
if (positionInList < 0)
return;

switch (holder)
{
Expand All @@ -40,5 +45,18 @@ public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int positi
break;
}
}

int GetPositionInList(int position)
{
if (CarouselView == null || ItemsSource == null)
return -1;

bool hasItems = ItemsSource != null && ItemsSource.Count > 0;

if (!hasItems)
return -1;

return (CarouselView.Loop && hasItems) ? position % ItemsSource.Count : position;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class ItemsViewAdapter<TItemsView, TItemsViewSource> : RecyclerView.Adapt

bool _disposed;
bool _usingItemTemplate = false;
DataTemplateSelector _itemTemplateSelector = null;

protected internal ItemsViewAdapter(TItemsView itemsView, Func<View, Context, ItemContentView> createItemContentView = null)
{
Expand Down Expand Up @@ -86,16 +87,36 @@ public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int

var itemContentView = _createItemContentView.Invoke(ItemsView, context);

// See if our cached templates have a match
if (_viewTypeDataTemplates.TryGetValue(viewType, out var dataTemplate))
{
return new TemplatedItemViewHolder(itemContentView, dataTemplate, IsSelectionEnabled(parent, viewType));
}

return new TemplatedItemViewHolder(itemContentView, ItemsView.ItemTemplate, IsSelectionEnabled(parent, viewType));
}

public override int ItemCount => ItemsSource.Count;

System.Collections.Generic.Dictionary<int, DataTemplate> _viewTypeDataTemplates = new();

public override int GetItemViewType(int position)
{
if (_usingItemTemplate)
{
return ItemViewType.TemplatedItem;
if (_itemTemplateSelector is null)
return ItemViewType.TemplatedItem;

var item = ItemsSource?.GetItem(position);

var template = _itemTemplateSelector?.SelectTemplate(item, ItemsView);
var id = template?.Id ?? ItemViewType.TemplatedItem;

// Cache the data template for future use
if (!_viewTypeDataTemplates.ContainsKey(id))
_viewTypeDataTemplates.Add(id, template);

return id;
}

// No template, just use the Text view
Expand All @@ -118,6 +139,11 @@ protected override void Dispose(bool disposing)
}
}

public override long GetItemId(int position)
{
return position;
}

public virtual int GetPositionForItem(object item)
{
return ItemsSource.GetPosition(item);
Expand All @@ -131,6 +157,7 @@ protected virtual void BindTemplatedItemViewHolder(TemplatedItemViewHolder templ
void UpdateUsingItemTemplate()
{
_usingItemTemplate = ItemsView.ItemTemplate != null;
_itemTemplateSelector = ItemsView.ItemTemplate as DataTemplateSelector;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public override UICollectionViewCell GetCell(UICollectionView collectionView, NS

if (Carousel?.Loop == true && _carouselViewLoopManager != null)
{
var cellAndCorrectedIndex = _carouselViewLoopManager.GetCellAndCorrectIndex(collectionView, indexPath, DetermineCellReuseId());
var cellAndCorrectedIndex = _carouselViewLoopManager.GetCellAndCorrectIndex(collectionView, indexPath, DetermineCellReuseId(indexPath));
cell = cellAndCorrectedIndex.cell;
var correctedIndexPath = NSIndexPath.FromRowSection(cellAndCorrectedIndex.correctedIndex, 0);

Expand Down Expand Up @@ -135,6 +135,9 @@ public override void UpdateItemsSource()

protected override UICollectionViewDelegateFlowLayout CreateDelegator() => new CarouselViewDelegator(ItemsViewLayout, this);

#if NET8_0_OR_GREATER
[Obsolete("Use DetermineCellReuseId(NSIndexPath indexPath) instead.")]
#endif
protected override string DetermineCellReuseId()
Redth marked this conversation as resolved.
Show resolved Hide resolved
{
if (Carousel.ItemTemplate != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.ComponentModel;
using CoreGraphics;
using Foundation;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Graphics;
using ObjCRuntime;
using UIKit;
Expand Down Expand Up @@ -34,6 +35,7 @@ public abstract class ItemsViewController<TItemsView> : UICollectionViewControll
UIView _emptyUIView;
VisualElement _emptyViewFormsElement;
Dictionary<object, TemplatedCell> _measurementCells = new Dictionary<object, TemplatedCell>();
List<string> _cellReuseIds = new List<string>();

protected UICollectionViewDelegateFlowLayout Delegator { get; set; }

Expand Down Expand Up @@ -89,7 +91,7 @@ protected override void Dispose(bool disposing)

public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = collectionView.DequeueReusableCell(DetermineCellReuseId(), indexPath) as UICollectionViewCell;
var cell = collectionView.DequeueReusableCell(DetermineCellReuseId(indexPath), indexPath) as UICollectionViewCell;

switch (cell)
{
Expand Down Expand Up @@ -339,6 +341,41 @@ protected virtual void CacheCellAttributes(NSIndexPath indexPath, CGSize size)
}
}

#if NET8_0_OR_GREATER
protected
#else
internal
#endif
virtual string DetermineCellReuseId(NSIndexPath indexPath)
{
if (ItemsView.ItemTemplate != null)
{
var item = ItemsSource[indexPath];

var dataTemplate = ItemsView.ItemTemplate.SelectDataTemplate(item, ItemsView);

var cellOrientation = ItemsViewLayout.ScrollDirection == UICollectionViewScrollDirection.Vertical ? "v" : "h";
var cellType = ItemsViewLayout.ScrollDirection == UICollectionViewScrollDirection.Vertical ? typeof(VerticalCell) : typeof(HorizontalCell);

var reuseId = $"_maui_{cellOrientation}_{dataTemplate.Id}";

if (!_cellReuseIds.Contains(reuseId))
{
CollectionView.RegisterClassForCell(cellType, new NSString(reuseId));
_cellReuseIds.Add(reuseId);
}

return reuseId;
}

return ItemsViewLayout.ScrollDirection == UICollectionViewScrollDirection.Horizontal
? HorizontalDefaultCell.ReuseId
: VerticalDefaultCell.ReuseId;
}

#if NET8_0_OR_GREATER
[Obsolete("Use DetermineCellReuseId(NSIndexPath indexPath) instead.")]
#endif
protected virtual string DetermineCellReuseId()
{
if (ItemsView.ItemTemplate != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Microsoft.Maui.Controls.Border.~Border() -> void
Microsoft.Maui.Controls.Handlers.Compatibility.FrameRenderer.FrameRenderer(Android.Content.Context! context, Microsoft.Maui.IPropertyMapper! mapper) -> void
Microsoft.Maui.Controls.Handlers.Compatibility.FrameRenderer.FrameRenderer(Android.Content.Context! context, Microsoft.Maui.IPropertyMapper! mapper, Microsoft.Maui.CommandMapper! commandMapper) -> void
override Microsoft.Maui.Controls.Handlers.Items.CarouselViewAdapter<TItemsView, TItemsViewSource>.GetItemViewType(int position) -> int
override Microsoft.Maui.Controls.Handlers.Items.ItemsViewAdapter<TItemsView, TItemsViewSource>.GetItemId(int position) -> long
Microsoft.Maui.Controls.Handlers.Items.MauiRecyclerView<TItemsView, TAdapter, TItemsViewSource>.~MauiRecyclerView() -> void
override Microsoft.Maui.Controls.Handlers.Items.ItemsViewHandler<TItemsView>.GetDesiredSize(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size
override Microsoft.Maui.Controls.Handlers.Items.ItemsViewHandler<TItemsView>.PlatformArrange(Microsoft.Maui.Graphics.Rect frame) -> void
Expand Down