C# 视图根据子项计数扩展其自身的宽度 问题
标题说明了一切,我想做的是得到视图的宽度(一旦它被计算出来)并乘以x来创建“页面”的平铺。然后,它将位于C# 视图根据子项计数扩展其自身的宽度 问题,c#,xamarin,xamarin.forms,C#,Xamarin,Xamarin.forms,标题说明了一切,我想做的是得到视图的宽度(一旦它被计算出来)并乘以x来创建“页面”的平铺。然后,它将位于滚动视图中,因此我们可以左右导航 为此,我使用了一个名为WrapLayout的自定义视图,这为我完成了大部分工作。我修改了它,试图用我自己计算的宽度覆盖它的宽度(width*pagecount) 我关注的主要方法是OnMeasure,我相信这是针对这种情况进行覆盖的正确方法 protected override SizeRequest OnMeasure(double widthConstra
滚动视图中
,因此我们可以左右导航
为此,我使用了一个名为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个平铺。这个演示是使用本文中的代码创建的(使用设备宽度,而不是视图宽度)
代码 这将是大量的代码,因为这是一个自定义视图,所以请容忍我,我将把这篇文章放在文章的末尾,以缩短问题: 可重复播放
公共类RepeatableWrapLayout:WrapLayoutSimple
{
public static BindableProperty ItemsSourceProperty=BindableProperty.Create(nameof(ItemsSource)、typeof(IEnumerable)、typeof(RepeatableWrapLayout)、null、defaultBindingMode:BindingMode.OneWay、propertyChanged:itemshanged);
公共IEnumerable ItemsSource
{
得到
{
返回(IEnumerable)GetValue(ItemsSourceProperty);
}
设置
{
设置值(ItemsSourceProperty,value);
}
}
public static BindableProperty ItemTemplateProperty=BindableProperty.Create(nameof(ItemTemplate)、typeof(DataTemplate)、typeof(RepeatableWrapLayout)、default(DataTemplate)、propertyChanged:(bindable、oldValue、newValue)=>
{
变量控制=(RepeatableWrapLayout)可绑定;
//当发生属性更改早于ItemTemplate的ItemsSource时,请手动提高ItemsChanged
if(newValue!=null&&control.ItemsSource!=null&&control.doneItemSourceChanged)
{
ItemsChanged(可绑定、空、control.ItemsSource);
}
}
);
公共数据模板ItemTemplate
{
得到
{
返回(DataTemplate)GetValue(ItemTemplateProperty);
}
设置
{
SetValue(ItemTemplateProperty,值);
}
}
public static BindableProperty ItemTapCommandProperty=BindableProperty.Create(nameof(ItemTapCommand)、typeof(ICommand)、typeof(RepeatableWrapLayout)、default(ICommand)、defaultBindingMode:BindingMode.OneWay、propertyChanged:ItemTapCommandChanged);
///
///在点击某个项目时调用的命令。
///
公共ICommand ItemTapCommand
{
得到
{
返回(ICommand)GetValue(ItemTapCommandProperty);
}
设置
{
SetValue(ItemTapCommandProperty,值);
}
}
private bool doneItemSourceChanged=false;
私有静态void ItemTapCommandChanged(BindableObject bindable、object oldValue、object newValue)
{
变量控制=(RepeatableWrapLayout)可绑定;
if(oldValue!=newValue&&control.ItemsSource!=null)
{
更新命令(控制);
}
}
私有静态void ItemsChanged(BindableObject bindable、object oldValue、object newValue)
{
变量控制=(RepeatableWrapLayout)可绑定;
//当发生propertychanged早于ItemsSource而不是ItemTemplate时,不执行任何操作。
if(control.ItemTemplate==null)
{
control.doneItemSourceChanged=false;
返回;
}
control.doneItemSourceChanged=true;
IEnumerable新值可计算;
尝试
{
newValueAsEnumerable=作为IEnumerable的newValue;
}
捕获(例外e)
{
投掷e;
}
var oldObservableCollection=InotifyCollection更改时的oldValue;
if(oldObservableCollection!=null)
{
oldObservableCollection.CollectionChanged-=control.OnItemSourceCollectionChanged;
}
var newObservableCollection=InotifyCollection更改时的新值;
if(newobserveCollection!=null)
{
newObservableCollection.CollectionChanged+=control.OnItemSourceCollectionChanged;
}
control.Children.Clear();
if(newValueAsEnumerable!=null)
{
foreach(NewValuesEnumerable中的var项)
{
var view=CreateChildViewFor(control.ItemTemplate,item,control);
控件.Children.Add(视图);
}
}
if(control.ItemTapCommand!=null)
{
更新命令(控制);
}
control.UpdateChildrenLayout();
控件。InvalidateLayout();
}
私有静态void UpdateCommand(RepeatableWrapLayout控件)
{
foreach(control.Children中的var视图)
{
添加(新的TapGestureRecognizer{Command=control.ItemTapCommand,CommandParameter=view.BindingContext,});
}
}
私有资源集合已更改(对象发送方,NotifyCollect
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();
}
}
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();
}
}
<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>
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); }
}
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;
}
}
<Controls:RepeatableWrapLayout ParentWidth="{Binding Source={x:Reference Name=parentScrollView} ,Path=Width}" ...>
...
</Controls:RepeatableWrapLayout>