问题
I am trying to design a control that would allow users to scroll up or down a range of numbers (e.g. 0-2000) with a similar feel of scrolling up or down a list.
I tried to use a ListView
or GridView
inside a Flyout
for this, but it's simply too slow just to load a couple thousand items.
Now I'm thinking the best way might be to simply simulate the list scrolling without actually having any items (i.e. an infinite scrolling selector). Any suggestions on what control to use as a base to implement such an infinite selector?
回答1:
Here is looped virtualized scroller. It keeps only 5 items so you could add a lot of them. For selection, you may add an event on Item.
public sealed partial class MainPage
{
private static int Count = 20;
private static float ItemSize = 128;
private Visual _spawnerVisual;
private readonly List<ItemModel> _items = new List<ItemModel>();
private readonly List<Item> _selectedItems = new List<Item>();
private int _index;
private static readonly Random Random = new Random();
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
PopulateItems();
SetInitialItems();
}
private void PopulateItems()
{
for (int i = 0; i < Count; i++)
{
_items.Add(new ItemModel());
}
}
private void SetInitialItems()
{
_index = -5;
for (int i = 0; i < 5; i++)
{
KillAtTop(true);
}
_spawnerVisual = ElementCompositionPreview.GetElementVisual(Spawner);
}
private void KillAtTop(bool atTop)
{
if (atTop)
{
if (_selectedItems.Count > 4)
{
_selectedItems.RemoveAt(0);
Spawner.Children.RemoveAt(0);
}
var index = GetIndex(_index + 3);
var item = new Item
{
Id = index,
BackColor = Color.FromArgb(255, (byte)Random.Next(0, 255), (byte)Random.Next(0, 255), (byte)Random.Next(0, 255)),
ItemSize = ItemSize,
ItemModel = _items[index],
OffsetY = (_index + 6) * ItemSize
};
_selectedItems.Add(item);
Spawner.Children.Add(item);
_index++;
}
else
{
if (_selectedItems.Count > 4)
{
_selectedItems.RemoveAt(_selectedItems.Count - 1);
Spawner.Children.RemoveAt(Spawner.Children.Count - 1);
}
var index = GetIndex(_index - 3);
var item = new Item
{
Id = index,
BackColor = Color.FromArgb(255, (byte)Random.Next(0, 255), (byte)Random.Next(0, 255), (byte)Random.Next(0, 255)),
ItemSize = ItemSize,
ItemModel = _items[index],
OffsetY = _index * ItemSize
};
_selectedItems.Insert(0, item);
Spawner.Children.Insert(0, item);
_index--;
}
}
private int GetIndex(int index)
{
index = index % _items.Count;
if (index > _items.Count - 1) return index - _items.Count;
if (index < 0) return _items.Count + index;
return index;
}
private void Slider_OnSetDelta(float deltaY)
{
SetPosition(deltaY);
}
public void SetPosition(float offsetY)
{
_spawnerVisual.Offset += new Vector3(0, offsetY / 10, 0);
if (_spawnerVisual.Offset.Y > -ItemSize * _index + ItemSize)
{
KillAtTop(false);
}
if (_spawnerVisual.Offset.Y < -ItemSize * _index)
{
KillAtTop(true);
}
}
}
Just add your ItemControl and call SetPosition to scroll. Should look like this:
回答2:
I tried to use a ListView or GridView inside a Flyout for this, but it's simply too slow just to load a couple thousand items.
For your requirement, you do not have to create all the items at once. You can load the remaining items as needed via implement ISupportIncrementalLoading interface. It allows to easily create a collection in which data is loaded incrementally, when a user is about to the end of the items available on the user interface. Using it, we can obtain a fast & fluid scrolling while loading an huge set of records.
public class ItemToShow : ObservableCollection<string>, ISupportIncrementalLoading
{
public int lastItem = 1;
public bool HasMoreItems
{
get
{
if (lastItem == 2000) return false;
else return true;
}
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
var progressBar = ((Window.Current.Content as Frame).Content as MainPage).MyProgressBar;
CoreDispatcher coreDispatcher = Window.Current.Dispatcher;
return Task.Run<LoadMoreItemsResult>(() =>
{
coreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
progressBar.IsIndeterminate = true;
progressBar.Visibility = Visibility.Visible;
});
List<string> items = new List<string>();
for (var i = 0; i < count; i++)
{
items.Add(string.Format("Items{0}", lastItem));
lastItem++;
if (lastItem == 2000)
break;
}
coreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach (string item in items)
{
this.Add(item);
progressBar.Visibility = Visibility.Collapsed;
progressBar.IsHoldingEnabled = false;
}
});
return new LoadMoreItemsResult() { Count = count };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
Usage
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView
x:Name="ListViewMain"
Margin="0,100,0,0"
Padding="100,0,0,10">
<ListView.Resources>
<DataTemplate x:Key="DataTemplateListViewMain">
<Grid
Width="100"
Height="100"
Background="LightGray">
<TextBlock
VerticalAlignment="Center"
FontSize="18"
Text="{Binding}"
TextAlignment="Center" />
</Grid>
</DataTemplate>
</ListView.Resources>
<ListView.ItemTemplate>
<StaticResource ResourceKey="DataTemplateListViewMain" />
</ListView.ItemTemplate>
</ListView>
<ProgressBar
x:Name="MyProgressBar"
Height="10"
Margin="0,5,0,0"
VerticalAlignment="Top"
x:FieldModifier="public"
Visibility="Collapsed" />
</Grid>
MainPage.xaml.cs
private void Page_Loaded(object sender, RoutedEventArgs e)
{
ListViewMain.ItemsSource = new ItemToShow();
}
来源:https://stackoverflow.com/questions/45495328/easiest-way-to-simulate-infinite-scrolling-selector-with-uwp