cmeeren
cmeeren

Reputation: 4210

How to align ListView items to bottom of screen when list has few items?

I'd like to have a Xamarin.Forms ListView that starts showing its elements from the bottom up, so that elements are displayed at the bottom of the ListView when there are few items. See the image for explanation. Is this possible to do without having to set the HeightRequest of the ListView manually? (And if setting the HeightRequest manually is required, how can I compute the height of the ListView's child elements when I'm using HasUnevenRows=true?)

Here's how I want it to look (few items to the left, many items to the right);

Desired behavior for few items to the left; many items to the right.

For reference, here is the way ListView normally works when there are few items:

Current behavior for few items.

Upvotes: 3

Views: 1678

Answers (2)

Sharada
Sharada

Reputation: 13601

If ListView is not mandatory, and you just need a repeatable control with templating support - you can use ItemsControl. As it uses StackLayout as its panel, it only expands to its required height. So, you don't need to compute it's height. Please do note ItemsControl has very basic virtualization behavior (recycling) - so if you have a lot of items then you will have to modify the control to support virtualization too.

Secondly, you can extend Grid to automatically set the second RowDefinition to Auto or Star as per bottom content size. i.e. by default keep it as Auto, but if the content size is greater than 50% of viewport height - then reset RowDefinition to Star. For eg, you can create a custom SplitterGrid as:

public class SplitterGrid : Grid
{
    public SplitterGrid()
    {
        RowDefinitions = new RowDefinitionCollection() 
        { 
            new RowDefinition { Height = GridLength.Star }, 
            new RowDefinition { Height = GridLength.Auto }
        };
    }

    void Content2_SizeChanged(object sender, EventArgs e)
    {
        if (Height == 0 && Width == 0)
            return;

        Content2.SizeChanged -= Content2_SizeChanged;
        if (Content2.Height > Height / 2)
            RowDefinitions[1].Height = GridLength.Star;
        else
            RowDefinitions[1].Height = GridLength.Auto;
    }

    public static readonly BindableProperty Content1Property =
        BindableProperty.Create(
        "Content1", typeof(View), typeof(SplitterGrid),
        defaultValue: null, propertyChanged: OnContent1Changed);

    public View Content1
    {
        get { return (View)GetValue(Content1Property); }
        set { SetValue(Content1Property, value); }
    }

    private static void OnContent1Changed(BindableObject bindable, object oldValue, object newValue)
    {
        ((SplitterGrid)bindable).OnContent1ChangedImpl((View)oldValue, (View)newValue);
    }

    void OnContent1ChangedImpl(View oldValue, View newValue)
    {
        if (oldValue == null)
            Children.Add(Content1);
    }

    public static readonly BindableProperty Content2Property =
        BindableProperty.Create(
        "Content2", typeof(View), typeof(SplitterGrid),
        defaultValue: null, propertyChanged: OnContent2Changed);

    public View Content2
    {
        get { return (View)GetValue(Content2Property); }
        set { SetValue(Content2Property, value); }
    }

    private static void OnContent2Changed(BindableObject bindable, object oldValue, object newValue)
    {
        ((SplitterGrid)bindable).OnContent2ChangedImpl((View)oldValue, (View)newValue);
    }

    void OnContent2ChangedImpl(View oldValue, View newValue)
    {
        if (oldValue == null)
        {
            Children.Add(Content2);
            Content2.SizeChanged += Content2_SizeChanged;
            Grid.SetRow(Content2, 1);
        }
        else 
            Content2.SizeChanged -= Content2_SizeChanged;
    }
}

And, usage would look like:

<local:SplitterGrid Margin="0,20,0,0">
    <local:SplitterGrid.Content1>
        <ContentView Padding="10" BackgroundColor="#E4E4E4">
            <Label Text="This page contains about 3 items." />
        </ContentView>
    </local:SplitterGrid.Content1>
    <local:SplitterGrid.Content2>
        <ScrollView VerticalOptions="End">
            <local:ItemsControl>
                <local:ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Label FontAttributes="Bold" FontSize="40" Text="{Binding .}" />
                    </DataTemplate>
                </local:ItemsControl.ItemTemplate>
                <local:ItemsControl.ItemsSource>
                    <x:Array Type="{x:Type x:String}">
                        <x:String>Item 1</x:String>
                        <x:String>Item 2</x:String>
                        <x:String>Item 3</x:String>
                    </x:Array>
                </local:ItemsControl.ItemsSource>
            </local:ItemsControl>
        </ScrollView>
    </local:SplitterGrid.Content2>
</local:SplitterGrid>

enter image description here

Upvotes: 3

Pierre P.
Pierre P.

Reputation: 888

I would go with a grid where two rows are defined with one set to "*" or the Height you want in pixels and the bottom one to "Auto" with the listview VerticalOptions set to "End".

Upvotes: 0

Related Questions