Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Redth committed Jun 7, 2024
2 parents 0a25496 + e7b420a commit ef1b612
Show file tree
Hide file tree
Showing 16 changed files with 349 additions and 163 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -350,4 +350,4 @@ MigrationBackup/
.ionide/
.DS_Store

.idea/
.idea/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# VirtualListView for .NET MAUI
This is an experiment in creating a virtualized ListView control for .NET MAUI to support simple, fast, multi-templated, uneven item sized lists by not adding too many bells and whistles and using an adapter pattern data source.

![Nuget: Redth.Maui.VirtualListView](https://img.shields.io/nuget/vpre/Redth.Maui.VirtualListView?logo=nuget&label=Redth.Maui.VirtualListView&color=004880&link=https%3A%2F%2Fwww.nuget.org%2Fpackages%2FRedth.Maui.VirtualListView%2F)
[![Nuget: Redth.Maui.VirtualListView](https://img.shields.io/nuget/vpre/Redth.Maui.VirtualListView?logo=nuget&label=Redth.Maui.VirtualListView&color=004880&link=https%3A%2F%2Fwww.nuget.org%2Fpackages%2FRedth.Maui.VirtualListView%2F)](https://www.nuget.org/packages/Redth.Maui.VirtualListView)


## Vroooom!
Expand Down
9 changes: 9 additions & 0 deletions Sample/VirtualListViewSample/MusicLibraryPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@
<Label Padding="20" Text="That's all folks!" TextColor="White" HorizontalOptions="Center" />
</Grid>
</vlv:VirtualListView.GlobalFooter>

<vlv:VirtualListView.EmptyView>
<Grid>
<Label
VerticalOptions="Center"
HorizontalOptions="Center"
Text="No Items" />
</Grid>
</vlv:VirtualListView.EmptyView>
</vlv:VirtualListView>

</ContentPage>
2 changes: 2 additions & 0 deletions Sample/VirtualListViewSample/SectionedAdapterPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<vlv:VirtualListView
Grid.Row="0"
Grid.Column="0" Grid.ColumnSpan="3"
IsRefreshEnabled="False"
OnRefresh="Vlv_OnOnRefresh"
x:Name="vlv"
OnSelectedItemsChanged="vlv_SelectedItemsChanged"
SelectionMode="Single">
Expand Down
6 changes: 6 additions & 0 deletions Sample/VirtualListViewSample/SectionedAdapterPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,10 @@ private void vlv_SelectedItemsChanged(object sender, SelectedItemsChangedEventAr
}

}

private async void Vlv_OnOnRefresh(object sender, RefreshEventArgs e)
{
await Task.Delay(3000);
e.Complete();
}
}
7 changes: 6 additions & 1 deletion VirtualListView/Adapters/VirtualListViewAdapterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

public abstract class VirtualListViewAdapterBase<TSection, TItem> : IVirtualListViewAdapter
{
public virtual int GetNumberOfSections() => 1;
// This adapter assumes we only ever have 1 section
// however we really want to return 0 if there's no items at all
// So, ask the derived class how many items might be in the first
// section and if any, we return 1 section otherwise 0
public virtual int GetNumberOfSections() =>
GetNumberOfItemsInSection(0) > 0 ? 1 : 0;

public event EventHandler OnDataInvalidated;

Expand Down
2 changes: 2 additions & 0 deletions VirtualListView/Apple/CvCell.ios.maccatalyst.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Microsoft.Maui;

internal class CvCell : UICollectionViewCell
{
internal const string ReuseIdUnknown = "UNKNOWN";

public VirtualListViewHandler Handler { get; set; }

public WeakReference<NSIndexPath> IndexPath { get; set; }
Expand Down
77 changes: 49 additions & 28 deletions VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Foundation;
#nullable enable
using Foundation;
using UIKit;

namespace Microsoft.Maui;
Expand All @@ -20,54 +21,74 @@ public CvDataSource(VirtualListViewHandler handler)

public override nint NumberOfSections(UICollectionView collectionView)
=> 1;

public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var info = Handler?.PositionalViewSelector?.GetInfo(indexPath.Item.ToInt32());

var data = Handler?.PositionalViewSelector?.Adapter?.DataFor(info.Kind, info.SectionIndex, info.ItemIndex);

var reuseId = Handler?.PositionalViewSelector?.ViewSelector?.GetReuseId(info, data);
object? data = null;

var nativeReuseId = info.Kind switch
var nativeReuseId = CvCell.ReuseIdUnknown;

if (info is not null)
{
PositionKind.Item => itemIdManager.GetReuseId(collectionView, reuseId),
PositionKind.SectionHeader => sectionHeaderIdManager.GetReuseId(collectionView, reuseId),
PositionKind.SectionFooter => sectionFooterIdManager.GetReuseId(collectionView, reuseId),
PositionKind.Header => globalIdManager.GetReuseId(collectionView, reuseId),
PositionKind.Footer => globalIdManager.GetReuseId(collectionView, reuseId),
_ => "UNKNOWN",
};

var cell = (collectionView.DequeueReusableCell(nativeReuseId, indexPath) as CvCell)!;
data = Handler?.PositionalViewSelector?.Adapter?.DataFor(info.Kind, info.SectionIndex, info.ItemIndex);

if (data is not null)
{
var reuseId = Handler?.PositionalViewSelector?.ViewSelector?.GetReuseId(info, data);

nativeReuseId = info.Kind switch
{
PositionKind.Item => itemIdManager.GetReuseId(collectionView, reuseId),
PositionKind.SectionHeader => sectionHeaderIdManager.GetReuseId(collectionView, reuseId),
PositionKind.SectionFooter => sectionFooterIdManager.GetReuseId(collectionView, reuseId),
PositionKind.Header => globalIdManager.GetReuseId(collectionView, reuseId),
PositionKind.Footer => globalIdManager.GetReuseId(collectionView, reuseId),
_ => CvCell.ReuseIdUnknown,
};
}
}

var nativeCell = collectionView.DequeueReusableCell(nativeReuseId, indexPath);
if (nativeCell is not CvCell cell)
return (UICollectionViewCell)nativeCell;

cell.SetTapHandlerCallback(TapCellHandler);
cell.Handler = Handler;
cell.IndexPath = new WeakReference<NSIndexPath>(indexPath);

cell.ReuseCallback = new WeakReference<Action<IView>>((rv) =>
{
if (cell?.VirtualView?.TryGetTarget(out var cellVirtualView) ?? false)
Handler.VirtualView.ViewSelector.ViewDetached(info, cellVirtualView);
if (info is not null && (cell.VirtualView?.TryGetTarget(out var cellView) ?? false))
Handler?.VirtualView?.ViewSelector?.ViewDetached(info, cellView);
});

if (info.SectionIndex < 0 || info.ItemIndex < 0)
info.IsSelected = false;
else
info.IsSelected = Handler?.IsItemSelected(info.SectionIndex, info.ItemIndex) ?? false;
if (info is not null)
{
if (info.SectionIndex < 0 || info.ItemIndex < 0)
info.IsSelected = false;
else
info.IsSelected = Handler?.IsItemSelected(info.SectionIndex, info.ItemIndex) ?? false;
}

if (cell.NeedsView)
if (cell.NeedsView && info is not null && data is not null)
{
var view = Handler?.PositionalViewSelector?.ViewSelector?.CreateView(info, data);
cell.SetupView(view);
if (view is not null)
cell.SetupView(view);
}

cell.UpdatePosition(info);

if (cell.VirtualView.TryGetTarget(out var cellVirtualView))
if (info is not null)
{
Handler?.PositionalViewSelector?.ViewSelector?.RecycleView(info, data, cellVirtualView);
cell.UpdatePosition(info);

if (data is not null && (cell.VirtualView?.TryGetTarget(out var cellVirtualView) ?? false))
{
Handler?.PositionalViewSelector?.ViewSelector?.RecycleView(info, data, cellVirtualView);

Handler.VirtualView.ViewSelector.ViewAttached(info, cellVirtualView);
Handler?.VirtualView?.ViewSelector?.ViewAttached(info, cellVirtualView);
}
}

return cell;
Expand Down
28 changes: 15 additions & 13 deletions VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ public CvDelegate(VirtualListViewHandler handler, UICollectionView collectionVie
{
Handler = handler;
NativeCollectionView = new WeakReference<UICollectionView>(collectionView);
collectionView.RegisterClassForCell(typeof(CvCell), CvCell.ReuseIdUnknown);
}

internal readonly WeakReference<UICollectionView> NativeCollectionView;
internal readonly VirtualListViewHandler Handler;

public WeakReference<Action<NFloat, NFloat>> ScrollHandler { get; set; }
public Action<NFloat, NFloat> ScrollHandler { get; set; }

Check warning on line 20 in VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs

View workflow job for this annotation

GitHub Actions / Build

Member 'ScrollHandler' could cause memory leaks in an NSObject subclass. Remove the member, store the value as a WeakReference, or add the

Check warning on line 20 in VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs

View workflow job for this annotation

GitHub Actions / Build

Member 'ScrollHandler' could cause memory leaks in an NSObject subclass. Remove the member, store the value as a WeakReference, or add the

Check warning on line 20 in VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs

View workflow job for this annotation

GitHub Actions / Build

Member 'ScrollHandler' could cause memory leaks in an NSObject subclass. Remove the member, store the value as a WeakReference, or add the

Check warning on line 20 in VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs

View workflow job for this annotation

GitHub Actions / Build

Member 'ScrollHandler' could cause memory leaks in an NSObject subclass. Remove the member, store the value as a WeakReference, or add the

Check warning on line 20 in VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs

View workflow job for this annotation

GitHub Actions / Build

Member 'ScrollHandler' could cause memory leaks in an NSObject subclass. Remove the member, store the value as a WeakReference, or add the

Check warning on line 20 in VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs

View workflow job for this annotation

GitHub Actions / Build

Member 'ScrollHandler' could cause memory leaks in an NSObject subclass. Remove the member, store the value as a WeakReference, or add the

public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath)
=> HandleSelection(collectionView, indexPath, true);
Expand All @@ -27,27 +28,28 @@ public override void ItemDeselected(UICollectionView collectionView, NSIndexPath
void HandleSelection(UICollectionView collectionView, NSIndexPath indexPath, bool selected)
{
//UIView.AnimationsEnabled = false;
var selectedCell = collectionView.CellForItem(indexPath) as CvCell;

if ((selectedCell?.PositionInfo?.Kind ?? PositionKind.Header) == PositionKind.Item)
if (collectionView.CellForItem(indexPath) is CvCell selectedCell
&& (selectedCell.PositionInfo?.Kind ?? PositionKind.Header) == PositionKind.Item)
{
selectedCell.UpdateSelected(selected);

var itemPos = new ItemPosition(
selectedCell.PositionInfo.SectionIndex,
selectedCell.PositionInfo.ItemIndex);
if (selectedCell.PositionInfo is not null)
{
var itemPos = new ItemPosition(
selectedCell.PositionInfo.SectionIndex,
selectedCell.PositionInfo.ItemIndex);

if (selected)
Handler?.VirtualView?.SelectItem(itemPos);
else
Handler?.VirtualView?.DeselectItem(itemPos);
if (selected)
Handler?.VirtualView?.SelectItem(itemPos);
else
Handler?.VirtualView?.DeselectItem(itemPos);
}
}
}

public override void Scrolled(UIScrollView scrollView)
{
if (ScrollHandler?.TryGetTarget(out var handler) ?? false)
handler?.Invoke(scrollView.ContentOffset.X, scrollView.ContentOffset.Y);
ScrollHandler?.Invoke(scrollView.ContentOffset.X, scrollView.ContentOffset.Y);
}

public override bool ShouldSelectItem(UICollectionView collectionView, NSIndexPath indexPath)
Expand Down
45 changes: 37 additions & 8 deletions VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,21 @@ protected override UICollectionView CreatePlatformView()


refreshControl = new UIRefreshControl();
refreshControl.Enabled = VirtualView?.IsRefreshEnabled ?? false;
refreshControl.AddTarget(new EventHandler((s, a) =>
{
refreshControl.BeginRefreshing();
VirtualView?.Refresh(() => refreshControl.EndRefreshing());
try
{
VirtualView?.Refresh(() => refreshControl.EndRefreshing());
}
catch
{
refreshControl.EndRefreshing();
}
}), UIControlEvent.ValueChanged);

collectionView.AddSubview(refreshControl);
//collectionView.AddSubview(refreshControl);

collectionView.AlwaysBounceVertical = true;

Expand All @@ -62,8 +70,7 @@ protected override void ConnectHandler(UICollectionView nativeView)
dataSource = new CvDataSource(this);

cvdelegate = new CvDelegate(this, nativeView);
cvdelegate.ScrollHandler = new WeakReference<Action<System.Runtime.InteropServices.NFloat, System.Runtime.InteropServices.NFloat>>((x, y) =>
VirtualView?.Scrolled(x, y));
cvdelegate.ScrollHandler = (x, y) => VirtualView?.Scrolled(x, y);

nativeView.DataSource = dataSource;
nativeView.Delegate = cvdelegate;
Expand Down Expand Up @@ -176,8 +183,20 @@ public static void MapRefreshAccentColor(VirtualListViewHandler handler, IVirtua

public static void MapIsRefreshEnabled(VirtualListViewHandler handler, IVirtualListView virtualListView)
{
var isRefreshEnabled = virtualListView?.IsRefreshEnabled ?? false;
if (handler.refreshControl is not null)
handler.refreshControl.Enabled = virtualListView.IsRefreshEnabled;
{
if (isRefreshEnabled)
{
handler.PlatformView.AddSubview(handler.refreshControl);
handler.refreshControl.Enabled = true;
}
else
{
handler.refreshControl.Enabled = false;
handler.refreshControl.RemoveFromSuperview();
}
}
}


Expand Down Expand Up @@ -213,16 +232,26 @@ void UpdateEmptyView()
}
}

void UpdateVerticalScrollbarVisibility(ScrollBarVisibility scrollBarVisibility)
{
PlatformView.ShowsVerticalScrollIndicator = scrollBarVisibility == ScrollBarVisibility.Always || scrollBarVisibility == ScrollBarVisibility.Default;
}

void UpdateHorizontalScrollbarVisibility(ScrollBarVisibility scrollBarVisibility)
{
PlatformView.ShowsHorizontalScrollIndicator = scrollBarVisibility == ScrollBarVisibility.Always || scrollBarVisibility == ScrollBarVisibility.Default;
}

public void InvalidateData()
{
this.PlatformView.InvokeOnMainThread(() => {
layout?.InvalidateLayout();
//layout?.InvalidateLayout();

UpdateEmptyViewVisibility();

PlatformView?.SetNeedsLayout();
//PlatformView?.SetNeedsLayout();
PlatformView?.ReloadData();
PlatformView?.LayoutIfNeeded();
//PlatformView?.LayoutIfNeeded();
});

}
Expand Down
Loading

0 comments on commit ef1b612

Please sign in to comment.