dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.23k stars 1.76k forks source link

VariableSpanGridItemsLayout for CollectionView (GridItemsLayout w/ a Span that varies) #3803

Open billvenhaus opened 2 years ago

billvenhaus commented 2 years ago

Description

Add a new ItemsLayout to the CollectionView that is similar to the GridItemsLayout but instead has a span that varies based on the size of the control. Rather than having item sizes that grow & shrink, the span will grow & shrink. My proposed name for this new layout is VariableSpanGridItemsLayout. I have already developed implementations for this on Android, iOS, & Windows. They can be found in my ReorderableCollectionView library.

Example https://github.com/billvenhaus/ReorderableCollectionView.Maui

variablespangriditemslayout_windows

For my ReorderableCollectionView library I also give users the option to customize the spans for specific items. However, with this, I can only provide the ability to customize the column span for an item, not the row span. This is do to limitations of Android's GridLayoutManager & SpanSizeLookup only having the ability to change the column. If we wish to support both column & row customizations we'd likely have to use a custom LayoutManager like shown here.

public class SpanLookup : ISpanLookup
{
    public int GetColumnSpan(object item)
    {
        if (item is Monkey monkey && monkey.Name == "Blue Monkey")
        {
            return 2;
        }
        return 1;
    }
}

itemspanlookup_android

Public API Changes

Instead of assigning a Span value like the GridItemsLayout, the user will be required to assign an ItemWidth & ItemHeight.

Like the GridItemsLayout, the user will have the option to assign spacing.

<CollectionView ItemsSource="{Binding Monkeys}">
    <CollectionView.ItemsLayout>
        <VariableSpanGridItemsLayout ItemWidth="170" ItemHeight="80" VerticalItemSpacing="5" HorizontalItemSpacing="5" />
    </CollectionView.ItemsLayout>
</CollectionView>
public class VariableSpanGridItemsLayout : ItemsLayout
{
    public static readonly BindableProperty ItemWidthProperty;
    public double ItemWidth { get; set; }

    public static readonly BindableProperty ItemHeightProperty;
    public double ItemHeight { get; set; }

    public static readonly BindableProperty VerticalItemSpacingProperty;
    public double VerticalItemSpacing { get; set; }

    public static readonly BindableProperty HorizontalItemSpacingProperty;
    public double HorizontalItemSpacing { get; set; }

    // OPTIONAL - The default span for each item is 1. 
    public static readonly BindableProperty ItemSpanSizeLookupProperty;
    public IItemSpanLookup ItemSpanLookup { get; set; }
}

Additionally, if the user chooses, they can assign a custom ItemSpanLookup to the layout. When this property is null, the handlers will assume an item span of 1.

// OPTIONAL 
public interface IItemSpanLookup
{
    int GetColumnSpan(object item);
}

Intended Use-Case

The GridItemsLayout is nice however there are definitely times when it'd help to have a more adaptive layout that can pack as many items onto the screen as possible. The big beneficiary to this are resizable Windows applications. However mobile platforms stand to benefit as well with the ability to automatically adjust when the device rotates.

Notes

On Windows, the VariableSizedWrapGrid does not support virtualization so it should only be used when needed. If the user has not supplied a custom IItemSpanLookup, the platform handler should use the standard ItemsWrapGrid which does support virtualization.

mjfara commented 2 years ago

+1 Honestly surprised this is currently missing

pekspro commented 2 years ago

I’m also looking for something like this. I’m currently have 8 different CollectionViews (all the same, but different amounts of columns), but just one visible at once on one single page. :-(

flynn248 commented 2 years ago

I’m also looking for something like this. I’m currently have 8 different CollectionViews (all the same, but different amounts of columns), but just one visible at once on one single page. :-(

@pekspro. Could you set a bindable property to the Span attribute for one of the CollectionViews? The idea is to update this bound property when a change in columns is desired. I'm unsure if it would work in your case or not, but it's an idea that might be able to knock out a few of those CollectionViews.

pekspro commented 2 years ago

@flynn248, I have tried using Span as a bindable property but it did not make any change. It was continuing the original number of spans and ignored the new value.

In some cases, I guess a FlexLayout with a BindableLayout should be a decent workaround in some cases:

https://learn.microsoft.com/en-us/dotnet/maui/user-interface/layouts/bindablelayout

I just gave it a quick test, but it just thrown me a strange Exception that I don't have time to dig into.

flynn248 commented 2 years ago

@pekspro. That's odd that it didn't work for you. I tried it out yesterday and got it to work with a small sample app. I can upload the repro if you'd like to take a look at it.

I've not done anything with BindableLayouts, so I can't help there.

pekspro commented 2 years ago

Yes, @flynn248, I would appreciate a repro very much :-) I tried it again today with version 6.0.541, but it was no difference. In short, this is how I do it:


<CollectionView 
    ItemsSource="{Binding GroupedItems}"
    IsGrouped="True"
    >
    <CollectionView.ItemsLayout>
        <GridItemsLayout 
            Orientation="Vertical"
            Span="{Binding ColumnCount}"
            VerticalItemSpacing="8"
            HorizontalItemSpacing="4" />
    </CollectionView.ItemsLayout>
flynn248 commented 2 years ago

@pekspro. Sounds good. I've added a sample repro that has examples of a non-grouped vertical and horizontal grid CollectionView as well as a grouped CollectionView that is has the span that is data bound.

I did use the Community.Toolkit.MVVM to do the leg work with creating commands and handling the implementation of INotifyPropertyChanged, so that is perhaps one difference to keep in mind.

As for the Maui version, I'm not entirely sure where to check that.

Let me know if you're able to get the repro to work :).

pekspro commented 2 years ago

Thanks, @flynn248, I do appreciate your work! I have used your sample application and have figured out what is wrong. Binding to Span does work, but only on Android :-( In Windows it is stuck on the first value. This is true also for your application.

When I find the time, I will write a proper bug report :-)

ghost commented 1 year ago

We've added this issue to our backlog, and we will work to address it as time and resources allow. If you have any additional information or questions about this issue, please leave a comment. For additional info about issue management, please read our Triage Process.