RepeaterView

Joshua.R.RussoJoshua.R.Russo USUniversity ✭✭
edited January 24 in Xamarin.Forms Evolution

Summary

A view similar to a ListView without baked in scrolling, or a TableView with an ItemsSource. The view should allow for an instance of itself to be created as a child element as well, for list within list functionality.

API Changes

I believe that the additional RepeaterView would have a subset of the ListView API. The repeater would not have any of the pull-to-refresh, context actions, nor selection functionality. There would also not be any baked in scrolling, behaving like the TableView in this regard.

This is what I have been using, with the only problem thus far being that I wasn't able to apply a tap gesture to an image. This problem probably applies to gestures in general

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using Xamarin.Forms;

public delegate void RepeaterViewItemAddedEventHandler(object sender, RepeaterViewItemAddedEventArgs args);

// in lieu of an actual Xamarin Forms ItemsControl, this is a heavily modified version of code from https://forums.xamarin.com/discussion/21635/xforms-needs-an-itemscontrol
public class RepeaterView : StackLayout
{
    public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
        "ItemSource", 
        typeof(IEnumerable),
        typeof(RepeaterView),
        new List<object>(),
        BindingMode.OneWay,
        propertyChanged: ItemsChanged);

    public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
        "ItemTemplate",
        typeof(DataTemplate),
        typeof(RepeaterView),
        default(DataTemplate));

    public event RepeaterViewItemAddedEventHandler ItemCreated;

    public IEnumerable ItemsSource
    {
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    public DataTemplate ItemTemplate
    {
        get { 
            return (DataTemplate)GetValue(ItemTemplateProperty); 
        }
        set { SetValue(ItemTemplateProperty, value); }
    }

    private static void ItemsChanged(BindableObject bindable, object oldValue, object newValue)
    {
        try
        {
            var control = (RepeaterView)bindable;
            var oldObservableCollection = oldValue as INotifyCollectionChanged;

            if (oldObservableCollection != null)
            {
                oldObservableCollection.CollectionChanged -= control.OnItemsSourceCollectionChanged;
            }

            var newObservableCollection = newValue as INotifyCollectionChanged;

            if (newObservableCollection != null)
            {
                newObservableCollection.CollectionChanged += control.OnItemsSourceCollectionChanged;
            }

            control.Children.Clear();

            if (newValue != null)
            {
                foreach (var item in (IEnumerable)newValue)
                {
                    var view = control.CreateChildViewFor(item);
                    control.Children.Add(view);
                    control.OnItemCreated(view);
                }
            }

            control.UpdateChildrenLayout();
            control.InvalidateLayout();
        }catch(Exception e){
            throw;
        }
    }

    protected virtual void OnItemCreated(View view) =>
    this.ItemCreated?.Invoke(this, new RepeaterViewItemAddedEventArgs(view, view.BindingContext));

    private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        var invalidate = false;

        if (e.OldItems != null)
        {
            this.Children.RemoveAt(e.OldStartingIndex);
            invalidate = true;
        }

        if (e.NewItems != null)
        {
            for (var i = 0; i < e.NewItems.Count; ++i)
            {
                var item = e.NewItems[i];
                var view = this.CreateChildViewFor(item);

                this.Children.Insert(i + e.NewStartingIndex, view);
                OnItemCreated(view);
            }

            invalidate = true;
        }

        if (invalidate)
        {
            this.UpdateChildrenLayout();
            this.InvalidateLayout();
        }
    }

    private View CreateChildViewFor(object item)
    {
        this.ItemTemplate.SetValue(BindableObject.BindingContextProperty, item);
        return (View)this.ItemTemplate.CreateContent();
    }
}

public class RepeaterViewItemAddedEventArgs : EventArgs
{
    private readonly View view;
    private readonly object model;

    public RepeaterViewItemAddedEventArgs(View view, object model)
    {
        this.view = view;
        this.model = model;
    }

    public View View => this.view;

    public object Model => this.model;
}

This is what I would like to do but the gesture recognizer is not working

      <controls:RepeaterView ItemsSource="{Binding InnlightenObject.Alerts}">
        <controls:RepeaterView.ItemTemplate>
          <DataTemplate>
            <ContentView Padding="20,10,20,10"
                         BackgroundColor="{Binding AgeColor}">
                <Grid>
                  <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                  </Grid.ColumnDefinitions>
                  <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                  </Grid.RowDefinitions>

                  <Label Grid.Row="0" Grid.Column="0"
                         Text="{Binding Text}"
                         TextColor="White"
                         FontAttributes="Bold"
                         FontSize="Medium" />

                  <ContentView Grid.Row="0" Grid.Column="1">
                    <Image Source="GreenStar.png"
                            WidthRequest="40"
                            HeightRequest="40"
                            IsVisible="{Binding Acknowledged}">
                      <Image.GestureRecognizers>
                        <TapGestureRecognizer
                            Command="{Binding UnacknowledgeEventCommand}"
                            CommandParameter="{Binding .}" />
                      </Image.GestureRecognizers>
                    </Image>

                    <Image Source="GreyStar.png"
                           WidthRequest="40"
                           HeightRequest="40"
                           IsVisible="{Binding Unacknowledged}">
                      <Image.GestureRecognizers>
                        <TapGestureRecognizer
                            Command="{Binding AcknowledgeEventCommand}"
                            CommandParameter="{Binding .}" />
                      </Image.GestureRecognizers>
                    </Image>
                  </ContentView>

                </Grid>

            </ContentView>
          </DataTemplate>
        </controls:RepeaterView.ItemTemplate>
      </controls:RepeaterView>

Intended Use Case

This is useful in highly data driven apps. When using an MVVM pattern the RepeaterView pattern is preferable over the TableView because manually adding items to a table view requires code in the view, which the MVVM pattern tries to avoid.

0
0 votes

Open · Last Updated

Posts

  • MarkRadcliffeMarkRadcliffe NZMember ✭✭✭

    I assume this would be like a bindable stackpanel with a datatemplate selector? how would you expect to use this?

    I currently do this for a dynamic grid layout for menu controls for people with fat fingers where I put large squares with icons and text as menu tiles and handle touch input (colouring on touch etc).

  • StephaneDelcroixStephaneDelcroix USInsider, Beta ✭✭✭✭

    Please refine your proposal.
    Propose an API for your repeaterview, explain, or draw, what it should look like, and give hints on how you think they should be implemented on each platform...

  • Joshua.R.RussoJoshua.R.Russo USUniversity ✭✭

    @StephaneDelcroix Is there anything more or different that you would like to see from what I added?

  • Joshua.R.RussoJoshua.R.Russo USUniversity ✭✭

    @MarkRadcliffe How is what your doing different that what you see from my control and example?

  • Joshua.R.RussoJoshua.R.Russo USUniversity ✭✭
    edited January 24

    I think I just realized why my bindings aren't working. I'm trying to bind to the parent binding context in the item binding context

    I added the commands to the item and assigned the parent context commands to the item commands but the parent action methods are not being triggered

  • Joshua.R.RussoJoshua.R.Russo USUniversity ✭✭
    edited January 24

    Ok I have my gesture recognizing working correctly now. In addition to initially trying to reference the command which was in the wrong context, I had the wrong class type referenced in the command.

    I still believe that this control would be helpful for people coming into the Xamarin Forms environment, or at very least offer it as a recipe in the Forms guides documentation

  • MarkRadcliffeMarkRadcliffe NZMember ✭✭✭

    @Joshua.R.Russo said:
    @MarkRadcliffe How is what your doing different that what you see from my control and example?

    I hadn't seen your control example by that stage, but looks like that is what you currently do. Just keep in mind that your gesture recognizer may not work if you put this repeater view in a scroll view or in some other types of content control. The way I got around that (my menu also can be in a scrollview) is I made a "smart scroll view" that passes gesutres using interfaces if a click occurs it finds the control in that space and sends the on press down and up, tapped etc actions to it through to any control that handles that interface. Making it quite easy to create presses/touches on anything in that view through a basic subclassing of the type. Do you expect this control to scroll or work in a scrollview??

  • JoshuaRussoJoshuaRusso USMember ✭✭

    @MarkRadcliffe Thank you so much for your insight. I had not reached that cross roads yet. Are you referring to using the MessageCenter and subscribing to a particular interface that way?

  • MarkRadcliffeMarkRadcliffe NZMember ✭✭✭

    @JoshuaRusso Not quite, I created a renderer for a "Smart Render", I did this from both a page and a scrollview. They are able to find the object that is being "pressed" based on the x and y coordinates and send those events to the child control if it implements IPressHander (just a basic interface). So you subclass the controls your want to have these events come through for on your repeater view. I was using box views as my buttons in my menu so I wanted them to change color on touch. Note in the scroll view you take the X and Y of the page but also the X and Y of the scroll point together to find the correct control.

    [assembly: ExportRenderer(typeof(ContentPage), typeof(SmartPageRenderer))]
    [assembly: ExportRenderer(typeof(ScrollView), typeof(SmartScrollViewRenderer))]
    namespace BlackhawkPlatform.App.iOS.Renderers
    {
    
        public class SmartPageRenderer : PageRenderer
        {
            private IPressedHandler _last;
    
            public override void TouchesBegan(NSSet touches, UIEvent evt)
            {
                base.TouchesBegan(touches, evt);
                var touch = touches.AnyObject as UITouch;
    
                if (touch != null)
                {
                    var point = touch.LocationInView(this.View);
                    var target = GetTouchedBox(point.X, point.Y, ((ContentPage)this.Element).Content);
    
                    target?.OnPressedStart();
    
                    _last = target;
                }
            }
    
            public override void TouchesMoved(NSSet touches, UIEvent evt)
            {
                base.TouchesMoved(touches, evt);
                var touch = touches.AnyObject as UITouch;
    
                if (touch != null && _last != null)
                {
                    var point = touch.LocationInView(this.View);
                    var target = GetTouchedBox(point.X, point.Y, ((ContentPage)this.Element).Content);
    
                    if (target != _last)
                    {
                        _last?.OnPressedCancelled();
    
                        _last = null;
                    }
                }
            }
    
            public override void TouchesCancelled(NSSet touches, UIEvent evt)
            {
                base.TouchesCancelled(touches, evt);
                var touch = touches.AnyObject as UITouch;
    
                if (touch != null)
                {
                    _last?.OnPressedCancelled();
    
                    _last = null;
                }
            }
    
            public override void TouchesEnded(NSSet touches, UIEvent evt)
            {
                base.TouchesEnded(touches, evt);
                var touch = touches.AnyObject as UITouch;
    
                if (touch != null)
                {
                    _last?.OnPressedEnd();
    
                    _last = null;
                }
            }
    
            private IPressedHandler GetTouchedBox(double touchX, double touchY, VisualElement element)
            {
                var layoutElement = element as Layout<View>;
    
                if (layoutElement == null)
                    return null;
    
                var x = touchX - layoutElement.X;
                var y = touchY - layoutElement.Y;
    
                // Find which view/control, if any, the pointer is over
                foreach (var view in layoutElement.Children.Where(c => c is IPressedHandler))
                {
                    var child = view;
                    if (child.Bounds.Contains(x, y))
                    {
                        return child as IPressedHandler;
                    }
                }
    
                foreach (var view in layoutElement.Children.Where(c => c is Layout<View>))
                {
                    var layoutChild = (Layout<View>) view;
                    var subChild = GetTouchedBox(touchX, touchY, layoutChild);
    
                    if (subChild != null)
                        return subChild;
                }
    
    
                // At this point, target == null means the pointer isn't
                // over a view/control; target != null means it is
                return null;
            }
        }
    
        public class SmartScrollViewRenderer : ScrollViewRenderer
        {
            private IPressedHandler _last;
    
            public override void TouchesBegan(NSSet touches, UIEvent evt)
            {
                base.TouchesBegan(touches, evt);
                var touch = touches.AnyObject as UITouch;
    
                if (touch != null)
                {
                    var point = touch.LocationInView(this);
    
                    var target = GetTouchedBox(point.X, point.Y, ((ScrollView)this.Element).Content);
    
                    target?.OnPressedStart();
    
                    _last = target;
                }
            }
    
            public override void TouchesMoved(NSSet touches, UIEvent evt)
            {
                base.TouchesMoved(touches, evt);
                var touch = touches.AnyObject as UITouch;
    
                if (touch != null && _last != null)
                {
                    var point = touch.LocationInView(this);
    
                    var target = GetTouchedBox(point.X, point.Y, ((ScrollView)this.Element).Content);
    
                    if (target != _last)
                    {
                        _last?.OnPressedCancelled();
    
                        _last = null;
                    }
                }
            }
    
            public override void TouchesCancelled(NSSet touches, UIEvent evt)
            {
                base.TouchesCancelled(touches, evt);
                var touch = touches.AnyObject as UITouch;
    
                if (touch != null)
                {
                    _last?.OnPressedCancelled();
    
                    _last = null;
                }
            }
    
            public override void TouchesEnded(NSSet touches, UIEvent evt)
            {
                base.TouchesEnded(touches, evt);
                var touch = touches.AnyObject as UITouch;
    
                if (touch != null)
                {
                    _last?.OnPressedEnd();
    
                    _last = null;
                }
            }
    
            private IPressedHandler GetTouchedBox(double touchX, double touchY, VisualElement element)
            {
                var layoutElement = element as Layout<View>;
    
                if (layoutElement == null)
                    return null;
    
                var x = touchX - layoutElement.X;
                var y = touchY - layoutElement.Y;
    
                // Find which View/Control, if any, the pointer is over
                foreach (var view in layoutElement.Children.Where(c => c is IPressedHandler))
                {
                    var child = view;
                    if (child.Bounds.Contains(x, y))
                    {
                        return child as IPressedHandler;
                    }
                }
    
                foreach (var view in layoutElement.Children.Where(c => c is Layout<View>))
                {
                    var layoutChild = (Layout<View>)view;
                    var subChild = GetTouchedBox(touchX, touchY, layoutChild);
    
                    if (subChild != null)
                        return subChild;
                }
    
                // At this point, target == null means the pointer isn't
                // over a View/Control; target != null means it is
                return null;
            }
        }
    
  • JoshuaRussoJoshuaRusso USMember ✭✭

    Interesting, would this also be required on the Android side of things?

  • MarkRadcliffeMarkRadcliffe NZMember ✭✭✭

    Yea, you do one renderer per platform, I have done one for each platform. there isn't much difference between them.

    i.e you swap out the override functions in iOS with this for smart page

    public override bool DispatchTouchEvent(MotionEvent e)
            {
                if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel)
                {
                    _last?.OnPressedEnd();
    
                    _last = null;
                }
                else if (e.Action == MotionEventActions.Down)
                {
                    var x = e.GetX()/Resources.DisplayMetrics.Density;
                    var y = e.GetY()/Resources.DisplayMetrics.Density;
                    var target = GetTouchedBox(x, y, ((ContentPage)Element).Content);
    
                    target?.OnPressedStart();
    
                    _last = target;
                }
                else if (e.Action == MotionEventActions.Move)
                {
                    var x = e.GetX() / Resources.DisplayMetrics.Density;
                    var y = e.GetY() / Resources.DisplayMetrics.Density;
                    var target = GetTouchedBox(x, y, ((ContentPage)Element).Content);
    
                    if (target != _last)
                    {
                        _last?.OnPressedEnd();
    
                        _last = null;
                    }
                }
                return base.DispatchTouchEvent(e);
            }
    

    And this for SmartScrollView

    public override bool DispatchTouchEvent(MotionEvent e)
            {
                if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel)
                {
                    _last?.OnPressedEnd();
    
                    _last = null;
                }
                else if (e.Action == MotionEventActions.Down)
                {
                    var x = e.GetX() / Resources.DisplayMetrics.Density;
                    var y = e.GetY() / Resources.DisplayMetrics.Density;
    
                    var scrollView = (ScrollView) Element;
    
                    var target = GetTouchedBox(x + scrollView.ScrollX, y + scrollView.ScrollY, scrollView.Content);
    
                    target?.OnPressedStart();
    
                    _last = target;
                }
                else if (e.Action == MotionEventActions.Move)
                {
                    var x = e.GetX() / Resources.DisplayMetrics.Density;
                    var y = e.GetY() / Resources.DisplayMetrics.Density;
    
                    var scrollView = (ScrollView)Element;
    
                    var target = GetTouchedBox(x + scrollView.ScrollX, y + scrollView.ScrollY, scrollView.Content);
    
                    if (target != _last)
                    {
                        _last?.OnPressedEnd();
    
                        _last = null;
                    }
                }
                return base.DispatchTouchEvent(e);
            }
    

    This is getting off topic now though..

  • MarkRadcliffeMarkRadcliffe NZMember ✭✭✭

    FYI on the android one you would want to seperate out the end and cancel to their respective functions in the interface, I had no need to so was lazy.

  • JoshuaRussoJoshuaRusso USMember ✭✭

    I'm not sure how off topic this really is. I would expect that a repeater would need to be allowable in a ScrollView. That just seems like something people would expect from an out-of-the-box control, and even if this is added as a recipe in the documentation scroll-ability is something that needs to be spoken to

  • MarkRadcliffeMarkRadcliffe NZMember ✭✭✭

    Well the repeater would work in a scrollview anyway, but touch interactions and gestures become a bit harder as touch in a scrollview should mostly be for scrolling the view when it is a touch screen. same reason buttons dont really work in list views etc.

  • JoshuaRussoJoshuaRusso USMember ✭✭

    Is it possible that the touch interactions are not a concern anymore? I just created a contrived example with my image containing a tap gesture and made sure it repeated enough to scroll the content. The tap still worked without adding any special renderer

  • AdamPAdamP AUUniversity ✭✭✭✭✭

    I have created a similar control before, however it required a column property, as I was using it more like a bindable Grid. But considering its incredibly close to this proposal, it might be worth adding. However a control that handles both might be an issue since StackLayout and Grid use different layout calculation methods. Unsure of performance implication of extending from either.

  • JoshuaRussoJoshuaRusso USMember ✭✭
    edited January 25

    I love the idea of a bindable Grid! They're both are valid in different contexts. I've been using grid's within my StackLayout based repeater and it works, but I have contemplated making a bindable Grid as well. Unless I'm missing something I can't really see why there would be a performance hit. The output is generated when the page is built and then done. You have to be aware of the lack of virtualization, but the utility for short lists far outweighs the dangers of a developer trying to stuff too much into one.

  • VelocityVelocity NZMember ✭✭✭
    edited January 25

    @JoshuaRusso We have a BindableStackLayout and a BindableGrid which we have developed and use internally in our applications. All are built in PCL and do everything you are asking for above with no renderers required.

    Let me know if you are interested. Might be looking at open sourcing some of these controls in the future so they can be improved by the wider community.

  • Joshua.R.RussoJoshua.R.Russo USUniversity ✭✭

    @Velocity Absolutely. I'm always interested in how others have solved interesting problems

  • MarkRadcliffeMarkRadcliffe NZMember ✭✭✭

    @JoshuaRusso said:
    Is it possible that the touch interactions are not a concern anymore? I just created a contrived example with my image containing a tap gesture and made sure it repeated enough to scroll the content. The tap still worked without adding any special renderer

    Certain guestures work like the tap guesture. in the end it depends what type of input you are wanting. I.e if you want press down to change color and press up to trigger/change color back etc. The problem I guess stems from the lack of built in guesture support.

  • rmarinhormarinho PTMember, Insider, Beta Xamurai

    LEt's call it ItemsControl ?

  • Joshua.R.RussoJoshua.R.Russo USUniversity ✭✭

    @rmarinho Sounds good to me. I would just love to see it, what ever it ends up being called. This control is the linchpin to my app. The detail of the my objects are filled with lists of alternate IPs, passwords, contacts from email addresses to phone numbers and addresses.

  • JensDemeyJensDemey USMember ✭✭

    I have also made a very similar control (because sometimes I just don't need all the ListView extra's). It seems like there are quite a few developers who could make use of an ItemsControl.

  • MichaelRidlandMichaelRidland AUInsider, University ✭✭✭

    ItemsControl, RepeaterView, CollectionView, WrapLayout.

    Are people thinking of a control similar to the UICollectionView / RecyclerView? Or would this be different?

  • MichaelRidlandMichaelRidland AUInsider, University ✭✭✭
  • VelocityVelocity NZMember ✭✭✭

    @JoshuaRusso @MichaelRidland @rmarinho
    Please see our BindableStackLayout control which is in its simplest form a bindable version of StackLayout.

    We have used this extensively where there is a need for a simple, no-frills bindable StackLayout.

    • Bindable properties for ItemTemplate and ItemsSource.
    • Auto-rebind/repopulate when data template or items source is changed.
    • Supports a standard Xamarin.Forms DataTemplate or DataTemplateSelector.
    • Works inside a ScrollView^.
    • Portable control, no renderers required.

    ^Note: BindableStackLayout is not a replacement for a virtualized layout such as a ListView or a collection view.
    It is designed to work as a simple repeater control for specific UI applications.

    We are happy to contribute this to the Xamarin.Forms Evolution initiative.

  • Joshua.R.RussoJoshua.R.Russo USUniversity ✭✭

    @MichaelRidland It all boils down to a view that's bindable to a list of objects, similar to a ListView without the baked in scrolling of the ListView. This allows for the display of lists within lists, multiple lists on a page, and lists easily mixed with non-list based views.

  • DanSiegelDanSiegel USUniversity ✭✭

    I would say @MichaelRidland is pointing at the type of control that is badly needed in XF. While I realize we're talking a native iOS control the functionality of the UICollectionView is awesome from being able to scroll horizontally, vertically or not at all, having 1:n columns/rows. If you add that with DataTemplates like we're already used to for the ListView so you can create a custom feel per cell based on the binding context for the cell... and of course make it work on every platform... you'd have a lot of happy developers.

  • VelocityVelocity NZMember ✭✭✭

    @DanSiegel @MichaelRidland Good suggestions, I have proposed this as a separate idea here.
    Let's move any further discussion of a virtualized collection view into the new thread.

  • Joshua.R.RussoJoshua.R.Russo USUniversity ✭✭
    While Forms should fully support the native controls as much as possible, there's a gap in functionality with the native controls that could be filled. Lists within lists are a powerful and intuitive concept for highly data driven apps. Virtualization is not always required and doesn't always make sense if you have a collection of small lists.
  • VelocityVelocity NZMember ✭✭✭
    edited March 13
    > @Joshua.R.Russo said:
    > While Forms should fully support the native controls as much as possible, there's a gap in functionality with the native controls that could be filled. Lists within lists are a powerful and intuitive concept for highly data driven apps. Virtualization is not always required and doesn't always make sense if you have a collection of small lists.

    Exactly, which is the very reason we designed BindableStackLayout.
    Virtualization is not always required and this simply provides bindable capability to the already great StackLayout.
  • Joshua.R.RussoJoshua.R.Russo USUniversity ✭✭

    @Velocity said:

    @Joshua.R.Russo said:
    While Forms should fully support the native controls as much as possible, there's a gap in functionality with the native controls that could be filled. Lists within lists are a powerful and intuitive concept for highly data driven apps. Virtualization is not always required and doesn't always make sense if you have a collection of small lists.


    Exactly, which is the very reason we designed BindableStackLayout (link above). Virtualization is not always required and this simply provides bindable capability to the already great StackLayout.

    Ok gotcha. I understand the intention of your post now and I completely agree

  • VelocityVelocity NZMember ✭✭✭

    @rmarinho OK looks like we have a good idea of what this control aka "ItemsControl"needs to do.
    Happy to volunteer a PR based on our BindableStackLayout if this feature request is considered as approved.

    This control is built entirely in PCL and is a simple, solid extension to StackLayout that offers a bindable item source and templating. No hacks or renderers are required for gesture recognisers to work either! See above for more details.

  • predalphapredalpha FRMember ✭✭

    Hello,

    the lack of this control is really annoying as it is essential in most of app.
    So few months ago i decided to implement that kind of control.

    But recently ive definitively switched to https://github.com/muak/AiForms.Layouts as im not comforable with layouts calculations.
    The author makes a good job and his controls answer a lot of dilemmas.

    Probably this control could be part of Xamarin forms controls as Xamarin team did with AlexRainman CarouselView.

    This is only a suggestion that exposes existing controls already available on Nuget.

  • AdamPAdamP AUUniversity ✭✭✭✭✭

    Another project, another need for this exact control.

    To avoid anything over complicated, we just need a bindable StackLayout. No columns, or cell recycling or anything fancy. Large lists are always better in a ListView anyway.

    Just a way to repeat a small number of items, but data bound.

    The original proposal of StackLayout, with an ItemsSource and ItemTemplate property is great for most scenarios, and incredibly easy to implement. It doesn't even need a platform specific implementation and no breaking changes.

  • VelocityVelocity NZMember ✭✭✭
    edited October 26
    > @AdamP said:
    > Another project, another need for this exact control.
    >
    > To avoid anything over complicated, we just need a bindable StackLayout. No columns, or cell recycling or anything fancy. Large lists are always better in a ListView anyway.
    >
    > Just a way to repeat a small number of items, but data bound.
    >
    > The original proposal of StackLayout, with an ItemsSource and ItemTemplate property is great for most scenarios, and incredibly easy to implement. It doesn't even need a platform specific implementation and no breaking changes.

    https://github.com/velocitysystems/xf-controls/blob/master/XF.Controls/XF.Controls/Layouts/BindableStackLayout.cs
  • AdamPAdamP AUUniversity ✭✭✭✭✭
    edited October 26

    thanks @Velocity. I've already created it, many times over, with each project, but just thought it would be handy to actually put in the XF framework.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited October 26

    I don't think you clearly defined what the problem is.

    Based on my experience a good reason why sometimes ListView is not what I need is because I don't want items to be selectable. I just want a control which stacks items. Another reason might be ListView is kind of heavy from a performance point of view.

    Intended Use Case

    This is useful in highly data driven apps. When using an MVVM pattern the RepeaterView pattern is preferable over the TableView because manually adding items to a table view requires code in the view, which the MVVM pattern tries to avoid.

    TableView has at least two inconveniences: 1) it doesn't have ItemsSource bindable and 2) it doesn't have an ItemTemplate property.

    I think it was really designed for static items, like settings or a menu, albeit there are many scenarios where items are not really that static, for example when you display the app settings. you want a setting to appear only if user checked another setting. This is a UI concern and it should be resolved in the Page code behind, without bindable items.

    In Windows XAML we use ItemsControl for this purpose. So I guess that's why many people suggested an ItemsControl. It would be really great to have it. But, since it's called ItemsControl I really want it follow same pattern like in Windows XAML. It should support using a Layout template (in Windows XAML it's Panel) to layout the items, by having a ItemsLayout property (similar to the ItemsPanel property in Windows XAML). This opens a lot of very cool possibilities. Anyone who worked enough with Windows XAML technologies knows what I'm talking about.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭
    edited October 26

    One "issue" (not an issue maybe, but just something to think about) I see with having an ItemsControl is the way it actually renders on the native platform.
    To understand what I mean, let's take the example of the existing StackLayout. The StackLayout doesn't have a renderer (it does, but it's a "generic" one), the layout of the items is implemented right in Xamarin Forms. It doesn't have a renderer which uses a UIStackView on iOS , or a LinearLayout on Android, or a StackLayout on UWP, or a NSStackView on MacOS. Today all platforms have a control which stacks items.
    I wonder, from the speed point of view, how would the layout process done by Xamarin Forms compare to using the native stack controls? Is it negligible?

Sign In or Register to comment.