[Flex] correctly measure images (#2010)

use Aspect.Fill to measure images. also make sure we do not crash if the
Bounds are smaller than the request. Like any other control, we will
draw outside of our bounds, and rely on ClipToBounds for a nice shave.
This commit is contained in:
Stephane Delcroix 2018-03-06 10:50:07 +01:00 коммит произвёл GitHub
Родитель c3eec10c86
Коммит 081eb37f8b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 67 добавлений и 82 удалений

Просмотреть файл

@ -241,7 +241,7 @@ namespace Xamarin.Flex
IList<Item> Children { get; set; }
bool ShouldOrderChildren { get; set; }
///<summary>This property defines how the layout engine will distribute space between and around child items that have been laid out on multiple lines. This property is ignored if the root item does not have its<see cref="P:Xamarin.Flex.Item.Wrap" /> property set to Wrap or WrapReverse.</summary>
///<summary>This property defines how the layout engine will distribute space between and around child items that have been laid out on multiple lines. This property is ignored if the root item does not have its <see cref="P:Xamarin.Flex.Item.Wrap" /> property set to Wrap or WrapReverse.</summary>
///<remarks>The default value for this property is Stretch.</remarks>
/// <value>The content of the align.</value>
public AlignContent AlignContent { get; set; } = AlignContent.Stretch;
@ -339,8 +339,8 @@ namespace Xamarin.Flex
/// <value>The bottom edge padding space.Negative values are not allowed.</value>
public float PaddingTop { get; set; } = 0f;
/// <summary>This property defines whether the item should be positioned by the flexbox rules of the layout engine(Relative) or have an absolute fixed position (Absolute). If this property is set to Absolute, the<see cref="P:Xamarin.Flex.Item.Left" />, <see cref = "P:Xamarin.Flex.Item.Right" />, <see cref = "P:Xamarin.Flex.Item.Top" /> and < see cref= "P:Xamarin.Flex.Item.Bottom" /> properties will then be used to determine the item's fixed position in its container.</summary>
/// <value>Any value part of the<see cref="T:Xamarin.Flex.Position" /> enumeration.</value>
/// <summary>This property defines whether the item should be positioned by the flexbox rules of the layout engine(Relative) or have an absolute fixed position (Absolute). If this property is set to Absolute, the <see cref="P:Xamarin.Flex.Item.Left" />, <see cref = "P:Xamarin.Flex.Item.Right" />, <see cref = "P:Xamarin.Flex.Item.Top" /> and <see cref= "P:Xamarin.Flex.Item.Bottom" /> properties will then be used to determine the item's fixed position in its container.</summary>
/// <value>Any value part of the <see cref="T:Xamarin.Flex.Position" /> enumeration.</value>
/// <remarks>The default value for this property is Relative</remarks>
public Position Position { get; set; } = Position.Relative;
@ -388,8 +388,8 @@ namespace Xamarin.Flex
}
/// <param name="child">The child item to be added.</param>
/// <summary>Adds a given child item to the current item.</summary>
/// <exception cref = "ArgumentException" > Thrown if the child has already been added to another item.</exception>
/// <summary>Adds a given child item to the current item.</summary>
/// <exception cref = "ArgumentException" > Thrown if the child has already been added to another item.</exception>
/// <param name="child">Child.</param>
public void Add(Item child)
{
@ -399,10 +399,10 @@ namespace Xamarin.Flex
ShouldOrderChildren |= child.Order != 0;
}
public void InsertAt(uint index, Item child)
public void InsertAt(int index, Item child)
{
ValidateChild(child);
(Children ?? (Children = new List<Item>())).Insert((int)index, child);
(Children ?? (Children = new List<Item>())).Insert(index, child);
child.Parent = this;
ShouldOrderChildren |= child.Order != 0;
}
@ -415,13 +415,13 @@ namespace Xamarin.Flex
return child;
}
public uint Count =>
(uint)(Children?.Count ?? 0);
public int Count =>
(Children?.Count ?? 0);
public Item ItemAt(uint index) =>
Children?[(int)index];
public Item ItemAt(int index) =>
Children?[index];
public Item this[uint index] {
public Item this[int index] {
get => ItemAt(index);
}
@ -434,9 +434,9 @@ namespace Xamarin.Flex
}
}
/// <summary>Determines the frames of each child(included nested ones) based on the flexbox rules that were applied on this item and the children themselves.After this method is called, the<see cref="P:Xamarin.Flex.Item.FrameX" />, <see cref = "P:Xamarin.Flex.Item.FrameY" />, <see cref = "P:Xamarin.Flex.Item.FrameWidth" /> and < see cref= "P:Xamarin.Flex.Item.FrameHeight" /> properties can be accessed on child items.</summary>
/// <remarks>This method must be called on a root (without parent) item where the <see cref = "P:Xamarin.Flex.Item.Width" /> and < see cref= "P:Xamarin.Flex.Item.Height" /> properties have also been set.</remarks>
/// <exception cref = "InvalidOperationException" > Thrown if the item has a parent (must be root) or if the item does not have a proper value set for < see cref = "P:Xamarin.Flex.Item.Width" /> and < see cref = "P:Xamarin.Flex.Item.Height" />.</ exception >
/// <summary>Determines the frames of each child(included nested ones) based on the flexbox rules that were applied on this item and the children themselves.After this method is called, the <see cref="P:Xamarin.Flex.Item.FrameX" />, <see cref = "P:Xamarin.Flex.Item.FrameY" />, <see cref = "P:Xamarin.Flex.Item.FrameWidth" /> and <see cref= "P:Xamarin.Flex.Item.FrameHeight" /> properties can be accessed on child items.</summary>
/// <remarks>This method must be called on a root (without parent) item where the <see cref = "P:Xamarin.Flex.Item.Width" /> and <see cref= "P:Xamarin.Flex.Item.Height" /> properties have also been set.</remarks>
/// <exception cref = "InvalidOperationException" > Thrown if the item has a parent (must be root) or if the item does not have a proper value set for <see cref = "P:Xamarin.Flex.Item.Width" /> and <see cref = "P:Xamarin.Flex.Item.Height" />.</exception>
public void Layout()
{
if (Parent != null)
@ -448,14 +448,6 @@ namespace Xamarin.Flex
layout_item(this, Width, Height);
}
public float Padding {
set => PaddingTop = PaddingLeft = PaddingRight = PaddingBottom = value;
}
public float Margin {
set => MarginTop = MarginLeft = MarginRight = MarginBottom = value;
}
public delegate void SelfSizingDelegate(Item item, ref float width, ref float height);
public SelfSizingDelegate SelfSizing { get; set; }
@ -483,22 +475,17 @@ namespace Xamarin.Flex
layout.init(item, width, height);
layout.reset();
uint last_layout_child = 0;
uint relative_children_count = 0;
for (uint i = 0; i < item.Count; i++) {
Item child = layout.child_at(item, i);
int last_layout_child = 0;
int relative_children_count = 0;
for (int i = 0; i < item.Count; i++) {
var child = layout.child_at(item, i);
// Items with an absolute position have their frames determined
// directly and are skipped during layout.
if (child.Position == Position.Absolute) {
float child_width = absolute_size(child.Width, child.Left, child.Right, width);
float child_height = absolute_size(child.Height, child.Top, child.Bottom, height);
float child_x = absolute_pos(child.Left, child.Right, child_width, width);
float child_y = absolute_pos(child.Top, child.Bottom, child_height, height);
child.Frame[0] = child_x;
child.Frame[1] = child_y;
child.Frame[2] = child_width;
child.Frame[3] = child_height;
child.Frame[2] = absolute_size(child.Width, child.Left, child.Right, width);
child.Frame[3] = absolute_size(child.Height, child.Top, child.Bottom, height);
child.Frame[0] = absolute_pos(child.Left, child.Right, child.Frame[2], width);
child.Frame[1] = absolute_pos(child.Top, child.Bottom, child.Frame[3], height);
// Now that the item has a frame, we can layout its children.
layout_item(child, child.Frame[2], child.Frame[3]);
@ -512,20 +499,16 @@ namespace Xamarin.Flex
child.Frame[3] = child.Height;
// Main axis size defaults to 0.
if (float.IsNaN(child.Frame[layout.frame_size_i])) {
if (float.IsNaN(child.Frame[layout.frame_size_i]))
child.Frame[layout.frame_size_i] = 0;
}
// Cross axis size defaults to the parent's size (or line size in wrap
// mode, which is calculated later on).
if (float.IsNaN(child.Frame[layout.frame_size2_i])) {
if (layout.wrap) {
if (layout.wrap)
layout.need_lines = true;
}
else {
child.Frame[layout.frame_size2_i] = (layout.vertical ? width : height)
- child.MarginThickness(!layout.vertical);
}
else
child.Frame[layout.frame_size2_i] = (layout.vertical ? width : height) - child.MarginThickness(!layout.vertical);
}
// Call the self_sizing callback if provided. Only non-NAN values
@ -536,15 +519,13 @@ namespace Xamarin.Flex
child.SelfSizing(child, ref size[0], ref size[1]);
for (uint j = 0; j < 2; j++) {
uint size_off = j + 2;
if (size_off == layout.frame_size2_i && child_align(child, item) == AlignItems.Stretch) {
for (int j = 0; j < 2; j++) {
int size_off = j + 2;
if (size_off == layout.frame_size2_i && child_align(child, item) == AlignItems.Stretch)
continue;
}
float val = size[j];
if (!float.IsNaN(val)) {
if (!float.IsNaN(val))
child.Frame[size_off] = val;
}
}
}
@ -623,7 +604,7 @@ namespace Xamarin.Flex
// Re-position the children of this line, honoring any child
// alignment previously set within the line.
for (uint j = line.child_begin; j < line.child_end; j++) {
for (int j = line.child_begin; j < line.child_end; j++) {
Item child = layout.child_at(item, j);
if (child.Position == Position.Absolute) {
// Should not be re-positioned.
@ -650,14 +631,10 @@ namespace Xamarin.Flex
layout.cleanup();
}
float MarginThickness(bool vertical)
{
if (vertical)
return MarginTop + MarginBottom;
return MarginLeft + MarginRight;
}
float MarginThickness(bool vertical) =>
vertical ? MarginTop + MarginBottom : MarginLeft + MarginRight;
static void layout_align(Justify align, float flex_dim, uint children_count, ref float pos_p, ref float spacing_p)
static void layout_align(Justify align, float flex_dim, int children_count, ref float pos_p, ref float spacing_p)
{
if (flex_dim < 0)
throw new ArgumentException();
@ -734,7 +711,7 @@ namespace Xamarin.Flex
}
}
static void layout_items(Item item, uint child_begin, uint child_end, uint children_count, ref flex_layout layout)
static void layout_items(Item item, int child_begin, int child_end, int children_count, ref flex_layout layout)
{
if (children_count > (child_end - child_begin))
throw new ArgumentException();
@ -767,7 +744,7 @@ namespace Xamarin.Flex
layout.pos2 -= layout.line_dim;
}
for (uint i = child_begin; i < child_end; i++) {
for (int i = child_begin; i < child_end; i++) {
Item child = layout.child_at(item, i);
if (child.Position == Position.Absolute) {
@ -880,7 +857,7 @@ namespace Xamarin.Flex
public uint frame_pos2_i; // cross axis position
public uint frame_size_i; // main axis size
public uint frame_size2_i; // cross axis size
uint[] ordered_indices;
int[] ordered_indices;
// Set for each line layout.
public float line_dim; // the cross axis size
@ -896,8 +873,8 @@ namespace Xamarin.Flex
public bool need_lines;
public struct flex_layout_line
{
public uint child_begin;
public uint child_end;
public int child_begin;
public int child_end;
public float size;
};
@ -923,11 +900,8 @@ namespace Xamarin.Flex
|| item.PaddingBottom < 0)
throw new ArgumentException();
width -= item.PaddingLeft + item.PaddingRight;
height -= item.PaddingTop + item.PaddingBottom;
if ( width < 0
|| height < 0)
throw new ArgumentException();
width = Math.Max(0, width - item.PaddingLeft + item.PaddingRight);
height = Math.Max(0, height - item.PaddingTop + item.PaddingBottom);
reverse = item.Direction == Direction.RowReverse || item.Direction == Direction.ColumnReverse;
vertical = true;
@ -955,18 +929,18 @@ namespace Xamarin.Flex
ordered_indices = null;
if (item.ShouldOrderChildren && item.Count > 0) {
var indices = new uint[item.Count];
var indices = new int[item.Count];
// Creating a list of item indices sorted using the children's `order'
// attribute values. We are using a simple insertion sort as we need
// stability (insertion order must be preserved) and cross-platform
// support. We should eventually switch to merge sort (or something
// else) if the number of items becomes significant enough.
for (uint i = 0; i < item.Count; i++) {
for (int i = 0; i < item.Count; i++) {
indices[i] = i;
for (uint j = i; j > 0; j--) {
uint prev = indices[j - 1];
uint curr = indices[j];
if (item.Children[(int)prev].Order <= item.Children[(int)curr].Order) {
for (int j = i; j > 0; j--) {
int prev = indices[j - 1];
int curr = indices[j];
if (item.Children[prev].Order <= item.Children[curr].Order) {
break;
}
indices[j - 1] = curr;
@ -997,8 +971,8 @@ namespace Xamarin.Flex
lines_sizes = 0;
}
public Item child_at(Item item, uint i) =>
item.Children[(int)(ordered_indices?[i] ?? i)];
public Item child_at(Item item, int i) =>
item.Children[(ordered_indices?[i] ?? i)];
public void cleanup()
{

Просмотреть файл

@ -281,13 +281,15 @@ namespace Xamarin.Forms
if (!(view is FlexLayout)) { //inner layouts don't get measured
item.SelfSizing = (Flex.Item it, ref float w, ref float h) => {
var sizeConstrains = item.GetConstraints();
sizeConstrains.Width = (_measuring && sizeConstrains.Width == 0) ? double.PositiveInfinity : sizeConstrains.Width;
sizeConstrains.Height = (_measuring && sizeConstrains.Height == 0) ? double.PositiveInfinity : sizeConstrains.Height;
var request = view.Measure(sizeConstrains.Width, sizeConstrains.Height).Request;
w = (float)request.Width;
h = (float)request.Height;
};
}
_root.InsertAt((uint)Children.IndexOf(view), item);
_root.InsertAt(Children.IndexOf(view), item);
SetFlexItem(view, item);
}
@ -344,6 +346,8 @@ namespace Xamarin.Forms
if (e.PropertyName == MarginProperty.PropertyName) {
var item = (sender as FlexLayout)?._root ?? GetFlexItem((BindableObject)sender);
if (item == null)
return;
var margin = (Thickness)((View)sender).GetValue(MarginProperty);
item.MarginLeft = (float)margin.Left;
item.MarginTop = (float)margin.Top;
@ -356,6 +360,8 @@ namespace Xamarin.Forms
if (e.PropertyName == PaddingProperty.PropertyName) {
var item = (sender as FlexLayout)?._root ?? GetFlexItem((BindableObject)sender);
if (item == null)
return;
var padding = (Thickness)((View)sender).GetValue(PaddingProperty);
item.PaddingLeft = (float)padding.Left;
item.PaddingTop = (float)padding.Top;
@ -391,14 +397,18 @@ namespace Xamarin.Forms
}
}
bool _measuring;
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
//All of this is a HACK as X.Flex doesn't supports measuring
if (!double.IsPositiveInfinity(widthConstraint) && !double.IsPositiveInfinity(heightConstraint))
return new SizeRequest(new Size(widthConstraint, heightConstraint));
_measuring = true;
//1. Set Shrink to 0, set align-self to start (to avoid stretching)
foreach (var item in _root) {
// Set Image.Aspect to Fill to get the value we expect in measuring
foreach (var child in Children) {
var item = GetFlexItem(child);
item.Shrink = 0;
item.AlignSelf = Flex.AlignSelf.Start;
}
@ -416,12 +426,13 @@ namespace Xamarin.Forms
heightConstraint = Math.Max(heightConstraint, item.Frame[1] + item.Frame[3] + item.MarginBottom);
}
//3. reset Shrink and algin-self
//3. reset Shrink, algin-self, and image.aspect
foreach (var child in Children) {
GetFlexItem(child).Shrink = (float)child.GetValue(ShrinkProperty);
GetFlexItem(child).AlignSelf = (Flex.AlignSelf)(FlexAlignSelf)child.GetValue(AlignSelfProperty);
var item = GetFlexItem(child);
item.Shrink = (float)child.GetValue(ShrinkProperty);
item.AlignSelf = (Flex.AlignSelf)(FlexAlignSelf)child.GetValue(AlignSelfProperty);
}
_measuring = false;
return new SizeRequest(new Size(widthConstraint, heightConstraint));
}