Search code examples
listviewxamarin.formsheightsizetocontent

Xamarin.Forms ListView size to content?


I have a pretty large form (adapted mainly for tablets), that has a TabbedPage nesting a ScrollView and a vertical StackPanel containing many controls.

I have few occurrences where I have a ListView that contains a few single-line items, and I need it to size to content.
I'd like to get rid of its scroll-bars, but anyway I don't want it to take up more space than what's required for its items.
Is there a way (even an ugly one) to achieve that without have to write a renderer x3 platforms?

Here's a pseudo describing my tree:

<ContentPage>
  <MasterDetailPage>
    <MasterDetailPage.Detail>
      <TabbedPage>
        <ContentPage>
          <ScrollView>
            <StackPanel>
              <!-- many controls-->
              <ListView>

When rendered, there is a huge gap coming after the ListView. How can I avoid that?

I tried messing around with the VerticalOptions and HeightRequest, non of which worked.

I'm looking for a dynamic way (preferably without inheritance) to achieve that without involving custom renderers.


Solution

  • Based on Lutaaya's answer, I made a behavior that automates this, determining and setting the row-height (Gist).

    Behavior:

    namespace Xamarin.Forms
    {
      using System;
      using System.Linq;
      public class AutoSizeBehavior : Behavior<ListView>
      {
        ListView _ListView;
        ITemplatedItemsView<Cell> Cells => _ListView;
    
        protected override void OnAttachedTo(ListView bindable)
        {
          bindable.ItemAppearing += AppearanceChanged;
          bindable.ItemDisappearing += AppearanceChanged;
          _ListView = bindable;
        }
    
        protected override void OnDetachingFrom(ListView bindable)
        {
          bindable.ItemAppearing -= AppearanceChanged;
          bindable.ItemDisappearing -= AppearanceChanged;
          _ListView = null;
        }
    
        void AppearanceChanged(object sender, ItemVisibilityEventArgs e) =>
          UpdateHeight(e.Item);
    
        void UpdateHeight(object item)
        {
          if (_ListView.HasUnevenRows)
          {
            double height;
            if ((height = _ListView.HeightRequest) == 
                (double)VisualElement.HeightRequestProperty.DefaultValue)
              height = 0;
    
            height += MeasureRowHeight(item);
            SetHeight(height);
          }
          else if (_ListView.RowHeight == (int)ListView.RowHeightProperty.DefaultValue)
          {
            var height = MeasureRowHeight(item);
            _ListView.RowHeight = height;
            SetHeight(height);
          }
        }
    
        int MeasureRowHeight(object item)
        {
          var template = _ListView.ItemTemplate;
          var cell = (Cell)template.CreateContent();
          cell.BindingContext = item;
          var height = cell.RenderHeight;
          var mod = height % 1;
          if (mod > 0)
            height = height - mod + 1;
          return (int)height;
        }
    
        void SetHeight(double height)
        {
          //TODO if header or footer is string etc.
          if (_ListView.Header is VisualElement header)
            height += header.Height;
          if (_ListView.Footer is VisualElement footer)
            height += footer.Height;
          _ListView.HeightRequest = height;
        }
      }
    }
    

    Usage:

    <ContentPage xmlns:xf="clr-namespace:Xamarin.Forms">
      <ListView>
        <ListView.Behaviors>
          <xf:AutoSizeBehavior />