Skip to content

Commit

Permalink
feat(ListView): incremental loading support for skia/wasm
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaoy312 committed Mar 21, 2022
1 parent a2ce826 commit 1410316
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/Uno.Foundation/IndexPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ int IComparable<IndexPath>.CompareTo(IndexPath other)

public static IndexPath Zero { get; } = new IndexPath();

public static IndexPath NotFound { get; } = new IndexPath(-1, 0);

public static bool operator <(IndexPath indexPath1, IndexPath indexPath2)
{
return Compare(indexPath1, indexPath2) < 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
Expand Down Expand Up @@ -30,6 +30,7 @@
using System.Runtime.CompilerServices;
using Windows.UI.Xaml.Data;
using Uno.UI.RuntimeTests.Extensions;
using System.Runtime.InteropServices.WindowsRuntime;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls
{
Expand Down Expand Up @@ -1766,9 +1767,114 @@ public async Task When_Pool_Aware_View_In_Item_Template()
}
}

private bool ApproxEquals(double value1, double value2) => Math.Abs(value1 - value2) <= 2;
[TestMethod]
public async Task When_Incremental_Load()
{
const int BatchSize = 25;

// setup
var container = new Grid { Height = 210, VerticalAlignment = VerticalAlignment.Bottom };

var list = new ListView
{
ItemContainerStyle = BasicContainerStyle,
ItemTemplate = FixedSizeItemTemplate // height=29
};
container.Children.Add(list);

var source = new InfiniteSource<int>(async start =>
{
await Task.Delay(25);
return Enumerable.Range(start, BatchSize).ToArray();
});
list.ItemsSource = source;

WindowHelper.WindowContent = container;
await WindowHelper.WaitForLoaded(list);
await Task.Delay(1000);
var initial = GetCurrenState();

// scroll to bottom
ScrollBy(list, 10000);
await Task.Delay(500);
await WindowHelper.WaitForIdle();
var firstScroll = GetCurrenState();

// scroll to bottom
ScrollBy(list, 10000);
await Task.Delay(500);
await WindowHelper.WaitForIdle();
var secondScroll = GetCurrenState();

Assert.AreEqual(BatchSize * 1, initial.LastLoaded, "Should start with first batch loaded.");
Assert.AreEqual(BatchSize * 2, firstScroll.LastLoaded, "Should have 2 batches loaded after first scroll.");
Assert.IsTrue(initial.LastMaterialized < firstScroll.LastMaterialized, "No extra item materialized after first scroll.");
Assert.AreEqual(BatchSize * 3, secondScroll.LastLoaded, "Should have 3 batches loaded after second scroll.");
Assert.IsTrue(firstScroll.LastMaterialized < secondScroll.LastMaterialized, "No extra item materialized after second scroll.");

(int LastLoaded, int LastMaterialized) GetCurrenState() =>
(
source.LastIndex,
Enumerable.Range(0, source.LastIndex).Reverse().FirstOrDefault(x => list.ContainerFromIndex(x) != null)
);
}

[TestMethod]
public async Task When_Incremental_Load_ShouldStop()
{
const int BatchSize = 25;

// setup
var container = new Grid { Height = 210, VerticalAlignment = VerticalAlignment.Bottom };

var list = new ListView
{
ItemContainerStyle = BasicContainerStyle,
ItemTemplate = FixedSizeItemTemplate // height=29
};
container.Children.Add(list);

var source = new InfiniteSource<int>(async start =>
{
await Task.Delay(25);
return Enumerable.Range(start, BatchSize).ToArray();
});
list.ItemsSource = source;

WindowHelper.WindowContent = container;
await WindowHelper.WaitForLoaded(list);
await Task.Delay(1000);
var initial = GetCurrenState();

// scroll to bottom
ScrollBy(list, 10000);
await Task.Delay(500);
await WindowHelper.WaitForIdle();
var firstScroll = GetCurrenState();

// Has'No'MoreItems
source.HasMoreItems = false;

// scroll to bottom
ScrollBy(list, 10000);
await Task.Delay(500);
await WindowHelper.WaitForIdle();
var secondScroll = GetCurrenState();

Assert.AreEqual(BatchSize * 1, initial.LastLoaded, "Should start with first batch loaded.");
Assert.AreEqual(BatchSize * 2, firstScroll.LastLoaded, "Should have 2 batches loaded after first scroll.");
Assert.IsTrue(initial.LastMaterialized < firstScroll.LastMaterialized, "No extra item materialized after first scroll.");
Assert.AreEqual(BatchSize * 2, secondScroll.LastLoaded, "Should still have 2 batches loaded after first scroll since HasMoreItems was false.");
Assert.AreEqual(BatchSize * 2 - 1, secondScroll.LastMaterialized, "Last materialized item should be the last from 2nd batch (50th/index=49).");

(int LastLoaded, int LastMaterialized) GetCurrenState() =>
(
source.LastIndex,
Enumerable.Range(0, source.LastIndex).Reverse().FirstOrDefault(x => list.ContainerFromIndex(x) != null)
);
}

private bool ApproxEquals(double value1, double value2) => Math.Abs(value1 - value2) <= 2;

#region Helper classes
private class When_Removed_From_Tree_And_Selection_TwoWay_Bound_DataContext : System.ComponentModel.INotifyPropertyChanged
Expand Down Expand Up @@ -1852,8 +1958,10 @@ public string Display
}
}
}
#endregion
}

#region Helper classes
public partial class OnItemsChangedListView : ListView
{
public Action ItemsChangedAction = null;
Expand Down Expand Up @@ -2015,5 +2123,39 @@ protected override DataTemplate SelectTemplateCore(object item)
}
}
}

public class InfiniteSource<T> : ObservableCollection<T>, ISupportIncrementalLoading
{
public delegate Task<T[]> AsyncFetch(int start);
public delegate T[] Fetch(int start);

private readonly AsyncFetch _fetchAsync;
private int _start;

public InfiniteSource(AsyncFetch fetch)
{
_fetchAsync = fetch;
_start = 0;
}

public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
return AsyncInfo.Run(async ct =>
{
var items = await _fetchAsync(_start);
foreach (var item in items)
{
Add(item);
}
_start += items.Length;

return new LoadMoreItemsResult { Count = count };
});
}

public bool HasMoreItems { get; set; } = true;

public int LastIndex => _start;
}
#endregion
}
26 changes: 24 additions & 2 deletions src/Uno.UI/UI/Xaml/Controls/ListViewBase/ListViewBase.managed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,26 @@ namespace Windows.UI.Xaml.Controls
{
public partial class ListViewBase
{
private int PageSize => throw new NotImplementedException();
private int PageSize
{
get
{
if (VirtualizingPanel is null)
{
return 0;
}

var layouter = VirtualizingPanel.GetLayouter();
var firstVisibleIndex = layouter.FirstVisibleIndex;
var lastVisibleIndex = layouter.LastVisibleIndex;
if (lastVisibleIndex == -1)
{
return 0;
}

return lastVisibleIndex - firstVisibleIndex + 1;
}
}

private void AddItems(int firstItem, int count, int section)
{
Expand Down Expand Up @@ -56,7 +75,10 @@ private void ReplaceGroup(int groupIndexInView)

private void TryLoadMoreItems()
{
//TODO: ISupportIncrementalLoading
if (VirtualizingPanel.GetLayouter() is { } layouter)
{
TryLoadMoreItems(layouter.LastVisibleIndex);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ private void OnScrollChanged(object? sender, ScrollViewerViewChangedEventArgs e)
unappliedDelta -= scrollIncrement;
unappliedDelta = Max(0, unappliedDelta);
UpdateLayout(extentAdjustment: sign * -unappliedDelta, isScroll: true);

#if __WASM__ || __SKIA__
(ItemsControl as ListViewBase)?.TryLoadMoreItems(LastVisibleIndex);
#endif
}
ArrangeElements(_availableSize, ViewportSize);
UpdateCompleted();
Expand Down Expand Up @@ -817,12 +821,12 @@ private void OnOrientationChanged(Orientation newValue)

private Uno.UI.IndexPath GetFirstVisibleIndexPath()
{
throw new NotImplementedException(); //TODO: FirstVisibleIndex
return GetFirstMaterializedLine()?.FirstItem ?? Uno.UI.IndexPath.NotFound;
}

private Uno.UI.IndexPath GetLastVisibleIndexPath()
{
throw new NotImplementedException(); //TODO: LastVisibleIndex
return GetLastMaterializedLine()?.LastItem ?? Uno.UI.IndexPath.NotFound;
}

private IEnumerable<float> GetSnapPointsInner(SnapPointsAlignment alignment)
Expand Down

0 comments on commit 1410316

Please sign in to comment.