Skip to content

VirtualizingDataTemplate - Content-Level Virtualization for virtualized ItemsControls #20259

@gentledepp

Description

@gentledepp

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

  1. 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
  2. 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
  3. Two Activation Modes

    • Explicit: <DataTemplate DataType="..." EnableVirtualization="True">
    • Automatic: Templates with DataType set automatically benefit (zero code changes)
  4. 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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions