根据孩子人数扩大视图的宽度

时间:2018-08-16 11:13:09

标签: c# xamarin xamarin.forms

问题

标题确实说了全部,我想做的就是获取视图宽度(一旦计算出宽度)并乘以x来创建图块的“页面”。然后它将位于scrollview内,以便我们可以左右导航。

为此,我正在使用名为WrapLayout的自定义视图,这将为我完成大部分工作。我已经对其进行了修改,以尝试用我自己计算的宽度(width * pagecount覆盖其宽度。

我关注的主要方法是OnMeasure,我认为这是针对情况进行覆盖的正确方法。

protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
    var pageCount = (Children.Count / (Rows * 3)) + (Children.Count % (Rows * 3) > 0 ? 1 : 0);
    if (HeightRequest > 0)
        heightConstraint = Math.Min(heightConstraint, HeightRequest);
    double internalHeight = double.IsPositiveInfinity(heightConstraint) ? double.PositiveInfinity : Math.Max(0, heightConstraint);
    if (double.IsPositiveInfinity(widthConstraint) && double.IsPositiveInfinity(heightConstraint))
    {
        return new SizeRequest(Size.Zero, Size.Zero);
    }

    var deviceWidth = Application.Current.MainPage.Width;
    return new SizeRequest(new Size(deviceWidth * pageCount, internalHeight));
}

因此,我正在使用覆盖将视图的大小调整为deviceWidth * pageCount,这与使用设备宽度不是我想要的宽度不同,它应该使用自己计算的宽度,因此可以在不拉伸的情况下使用此视图设备的整个宽度。

在Xamarin解决后,如何获取视图的宽度并覆盖该值以将其替换为我自己的值,以使视图变为原来的x倍?

我知道这是一个大问题,因此如果我错过任何关键信息,请随时发表评论。

注意:如果对此有一个好的答案,我将在有能力的情况下提供赏金。这让我发疯了!

预期结果演示

按照注释中的要求。您可以看到图块已正确缩放,并且视图已扩展其宽度,以允许每个视图6个图块。该演示是使用本文中的代码创建的(使用设备宽度而不是视图宽度)。

Demo


代码

这将是很多代码,因为它是一个自定义视图,所以请您忍受,我将其放在帖子的末尾,以使问题更简短:

RepeatableWrapLayout

public class RepeatableWrapLayout : WrapLayoutSimple
{
    public static BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof (IEnumerable), typeof (RepeatableWrapLayout), null, defaultBindingMode: BindingMode.OneWay, propertyChanged: ItemsChanged);
    public IEnumerable ItemsSource
    {
        get
        {
            return (IEnumerable)GetValue(ItemsSourceProperty);
        }

        set
        {
            SetValue(ItemsSourceProperty, value);
        }
    }

    public static BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof (DataTemplate), typeof (RepeatableWrapLayout), default (DataTemplate), propertyChanged: (bindable, oldValue, newValue) =>
    {
        var control = (RepeatableWrapLayout)bindable;
        //when to occur propertychanged earlier ItemsSource than ItemTemplate, raise ItemsChanged manually
        if (newValue != null && control.ItemsSource != null && !control.doneItemSourceChanged)
        {
            ItemsChanged(bindable, null, control.ItemsSource);
        }
    }

    );
    public DataTemplate ItemTemplate
    {
        get
        {
            return (DataTemplate)GetValue(ItemTemplateProperty);
        }

        set
        {
            SetValue(ItemTemplateProperty, value);
        }
    }

    public static BindableProperty ItemTapCommandProperty = BindableProperty.Create(nameof(ItemTapCommand), typeof (ICommand), typeof (RepeatableWrapLayout), default (ICommand), defaultBindingMode: BindingMode.OneWay, propertyChanged: ItemTapCommandChanged);
    /// <summary>
    /// Command invoked when it tapped a item.
    /// </summary>
    public ICommand ItemTapCommand
    {
        get
        {
            return (ICommand)GetValue(ItemTapCommandProperty);
        }

        set
        {
            SetValue(ItemTapCommandProperty, value);
        }
    }

    private bool doneItemSourceChanged = false;
    private static void ItemTapCommandChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var control = (RepeatableWrapLayout)bindable;
        if (oldValue != newValue && control.ItemsSource != null)
        {
            UpdateCommand(control);
        }
    }

    private static void ItemsChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var control = (RepeatableWrapLayout)bindable;
        // when to occur propertychanged earlier ItemsSource than ItemTemplate, do nothing.
        if (control.ItemTemplate == null)
        {
            control.doneItemSourceChanged = false;
            return;
        }

        control.doneItemSourceChanged = true;
        IEnumerable newValueAsEnumerable;
        try
        {
            newValueAsEnumerable = newValue as IEnumerable;
        }
        catch (Exception e)
        {
            throw e;
        }

        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 (newValueAsEnumerable != null)
        {
            foreach (var item in newValueAsEnumerable)
            {
                var view = CreateChildViewFor(control.ItemTemplate, item, control);
                control.Children.Add(view);
            }
        }

        if (control.ItemTapCommand != null)
        {
            UpdateCommand(control);
        }

        control.UpdateChildrenLayout();
        control.InvalidateLayout();
    }

    private static void UpdateCommand(RepeatableWrapLayout control)
    {
        foreach (var view in control.Children)
        {
            view.GestureRecognizers.Add(new TapGestureRecognizer{Command = control.ItemTapCommand, CommandParameter = view.BindingContext, });
        }
    }

    private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        var invalidate = false;
        if (e.Action == NotifyCollectionChangedAction.Replace)
        {
            this.Children.RemoveAt(e.OldStartingIndex);
            var item = e.NewItems[e.NewStartingIndex];
            var view = CreateChildViewFor(this.ItemTemplate, item, this);
            if (ItemTapCommand != null)
            {
                view.GestureRecognizers.Add(new TapGestureRecognizer{Command = ItemTapCommand, CommandParameter = item, });
            }

            this.Children.Insert(e.NewStartingIndex, view);
        }
        else if (e.Action == NotifyCollectionChangedAction.Add)
        {
            if (e.NewItems != null)
            {
                for (var i = 0; i < e.NewItems.Count; ++i)
                {
                    var item = e.NewItems[i];
                    var view = CreateChildViewFor(this.ItemTemplate, item, this);
                    if (ItemTapCommand != null)
                    {
                        view.GestureRecognizers.Add(new TapGestureRecognizer{Command = ItemTapCommand, CommandParameter = item, });
                    }

                    this.Children.Insert(i + e.NewStartingIndex, view);
                }
            }
        }
        else if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            if (e.OldItems != null)
            {
                this.Children.RemoveAt(e.OldStartingIndex);
            }
        }
        else if (e.Action == NotifyCollectionChangedAction.Reset)
        {
            this.Children.Clear();
        }
        else
        {
            return;
        }

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

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

    private static View CreateChildViewFor(DataTemplate template, object item, BindableObject container)
    {
        var selector = template as DataTemplateSelector;
        if (selector != null)
        {
            template = selector.SelectTemplate(item, container);
        }

        //Binding context
        template.SetValue(BindableObject.BindingContextProperty, item);
        return (View)template.CreateContent();
    }
}

WrapLayoutSimple

public class WrapLayoutSimple : Layout<View>
{
    Dictionary<Size, LayoutData> layoutDataCache = new Dictionary<Size, LayoutData>();
#region Props
    public static readonly BindableProperty RowsProperty = BindableProperty.Create("Rows", typeof (int), typeof (WrapLayout), 2, propertyChanged: (bindable, oldvalue, newvalue) =>
    {
    //((WrapTestLayout)bindable).InvalidateLayout();
    }

    );
    public static readonly BindableProperty ColumnsProperty = BindableProperty.Create("Columns", typeof (int), typeof (WrapLayout), 3, propertyChanged: (bindable, oldvalue, newvalue) =>
    {
    // ((WrapTestLayout)bindable).InvalidateLayout();
    }

    );
    public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create("ColumnSpacing", typeof (double), typeof (WrapLayout), 0.00, propertyChanged: (bindable, oldvalue, newvalue) =>
    {
    //((WrapTestLayout)bindable).InvalidateLayout();
    }

    );
    public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create("RowSpacing", typeof (double), typeof (WrapLayout), 0.00, propertyChanged: (bindable, oldvalue, newvalue) =>
    {
    //((WrapTestLayout)bindable).InvalidateLayout();
    }

    );
    public static readonly BindableProperty PagePaddingProperty = BindableProperty.Create("RowSpacing", typeof (Thickness), typeof (WrapLayout), new Thickness(0), propertyChanged: (bindable, oldvalue, newvalue) =>
    {
    //((WrapTestLayout)bindable).InvalidateLayout();
    }

    );
    public double ColumnSpacing
    {
        set
        {
            SetValue(ColumnSpacingProperty, value);
        }

        get
        {
            return (double)GetValue(ColumnSpacingProperty);
        }
    }

    public double RowSpacing
    {
        set
        {
            SetValue(RowSpacingProperty, value);
        }

        get
        {
            return (double)GetValue(RowSpacingProperty);
        }
    }

    public int Rows
    {
        set
        {
            SetValue(RowsProperty, value);
        }

        get
        {
            return (int)GetValue(RowsProperty);
        }
    }

    public int Columns
    {
        set
        {
            SetValue(ColumnsProperty, value);
        }

        get
        {
            return (int)GetValue(ColumnsProperty);
        }
    }

    public Thickness PagePadding
    {
        set
        {
            SetValue(PagePaddingProperty, value);
        }

        get
        {
            return (Thickness)GetValue(PagePaddingProperty);
        }
    }

#endregion
    public WrapLayoutSimple()
    {
        HorizontalOptions = LayoutOptions.FillAndExpand;
    }

    protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
    {
        var pageCount = (Children.Count / (Rows * 3)) + (Children.Count % (Rows * 3) > 0 ? 1 : 0);
        if (HeightRequest > 0)
            heightConstraint = Math.Min(heightConstraint, HeightRequest);
        double internalHeight = double.IsPositiveInfinity(heightConstraint) ? double.PositiveInfinity : Math.Max(0, heightConstraint);
        if (double.IsPositiveInfinity(widthConstraint) && double.IsPositiveInfinity(heightConstraint))
        {
            return new SizeRequest(Size.Zero, Size.Zero);
        }

        var deviceWidth = Application.Current.MainPage.Width;
        return new SizeRequest(new Size(deviceWidth * pageCount, internalHeight));
    }

    protected override void LayoutChildren(double x, double y, double width, double height)
    {
        var PageCount = (Children.Count / (Rows * 3)) + (Children.Count % (Rows * 3) > 0 ? 1 : 0);
        var pageWidth = width / PageCount;
        LayoutData layoutData = GetLayoutData(pageWidth, height);
        if (layoutData.VisibleChildCount == 0)
        {
            return;
        }

        double xChild = x;
        double yChild = y;
        int row = 0;
        int column = 0;
        int count = 0;
        int page = 0;
        int itemsPerPage = Rows * 3;
        foreach (View child in Children)
        {
            if (!child.IsVisible)
            {
                continue;
            }

            // New page
            if (count % itemsPerPage == 0 & count != 0)
            {
                // Add a page on
                page++;
                // Reset the Y so we start from the top again
                yChild = y;
            }

            count++;
            // A check for a guff width, if not use the good stuff.
            // Width * page will get it to the right width
            double xLocation;
            if (Double.IsInfinity(pageWidth))
                xLocation = 0;
            else
                xLocation = (pageWidth * page);
            LayoutChildIntoBoundingRegion(child, new Rectangle(new Point(xChild + xLocation, yChild), layoutData.CellSize));
            Debug.WriteLine("Adding child x: {0} y: {1} page: {2}", xChild + xLocation, yChild, page);
            // Reset for Second row if we hit our col limit
            if (++column == layoutData.Columns)
            {
                // Reset col
                column = 0;
                // Add row
                row++;
                // Reset the x so we start fromt he x start again (start of new row)
                xChild = x;
                // Add the height ready for the next placement (a row down)
                yChild += layoutData.CellSize.Height;
            }
            else
            {
                // Add the width ready for the next placement
                xChild += layoutData.CellSize.Width;
            }
        }
    }

    LayoutData GetLayoutData(double width, double height)
    {
        Debug.WriteLine("Page Width: " + width);
        Size size = new Size(width, height);
        // Check if cached information is available.
        if (layoutDataCache.ContainsKey(size))
        {
            return layoutDataCache[size];
        }

        int visibleChildCount = 0;
        Size maxChildSize = new Size();
        LayoutData layoutData = new LayoutData();
        // Enumerate through all the children.
        foreach (View child in Children)
        {
            // Skip invisible children.
            if (!child.IsVisible)
                continue;
            // Count the visible children.
            visibleChildCount++;
            // Get the child's requested size.
            SizeRequest childSizeRequest = child.Measure(Double.PositiveInfinity, Double.PositiveInfinity);
            // Accumulate the maximum child size.
            maxChildSize.Width = Math.Max(maxChildSize.Width, childSizeRequest.Request.Width);
            maxChildSize.Height = Math.Max(maxChildSize.Height, childSizeRequest.Request.Height);
        }

        if (visibleChildCount != 0)
        {
            // Now maximize the cell size based on the layout size.
            Size cellSize = new Size();
            if (Double.IsPositiveInfinity(width))
            {
                cellSize.Width = maxChildSize.Width;
            }
            else
            {
                cellSize.Width = width / Columns;
            }

            if (Double.IsPositiveInfinity(height))
            {
                cellSize.Height = maxChildSize.Height;
            }
            else
            {
                cellSize.Height = height / Rows;
            }

            layoutData = new LayoutData(visibleChildCount, cellSize, Rows, Columns);
        }

        layoutDataCache.Add(size, layoutData);
        Debug.WriteLine("Cell Width: " + layoutData.CellSize.Width + " Height: " + layoutData.CellSize.Height);
        return layoutData;
    }

    protected override void InvalidateLayout()
    {
        base.InvalidateLayout();
        // Discard all layout information for children added or removed.
        layoutDataCache.Clear();
    }

    protected override void OnChildMeasureInvalidated()
    {
        base.OnChildMeasureInvalidated();
        // Discard all layout information for child size changed.
        layoutDataCache.Clear();
    }
}

XAML

<l:RepeatableWrapLayout
                x:Name="rwl"
                HorizontalOptions="Fill" VerticalOptions="FillAndExpand" >
    <l:RepeatableWrapLayout.ItemTemplate>
        <DataTemplate>
            <StackLayout BackgroundColor="{Binding Color}">
                <Label VerticalTextAlignment="Center" HorizontalTextAlignment="Center"
                                Text="{Binding Name}" />
            </StackLayout>
        </DataTemplate>
    </l:RepeatableWrapLayout.ItemTemplate>
</l:RepeatableWrapLayout>

使用我提供的所有信息,您应该可以自己构建此文件,以进行必要的测试。

1 个答案:

答案 0 :(得分:0)

我现在已经找到了一些解决方法,它似乎工作得很好,因此将使用此版本进行测试。

我在wraplayout上设置了一个新属性以接受其父级宽度,因为在这种情况下,父级是滚动视图,因此我们使用该视图宽度并将其传递给wraplayout

public static readonly BindableProperty ParentWidthProperty = BindableProperty.Create("ParentWidth",
    typeof(double),
    typeof(WrapLayout),
    0.00,
    propertyChanged: (bindable, oldvalue, newvalue) =>
    {
        ((RepeatableWrapLayout)bindable).SetNewWidth();
    });

public double ParentWidth
{
    set { SetValue(ParentWidthProperty, value); }
    get { return (double)GetValue(ParentWidthProperty); }
}

使用此方法,我们可以在属性更改时触发事件,并设置wraplayout的宽度。我在这里进行了一些检查,以防止它在需要之前停止进行猛虎袭击。

double oldParentWidth;
public void SetNewWidth()
{
    var pageCount = (Children.Count / (Rows * 3)) + (Children.Count % (Rows * 3) > 0 ? 1 : 0);

    if (ParentWidth > 0 && ParentWidth != oldParentWidth)
    {
        oldParentWidth = ParentWidth;
        WidthRequest = ParentWidth * pageCount;
    }
}

XAML现在看起来像这样,将父级宽度绑定到我们新创建的属性。

<Controls:RepeatableWrapLayout ParentWidth="{Binding Source={x:Reference Name=parentScrollView} ,Path=Width}" ...>
   ...
</Controls:RepeatableWrapLayout>