diff --git a/.gitignore b/.gitignore
index 1408c92..68f4899 100644
--- a/.gitignore
+++ b/.gitignore
@@ -350,4 +350,4 @@ MigrationBackup/
.ionide/
.DS_Store
-.idea/
\ No newline at end of file
+.idea/
diff --git a/README.md b/README.md
index edf5c6c..82bec0d 100644
--- a/README.md
+++ b/README.md
@@ -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!
diff --git a/Sample/VirtualListViewSample/MusicLibraryPage.xaml b/Sample/VirtualListViewSample/MusicLibraryPage.xaml
index 45ad698..508391c 100644
--- a/Sample/VirtualListViewSample/MusicLibraryPage.xaml
+++ b/Sample/VirtualListViewSample/MusicLibraryPage.xaml
@@ -72,6 +72,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Sample/VirtualListViewSample/SectionedAdapterPage.xaml b/Sample/VirtualListViewSample/SectionedAdapterPage.xaml
index 3e7ed68..f448b95 100644
--- a/Sample/VirtualListViewSample/SectionedAdapterPage.xaml
+++ b/Sample/VirtualListViewSample/SectionedAdapterPage.xaml
@@ -10,6 +10,8 @@
diff --git a/Sample/VirtualListViewSample/SectionedAdapterPage.xaml.cs b/Sample/VirtualListViewSample/SectionedAdapterPage.xaml.cs
index 12e3944..b7dbc9e 100644
--- a/Sample/VirtualListViewSample/SectionedAdapterPage.xaml.cs
+++ b/Sample/VirtualListViewSample/SectionedAdapterPage.xaml.cs
@@ -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();
+ }
}
diff --git a/VirtualListView/Adapters/VirtualListViewAdapterBase.cs b/VirtualListView/Adapters/VirtualListViewAdapterBase.cs
index f3a1154..4c54722 100644
--- a/VirtualListView/Adapters/VirtualListViewAdapterBase.cs
+++ b/VirtualListView/Adapters/VirtualListViewAdapterBase.cs
@@ -2,7 +2,12 @@
public abstract class VirtualListViewAdapterBase : 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;
diff --git a/VirtualListView/Apple/CvCell.ios.maccatalyst.cs b/VirtualListView/Apple/CvCell.ios.maccatalyst.cs
index 7e0eff3..517b642 100644
--- a/VirtualListView/Apple/CvCell.ios.maccatalyst.cs
+++ b/VirtualListView/Apple/CvCell.ios.maccatalyst.cs
@@ -9,6 +9,8 @@ namespace Microsoft.Maui;
internal class CvCell : UICollectionViewCell
{
+ internal const string ReuseIdUnknown = "UNKNOWN";
+
public VirtualListViewHandler Handler { get; set; }
public WeakReference IndexPath { get; set; }
diff --git a/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs b/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs
index be6bf5a..2d2dd99 100644
--- a/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs
+++ b/VirtualListView/Apple/CvDataSource.ios.maccatalyst.cs
@@ -1,4 +1,5 @@
-using Foundation;
+#nullable enable
+using Foundation;
using UIKit;
namespace Microsoft.Maui;
@@ -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(indexPath);
cell.ReuseCallback = new WeakReference>((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;
diff --git a/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs b/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs
index c67e3f9..b1deabe 100644
--- a/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs
+++ b/VirtualListView/Apple/CvDelegate.ios.maccatalyst.cs
@@ -11,12 +11,13 @@ public CvDelegate(VirtualListViewHandler handler, UICollectionView collectionVie
{
Handler = handler;
NativeCollectionView = new WeakReference(collectionView);
+ collectionView.RegisterClassForCell(typeof(CvCell), CvCell.ReuseIdUnknown);
}
internal readonly WeakReference NativeCollectionView;
internal readonly VirtualListViewHandler Handler;
- public WeakReference> ScrollHandler { get; set; }
+ public Action ScrollHandler { get; set; }
public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath)
=> HandleSelection(collectionView, indexPath, true);
@@ -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)
diff --git a/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs b/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs
index 90a5579..84e4f2c 100644
--- a/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs
+++ b/VirtualListView/Apple/VirtualListViewHandler.ios.maccatalyst.cs
@@ -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;
@@ -62,8 +70,7 @@ protected override void ConnectHandler(UICollectionView nativeView)
dataSource = new CvDataSource(this);
cvdelegate = new CvDelegate(this, nativeView);
- cvdelegate.ScrollHandler = new WeakReference>((x, y) =>
- VirtualView?.Scrolled(x, y));
+ cvdelegate.ScrollHandler = (x, y) => VirtualView?.Scrolled(x, y);
nativeView.DataSource = dataSource;
nativeView.Delegate = cvdelegate;
@@ -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();
+ }
+ }
}
@@ -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();
});
}
diff --git a/VirtualListView/Controls/VirtualListView.cs b/VirtualListView/Controls/VirtualListView.cs
index fda0aa0..a6d0d73 100644
--- a/VirtualListView/Controls/VirtualListView.cs
+++ b/VirtualListView/Controls/VirtualListView.cs
@@ -1,6 +1,6 @@
using System.Windows.Input;
-using Microsoft.Maui.Adapters;
-
+using Microsoft.Maui.Adapters;
+
namespace Microsoft.Maui.Controls;
public partial class VirtualListView : View, IVirtualListView, IVirtualListViewSelector
@@ -30,13 +30,13 @@ public IView GlobalHeader
public static readonly BindableProperty GlobalHeaderProperty =
BindableProperty.Create(nameof(GlobalHeader), typeof(IView), typeof(VirtualListView), default);
- public bool IsHeaderVisible
- {
- get => (bool)GetValue(IsHeaderVisibleProperty);
- set => SetValue(IsHeaderVisibleProperty, value);
- }
-
- public static readonly BindableProperty IsHeaderVisibleProperty =
+ public bool IsHeaderVisible
+ {
+ get => (bool)GetValue(IsHeaderVisibleProperty);
+ set => SetValue(IsHeaderVisibleProperty, value);
+ }
+
+ public static readonly BindableProperty IsHeaderVisibleProperty =
BindableProperty.Create(nameof(IsHeaderVisible), typeof(bool), typeof(VirtualListView), true);
public IView GlobalFooter
@@ -49,14 +49,14 @@ public IView GlobalFooter
BindableProperty.Create(nameof(GlobalFooter), typeof(IView), typeof(VirtualListView), default);
- public bool IsFooterVisible
- {
- get => (bool)GetValue(IsFooterVisibleProperty);
- set => SetValue(IsFooterVisibleProperty, value);
- }
-
- public static readonly BindableProperty IsFooterVisibleProperty =
- BindableProperty.Create(nameof(IsFooterVisible), typeof(bool), typeof(VirtualListView), true);
+ public bool IsFooterVisible
+ {
+ get => (bool)GetValue(IsFooterVisibleProperty);
+ set => SetValue(IsFooterVisibleProperty, value);
+ }
+
+ public static readonly BindableProperty IsFooterVisibleProperty =
+ BindableProperty.Create(nameof(IsFooterVisible), typeof(bool), typeof(VirtualListView), true);
public DataTemplate ItemTemplate
@@ -77,8 +77,26 @@ public VirtualListViewItemTemplateSelector ItemTemplateSelector
public static readonly BindableProperty ItemTemplateSelectorProperty =
BindableProperty.Create(nameof(ItemTemplateSelector), typeof(VirtualListViewItemTemplateSelector), typeof(VirtualListView), default);
+
+ public ScrollBarVisibility VerticalScrollbarVisibility
+ {
+ get => (ScrollBarVisibility)GetValue(VerticalScrollbarVisibilityProperty);
+ set => SetValue(VerticalScrollbarVisibilityProperty, value);
+ }
+
+ public static readonly BindableProperty VerticalScrollbarVisibilityProperty =
+ BindableProperty.Create(nameof(VerticalScrollbarVisibility), typeof(ScrollBarVisibility), typeof(VirtualListView), ScrollBarVisibility.Default);
+ public ScrollBarVisibility HorizontalScrollbarVisibility
+ {
+ get => (ScrollBarVisibility)GetValue(HorizontalScrollbarVisibilityProperty);
+ set => SetValue(HorizontalScrollbarVisibilityProperty, value);
+ }
+
+ public static readonly BindableProperty HorizontalScrollbarVisibilityProperty =
+ BindableProperty.Create(nameof(HorizontalScrollbarVisibility), typeof(ScrollBarVisibility), typeof(VirtualListView), ScrollBarVisibility.Default);
+
public DataTemplate SectionHeaderTemplate
{
get => (DataTemplate)GetValue(SectionHeaderTemplateProperty);
@@ -148,26 +166,26 @@ public ICommand RefreshCommand
}
public static readonly BindableProperty RefreshCommandProperty =
- BindableProperty.Create(nameof(RefreshCommand), typeof(ICommand), typeof(VirtualListView), default);
-
- public Color RefreshAccentColor
- {
- get => (Color)GetValue(RefreshAccentColorProperty);
- set => SetValue(RefreshAccentColorProperty, value);
- }
-
- public static readonly BindableProperty RefreshAccentColorProperty =
- BindableProperty.Create(nameof(RefreshAccentColor), typeof(Color), typeof(VirtualListView), null);
-
- public bool IsRefreshEnabled
- {
- get => (bool)GetValue(IsRefreshEnabledProperty);
- set => SetValue(IsRefreshEnabledProperty, value);
- }
-
- public static readonly BindableProperty IsRefreshEnabledProperty =
- BindableProperty.Create(nameof(IsRefreshEnabled), typeof(bool), typeof(VirtualListView), false);
-
+ BindableProperty.Create(nameof(RefreshCommand), typeof(ICommand), typeof(VirtualListView), default);
+
+ public Color RefreshAccentColor
+ {
+ get => (Color)GetValue(RefreshAccentColorProperty);
+ set => SetValue(RefreshAccentColorProperty, value);
+ }
+
+ public static readonly BindableProperty RefreshAccentColorProperty =
+ BindableProperty.Create(nameof(RefreshAccentColor), typeof(Color), typeof(VirtualListView), null);
+
+ public bool IsRefreshEnabled
+ {
+ get => (bool)GetValue(IsRefreshEnabledProperty);
+ set => SetValue(IsRefreshEnabledProperty, value);
+ }
+
+ public static readonly BindableProperty IsRefreshEnabledProperty =
+ BindableProperty.Create(nameof(IsRefreshEnabled), typeof(bool), typeof(VirtualListView), false);
+
public ListOrientation Orientation
{
get => (ListOrientation)GetValue(OrientationProperty);
@@ -178,25 +196,25 @@ public ListOrientation Orientation
BindableProperty.Create(nameof(Orientation), typeof(ListOrientation), typeof(VirtualListView), ListOrientation.Vertical);
- public View EmptyView
- {
- get => (View)GetValue(EmptyViewProperty);
- set => SetValue(EmptyViewProperty, value);
- }
-
- public static readonly BindableProperty EmptyViewProperty =
- BindableProperty.Create(nameof(EmptyView), typeof(View), typeof(VirtualListView), null,
- propertyChanged: (bobj, oldValue, newValue) =>
- {
- if (bobj is VirtualListView virtualListView)
- {
- if (oldValue is IView oldView)
- virtualListView.RemoveLogicalChild(oldView);
-
- if (newValue is IView newView)
- virtualListView.AddLogicalChild(newView);
- }
- });
+ public View EmptyView
+ {
+ get => (View)GetValue(EmptyViewProperty);
+ set => SetValue(EmptyViewProperty, value);
+ }
+
+ public static readonly BindableProperty EmptyViewProperty =
+ BindableProperty.Create(nameof(EmptyView), typeof(View), typeof(VirtualListView), null,
+ propertyChanged: (bobj, oldValue, newValue) =>
+ {
+ if (bobj is VirtualListView virtualListView)
+ {
+ if (oldValue is IView oldView)
+ virtualListView.RemoveLogicalChild(oldView);
+
+ if (newValue is IView newView)
+ virtualListView.AddLogicalChild(newView);
+ }
+ });
IView IVirtualListView.EmptyView => EmptyView;
@@ -225,46 +243,46 @@ public ICommand ScrolledCommand
{
get => (ICommand)GetValue(ScrolledCommandProperty);
set => SetValue(ScrolledCommandProperty, value);
- }
-
+ }
+
public static readonly BindableProperty SelectedItemsProperty =
- BindableProperty.Create(nameof(SelectedItems), typeof(IList), typeof(VirtualListView), Array.Empty(),
- propertyChanged: (bindableObj, oldValue, newValue) =>
- {
- if (bindableObj is VirtualListView vlv
- && oldValue is IList oldSelection
- && newValue is IList newSelection)
- {
- vlv.RaiseSelectedItemsChanged(oldSelection.ToArray(), newSelection.ToArray());
- }
- });
- public IList SelectedItems
- {
- get => (IList)GetValue(SelectedItemsProperty);
- set => SetValue(SelectedItemsProperty, value ?? Array.Empty());
- }
-
+ BindableProperty.Create(nameof(SelectedItems), typeof(IList), typeof(VirtualListView), Array.Empty(),
+ propertyChanged: (bindableObj, oldValue, newValue) =>
+ {
+ if (bindableObj is VirtualListView vlv
+ && oldValue is IList oldSelection
+ && newValue is IList newSelection)
+ {
+ vlv.RaiseSelectedItemsChanged(oldSelection.ToArray(), newSelection.ToArray());
+ }
+ });
+ public IList SelectedItems
+ {
+ get => (IList)GetValue(SelectedItemsProperty);
+ set => SetValue(SelectedItemsProperty, value ?? Array.Empty());
+ }
+
public static readonly BindableProperty SelectedItemProperty =
- BindableProperty.Create(nameof(SelectedItem), typeof(ItemPosition?), typeof(VirtualListView), default,
- propertyChanged: (bindableObj, oldValue, newValue) =>
- {
- if (bindableObj is VirtualListView vlv)
- {
- if (newValue is null || newValue is not ItemPosition)
- vlv.SelectedItems = null;
- else if (newValue is ItemPosition p)
- vlv.SelectedItems = new[] { p };
- }
+ BindableProperty.Create(nameof(SelectedItem), typeof(ItemPosition?), typeof(VirtualListView), default,
+ propertyChanged: (bindableObj, oldValue, newValue) =>
+ {
+ if (bindableObj is VirtualListView vlv)
+ {
+ if (newValue is null || newValue is not ItemPosition)
+ vlv.SelectedItems = null;
+ else if (newValue is ItemPosition p)
+ vlv.SelectedItems = new[] { p };
+ }
});
- public ItemPosition? SelectedItem
- {
- get => (ItemPosition?)GetValue(SelectedItemProperty);
- set => SetValue(SelectedItemProperty, value);
- }
-
+ public ItemPosition? SelectedItem
+ {
+ get => (ItemPosition?)GetValue(SelectedItemProperty);
+ set => SetValue(SelectedItemProperty, value);
+ }
+
- public void DeselectItem(ItemPosition itemPosition)
+ public void DeselectItem(ItemPosition itemPosition)
{
if (SelectionMode == Maui.SelectionMode.Single)
{
@@ -280,17 +298,17 @@ public void DeselectItem(ItemPosition itemPosition)
{
current.Remove(itemPosition);
SelectedItems = current.ToArray();
- }
- }
+ }
+ }
}
- public void SelectItem(ItemPosition itemPosition)
+ public void SelectItem(ItemPosition itemPosition)
{
if (SelectionMode == Maui.SelectionMode.Single)
{
- if (!SelectedItem.HasValue || !SelectedItem.Value.Equals(itemPosition))
- {
- SelectedItem = itemPosition;
+ if (!SelectedItem.HasValue || !SelectedItem.Value.Equals(itemPosition))
+ {
+ SelectedItem = itemPosition;
}
}
else if (SelectionMode == Maui.SelectionMode.Multiple)
@@ -300,15 +318,15 @@ public void SelectItem(ItemPosition itemPosition)
{
SelectedItems = current.Append(itemPosition).ToArray();
}
- }
+ }
}
- public void ClearSelectedItems()
+ public void ClearSelectedItems()
{
if (SelectionMode == Maui.SelectionMode.Multiple)
- SelectedItems = null;
- else
- SelectedItem = null;
+ SelectedItems = null;
+ else
+ SelectedItem = null;
}
public void ScrollToItem(ItemPosition itemPosition, bool animated)
@@ -321,9 +339,9 @@ public bool SectionHasFooter(int sectionIndex)
=> SectionFooterTemplateSelector != null || SectionFooterTemplate != null;
public IView CreateView(PositionInfo position, object data)
- => position.Kind switch
+ => position.Kind switch
{
- PositionKind.Item =>
+ PositionKind.Item =>
ItemTemplateSelector?.SelectTemplate(data, position.SectionIndex, position.ItemIndex)?.CreateContent() as View
?? ItemTemplate?.CreateContent() as View,
PositionKind.SectionHeader =>
@@ -378,9 +396,9 @@ public void ViewDetached(PositionInfo position, IView view)
=> this.RemoveLogicalChild(view);
public void ViewAttached(PositionInfo position, IView view)
- => this.AddLogicalChild(view);
-
- void RaiseSelectedItemsChanged(ItemPosition[] previousSelection, ItemPosition[] newSelection)
+ => this.AddLogicalChild(view);
+
+ void RaiseSelectedItemsChanged(ItemPosition[] previousSelection, ItemPosition[] newSelection)
=> this.OnSelectedItemsChanged?.Invoke(this, new SelectedItemsChangedEventArgs(previousSelection, newSelection));
}
diff --git a/VirtualListView/IVirtualListView.cs b/VirtualListView/IVirtualListView.cs
index 0c80ea9..eea3308 100644
--- a/VirtualListView/IVirtualListView.cs
+++ b/VirtualListView/IVirtualListView.cs
@@ -25,6 +25,10 @@ public interface IVirtualListView : IView
IList SelectedItems { get; set; }
ItemPosition? SelectedItem { get; set; }
+
+ ScrollBarVisibility VerticalScrollbarVisibility { get; set; }
+
+ ScrollBarVisibility HorizontalScrollbarVisibility { get; set; }
event EventHandler OnSelectedItemsChanged;
diff --git a/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs b/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs
index c40f6fc..4f4e8cc 100644
--- a/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs
+++ b/VirtualListView/Platforms/Android/VirtualListViewHandler.android.cs
@@ -164,4 +164,35 @@ void UpdateEmptyView()
UpdateEmptyViewVisibility();
}
}
+
+ ScrollBarVisibility _defaultHorizontalScrollVisibility = ScrollBarVisibility.Default;
+ ScrollBarVisibility _defaultVerticalScrollVisibility = ScrollBarVisibility.Default;
+
+ void UpdateVerticalScrollbarVisibility(ScrollBarVisibility scrollBarVisibility)
+ {
+ if (_defaultVerticalScrollVisibility == ScrollBarVisibility.Default)
+ _defaultVerticalScrollVisibility =
+ recyclerView.VerticalScrollBarEnabled ? ScrollBarVisibility.Always : ScrollBarVisibility.Never;
+
+ var newVerticalScrollVisiblility = scrollBarVisibility;
+
+ if (newVerticalScrollVisiblility == ScrollBarVisibility.Default)
+ newVerticalScrollVisiblility = _defaultVerticalScrollVisibility;
+
+ recyclerView.VerticalScrollBarEnabled = newVerticalScrollVisiblility == ScrollBarVisibility.Always;
+ }
+
+ void UpdateHorizontalScrollbarVisibility(ScrollBarVisibility scrollBarVisibility)
+ {
+ if (_defaultHorizontalScrollVisibility == ScrollBarVisibility.Default)
+ _defaultHorizontalScrollVisibility =
+ recyclerView.HorizontalScrollBarEnabled ? ScrollBarVisibility.Always : ScrollBarVisibility.Never;
+
+ var newHorizontalScrollVisiblility = scrollBarVisibility;
+
+ if (newHorizontalScrollVisiblility == ScrollBarVisibility.Default)
+ newHorizontalScrollVisiblility = _defaultHorizontalScrollVisibility;
+
+ recyclerView.HorizontalScrollBarEnabled = newHorizontalScrollVisiblility == ScrollBarVisibility.Always;
+ }
}
\ No newline at end of file
diff --git a/VirtualListView/Platforms/Windows/VirtualListViewHandler.windows.cs b/VirtualListView/Platforms/Windows/VirtualListViewHandler.windows.cs
index c6ccb7d..9c0d766 100644
--- a/VirtualListView/Platforms/Windows/VirtualListViewHandler.windows.cs
+++ b/VirtualListView/Platforms/Windows/VirtualListViewHandler.windows.cs
@@ -4,6 +4,7 @@
using WGrid = Microsoft.UI.Xaml.Controls.Grid;
using WVisibility = Microsoft.UI.Xaml.Visibility;
using WFrameworkElement = Microsoft.UI.Xaml.FrameworkElement;
+using WScrollBarVisibility = Microsoft.UI.Xaml.Controls.ScrollBarVisibility;
using Microsoft.Maui.Platform;
using Microsoft.UI.Xaml;
@@ -164,4 +165,26 @@ void UpdateEmptyView()
UpdateEmptyViewVisibility();
}
+
+ void UpdateVerticalScrollbarVisibility(ScrollBarVisibility scrollBarVisibility)
+ {
+ scrollViewer.VerticalScrollBarVisibility = scrollBarVisibility switch
+ {
+ ScrollBarVisibility.Default => WScrollBarVisibility.Auto,
+ ScrollBarVisibility.Always => WScrollBarVisibility.Visible,
+ ScrollBarVisibility.Never => WScrollBarVisibility.Hidden,
+ _ => WScrollBarVisibility.Auto
+ };
+ }
+
+ void UpdateHorizontalScrollbarVisibility(ScrollBarVisibility scrollBarVisibility)
+ {
+ scrollViewer.HorizontalScrollBarVisibility = scrollBarVisibility switch
+ {
+ ScrollBarVisibility.Default => WScrollBarVisibility.Auto,
+ ScrollBarVisibility.Always => WScrollBarVisibility.Visible,
+ ScrollBarVisibility.Never => WScrollBarVisibility.Hidden,
+ _ => WScrollBarVisibility.Auto
+ };
+ }
}
diff --git a/VirtualListView/PositionalViewSelector.cs b/VirtualListView/PositionalViewSelector.cs
index 4cd3ee3..8b93b4a 100644
--- a/VirtualListView/PositionalViewSelector.cs
+++ b/VirtualListView/PositionalViewSelector.cs
@@ -28,12 +28,32 @@ int GetTotalCount()
{
var sum = 0;
- if (HasGlobalHeader)
- sum += 1;
+ var hasAtLeastOneItem = false;
+ var numberOfSections = Adapter.GetNumberOfSections();
+
+ if (HasGlobalHeader && numberOfSections > 0)
+ {
+ // Make sure that there's at least one section with at least
+ // one item, otherwise it's 'empty'
+ // The default adapter may always return 1 for number of sections
+ // so it's not enough to check that
+ for (int s = 0; s < numberOfSections; s++)
+ {
+ if (Adapter.GetNumberOfItemsInSection(s) > 0)
+ {
+ sum += 1;
+ // If we found one, we can stop looping
+ // since we just care to calculate a spot
+ // for the header cell if the adapter isn't empty
+ hasAtLeastOneItem = true;
+ break;
+ }
+ }
+ }
if (Adapter != null)
{
- for (int s = 0; s < Adapter.GetNumberOfSections(); s++)
+ for (int s = 0; s < numberOfSections; s++)
{
if (ViewSelector.SectionHasHeader(s))
sum += 1;
@@ -45,7 +65,9 @@ int GetTotalCount()
}
}
- if (HasGlobalFooter)
+ // Only count footer if there is already at least one item
+ // otherwise the adapter is empty and we shouldn't count it
+ if (HasGlobalFooter && hasAtLeastOneItem)
sum += 1;
return sum;
diff --git a/VirtualListView/VirtualListViewHandler.cs b/VirtualListView/VirtualListViewHandler.cs
index 2e73958..06dd620 100644
--- a/VirtualListView/VirtualListViewHandler.cs
+++ b/VirtualListView/VirtualListViewHandler.cs
@@ -18,6 +18,8 @@ public partial class VirtualListViewHandler
[nameof(IVirtualListView.IsRefreshEnabled)] = MapIsRefreshEnabled,
[nameof(IVirtualListView.EmptyView)] = MapEmptyView,
[nameof(IVirtualListView.SelectedItems)] = MapSelectedItems,
+ [nameof(IVirtualListView.VerticalScrollbarVisibility)] = MapVerticalScrollbarVisibility,
+ [nameof(IVirtualListView.HorizontalScrollbarVisibility)] = MapHorizontalScrollbarVisibility,
};
public static CommandMapper CommandMapper = new(ViewCommandMapper)
@@ -135,5 +137,15 @@ public static void MapIsFooterVisible(VirtualListViewHandler handler, IVirtualLi
{
handler?.InvalidateData();
}
+
+ public static void MapVerticalScrollbarVisibility(VirtualListViewHandler handler, IVirtualListView virtualListView)
+ {
+ handler?.UpdateVerticalScrollbarVisibility(virtualListView.VerticalScrollbarVisibility);
+ }
+
+ public static void MapHorizontalScrollbarVisibility(VirtualListViewHandler handler, IVirtualListView virtualListView)
+ {
+ handler?.UpdateHorizontalScrollbarVisibility(virtualListView.HorizontalScrollbarVisibility);
+ }
}
\ No newline at end of file