gpt4 book ai didi

c# - View 根据 child 数量扩展自己的宽度

转载 作者:行者123 更新时间:2023-11-30 12:55:02 26 4
gpt4 key购买 nike

问题

标题说明了一切,我想做的是获取 View 宽度(一旦计算出来)并将其乘以 x 以创建图 block 的“页面”。然后它将位于 scrollview 中,因此我们可以左右导航。

为此,我使用了一个名为 WrapLayout 的自定义 View ,它为我完成了大部分工作。我修改了它,尝试用我自己计算的宽度覆盖它的宽度 (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));
}

所以我使用覆盖将 View 的大小设置为 deviceWidth * pageCount,除了使用设备宽度不是我想要的之外,它应该使用自己计算的宽度以便此 View 可以在不拉伸(stretch)整个设备宽度的情况下使用。

在 Xamarin 计算出 View 的宽度并覆盖该值以将其替换为我自己的值,从而使 View 变大 x 倍后,我如何获取 View 的宽度?

我知道这是一个大问题,所以如果我遗漏了任何关键信息,请随时发表评论。

注意:如果对此有很好的回答,我会在可能的时候悬赏。这让我发疯!

预期结果演示

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

Demo


代码

这将是一个自定义 View ,因此会有很多代码,所以请耐心等待,我将把它放在帖子的末尾以缩短问题:

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>

使用我提供的所有内容,您应该能够自己构建它以在需要时进行测试。

最佳答案

我现在找到了一些解决方法,它似乎工作得很好,所以将使用这个版本进行测试。

我在 wraplayout 上设置了一个新属性以接受其父级宽度,因为在这种情况下,父级是 ScrollView ,我们使用该 View 宽度并将其传递给 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 现在看起来像这样将父级宽度绑定(bind)到我们新创建的属性。

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

关于c# - View 根据 child 数量扩展自己的宽度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51875694/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com