Content-Level Virtualization for Avalonia ListBox
Is your feature request related to a problem? Please describe.
Avalonia's VirtualizingStackPanel currently only recycles containers (ListBoxItem/ContentPresenter) during scrolling, but destroys and recreates their content (the expensive UI created by DataTemplates).
For complex templates with nested layouts (e.g., Border > StackPanel > multiple TextBlocks), this causes:
- Performance issues: Full Measure/Arrange cycle on every scroll
- Memory pressure: Continuous allocation/deallocation of control hierarchies
- Terrible Android performance: Scrolling is extremely janky and unresponsive on Android devices, making apps feel sluggish and unprofessional
- Poor mobile experience: Resource-constrained devices struggle with the constant UI recreation overhead
- Wasted work: Recreating identical UI structures hundreds of times
Example: A ListBox with 1000 items where each template creates 10-20 controls will destroy and recreate these controls repeatedly during scrolling, even though the template structure never changes. On Android, this makes the app nearly unusable for lists with complex templates.
Describe the solution you'd like
Implement type-aware container-level virtualization that keeps content attached to containers and pools them as a unit:
Key Features
-
Type-Aware Container Pooling
- Containers pooled by data type (e.g.,
typeof(ProductItem) vs typeof(TaskItem))
- Ensures containers are only reused for compatible data types
- Prevents template mismatches and unnecessary content rebuilding
-
Content Preservation
- Child controls stay attached to their containers during recycling
- No visual tree detachment/reattachment → no layout invalidation
- Only the
DataContext changes → bindings update efficiently
-
Two Activation Modes
- Explicit:
<DataTemplate DataType="..." EnableVirtualization="True">
- Automatic: Templates with
DataType set automatically benefit (zero code changes)
-
Opt-In Design
- Controlled via global flag for enabling/disabling the feature
- Falls back to original behavior when disabled
- Fully backward compatible
Expected Performance Gains
- 50-90% reduction in Measure/Arrange cycles during scrolling
- Dramatically reduced GC pressure (no control creation/destruction)
- 10-100x performance improvement for complex heterogeneous lists
- Critical for Android: Transforms janky, unusable scrolling into smooth, native-like performance
- Especially beneficial for mobile/embedded devices
Implementation Approach
The solution modifies ItemsControl.NeedsContainerOverride() to return type-specific recycle keys and ClearContainerForItemOverride() to conditionally skip content clearing when virtualization is active. This allows VirtualizingStackPanel to maintain separate pools per data type while keeping the Child control attached.
Describe alternatives you've considered
Alternative 1: Separate Content Pooling (Phases 1 & 2)
- Initially tried pooling Child controls separately in
ItemsControl._contentRecyclePool
- Problem: Still required visual tree detach/reattach → minimal performance gain
- Abandoned in favor of container-level approach
Alternative 2: IRecyclingDataTemplate.Build()
- Existing interface supports
Build(data, existing) parameter
- Problem: Only works when same template instance is reused (instance-equality constraint)
- Problem: Content cleared before it can be saved during container recycling
Alternative 3: Manual Control Reuse in User Code
- Developers could manually implement pooling logic in custom templates
- Problem: Complex, error-prone, and requires significant boilerplate
- Problem: Doesn't integrate with framework virtualization lifecycle
Additional context
Known Consideration: Current implementation has unlimited pool growth. The original MaxPoolSizePerKey property may need to be reintroduced at the container level to prevent unbounded memory usage on very long lists.
Testing: Successfully tested with 5000 heterogeneous items (4 different types with complex nested layouts) showing smooth scrolling and stable memory usage. Android performance improved from ~10 FPS (janky) to 60 FPS (smooth) with complex templates.
Additional features
- smooth scrolling in VirtualizedStackPanel even if the item-heights are heterogenous (no scroll jumping)
- warm-up logic, so VirtualizedStackPanel creates n containers when first rendered, so the likelihood that it needs to create a new one during scrolling is small.
We already implemented this.
What is missing:
- your API review
- unit tests (due to lack of time)
I am willing to deliver all of this and I can say that with complex layouts even on Windows this change makes a HUGE difference in scrolling performance.
But before I continue: Are you willing to accept this feature?
Content-Level Virtualization for Avalonia ListBox
Is your feature request related to a problem? Please describe.
Avalonia's
VirtualizingStackPanelcurrently only recycles containers (ListBoxItem/ContentPresenter) during scrolling, but destroys and recreates their content (the expensive UI created by DataTemplates).For complex templates with nested layouts (e.g., Border > StackPanel > multiple TextBlocks), this causes:
Example: A ListBox with 1000 items where each template creates 10-20 controls will destroy and recreate these controls repeatedly during scrolling, even though the template structure never changes. On Android, this makes the app nearly unusable for lists with complex templates.
Describe the solution you'd like
Implement type-aware container-level virtualization that keeps content attached to containers and pools them as a unit:
Key Features
Type-Aware Container Pooling
typeof(ProductItem)vstypeof(TaskItem))Content Preservation
DataContextchanges → bindings update efficientlyTwo Activation Modes
<DataTemplate DataType="..." EnableVirtualization="True">DataTypeset automatically benefit (zero code changes)Opt-In Design
Expected Performance Gains
Implementation Approach
The solution modifies
ItemsControl.NeedsContainerOverride()to return type-specific recycle keys andClearContainerForItemOverride()to conditionally skip content clearing when virtualization is active. This allowsVirtualizingStackPanelto maintain separate pools per data type while keeping the Child control attached.Describe alternatives you've considered
Alternative 1: Separate Content Pooling (Phases 1 & 2)
ItemsControl._contentRecyclePoolAlternative 2:
IRecyclingDataTemplate.Build()Build(data, existing)parameterAlternative 3: Manual Control Reuse in User Code
Additional context
Known Consideration: Current implementation has unlimited pool growth. The original
MaxPoolSizePerKeyproperty may need to be reintroduced at the container level to prevent unbounded memory usage on very long lists.Testing: Successfully tested with 5000 heterogeneous items (4 different types with complex nested layouts) showing smooth scrolling and stable memory usage. Android performance improved from ~10 FPS (janky) to 60 FPS (smooth) with complex templates.
Additional features
We already implemented this.
What is missing:
I am willing to deliver all of this and I can say that with complex layouts even on Windows this change makes a HUGE difference in scrolling performance.
But before I continue: Are you willing to accept this feature?