标题确实说了全部,我想做的就是获取视图宽度(一旦计算出宽度)并乘以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个图块。该演示是使用本文中的代码创建的(使用设备宽度而不是视图宽度)。
这将是很多代码,因为它是一个自定义视图,所以请您忍受,我将其放在帖子的末尾,以使问题更简短:
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>
使用我提供的所有信息,您应该可以自己构建此文件,以进行必要的测试。
答案 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>