364 строки
12 KiB
C#
364 строки
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using Xamarin.Forms.Internals;
|
|
|
|
namespace Xamarin.Forms
|
|
{
|
|
public class RelativeLayout : Layout<View>
|
|
{
|
|
public static readonly BindableProperty XConstraintProperty = BindableProperty.CreateAttached("XConstraint", typeof(Constraint), typeof(RelativeLayout), null, propertyChanged: ConstraintChanged);
|
|
|
|
public static readonly BindableProperty YConstraintProperty = BindableProperty.CreateAttached("YConstraint", typeof(Constraint), typeof(RelativeLayout), null, propertyChanged: ConstraintChanged);
|
|
|
|
public static readonly BindableProperty WidthConstraintProperty = BindableProperty.CreateAttached("WidthConstraint", typeof(Constraint), typeof(RelativeLayout), null, propertyChanged: ConstraintChanged);
|
|
|
|
public static readonly BindableProperty HeightConstraintProperty = BindableProperty.CreateAttached("HeightConstraint", typeof(Constraint), typeof(RelativeLayout), null, propertyChanged: ConstraintChanged);
|
|
|
|
public static readonly BindableProperty BoundsConstraintProperty = BindableProperty.CreateAttached("BoundsConstraint", typeof(BoundsConstraint), typeof(RelativeLayout), null);
|
|
|
|
readonly RelativeElementCollection _children;
|
|
|
|
IEnumerable<View> _childrenInSolveOrder;
|
|
|
|
public RelativeLayout()
|
|
{
|
|
VerticalOptions = HorizontalOptions = LayoutOptions.FillAndExpand;
|
|
_children = new RelativeElementCollection(InternalChildren, this);
|
|
_children.Parent = this;
|
|
}
|
|
|
|
public new IRelativeList<View> Children
|
|
{
|
|
get { return _children; }
|
|
}
|
|
|
|
IEnumerable<View> ChildrenInSolveOrder
|
|
{
|
|
get
|
|
{
|
|
if (_childrenInSolveOrder != null)
|
|
return _childrenInSolveOrder;
|
|
|
|
var result = new List<View>();
|
|
var solveTable = new Dictionary<View, bool>();
|
|
foreach (View child in Children.Cast<View>())
|
|
{
|
|
solveTable[child] = false;
|
|
}
|
|
|
|
List<View> unsolvedChildren = Children.Cast<View>().ToList();
|
|
while (unsolvedChildren.Any())
|
|
{
|
|
List<View> copy = unsolvedChildren.ToList();
|
|
var solvedChild = false;
|
|
foreach (View child in copy)
|
|
{
|
|
if (CanSolveView(child, solveTable))
|
|
{
|
|
result.Add(child);
|
|
solveTable[child] = true;
|
|
unsolvedChildren.Remove(child);
|
|
solvedChild = true;
|
|
}
|
|
}
|
|
if (!solvedChild)
|
|
throw new UnsolvableConstraintsException("Constraints as specified contain an unsolvable loop.");
|
|
}
|
|
|
|
_childrenInSolveOrder = result;
|
|
return _childrenInSolveOrder;
|
|
}
|
|
}
|
|
|
|
static void ConstraintChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
View view = bindable as View;
|
|
|
|
(view?.Parent as RelativeLayout)?.UpdateBoundsConstraint(view);
|
|
}
|
|
|
|
void UpdateBoundsConstraint(View view)
|
|
{
|
|
if (GetBoundsConstraint(view) == null)
|
|
return; // Bounds constraint hasn't been calculated yet, no need to update just yet
|
|
|
|
CreateBoundsFromConstraints(view, GetXConstraint(view), GetYConstraint(view), GetWidthConstraint(view), GetHeightConstraint(view));
|
|
|
|
_childrenInSolveOrder = null; // New constraints may have impact on solve order
|
|
|
|
InvalidateLayout();
|
|
}
|
|
|
|
public static BoundsConstraint GetBoundsConstraint(BindableObject bindable)
|
|
{
|
|
return (BoundsConstraint)bindable.GetValue(BoundsConstraintProperty);
|
|
}
|
|
|
|
public static Constraint GetHeightConstraint(BindableObject bindable)
|
|
{
|
|
return (Constraint)bindable.GetValue(HeightConstraintProperty);
|
|
}
|
|
|
|
public static Constraint GetWidthConstraint(BindableObject bindable)
|
|
{
|
|
return (Constraint)bindable.GetValue(WidthConstraintProperty);
|
|
}
|
|
|
|
public static Constraint GetXConstraint(BindableObject bindable)
|
|
{
|
|
return (Constraint)bindable.GetValue(XConstraintProperty);
|
|
}
|
|
|
|
public static Constraint GetYConstraint(BindableObject bindable)
|
|
{
|
|
return (Constraint)bindable.GetValue(YConstraintProperty);
|
|
}
|
|
|
|
public static void SetBoundsConstraint(BindableObject bindable, BoundsConstraint value)
|
|
{
|
|
bindable.SetValue(BoundsConstraintProperty, value);
|
|
}
|
|
|
|
public static void SetHeightConstraint(BindableObject bindable, Constraint value)
|
|
{
|
|
bindable.SetValue(HeightConstraintProperty, value);
|
|
}
|
|
|
|
public static void SetWidthConstraint(BindableObject bindable, Constraint value)
|
|
{
|
|
bindable.SetValue(WidthConstraintProperty, value);
|
|
}
|
|
|
|
public static void SetXConstraint(BindableObject bindable, Constraint value)
|
|
{
|
|
bindable.SetValue(XConstraintProperty, value);
|
|
}
|
|
|
|
public static void SetYConstraint(BindableObject bindable, Constraint value)
|
|
{
|
|
bindable.SetValue(YConstraintProperty, value);
|
|
}
|
|
|
|
protected override void LayoutChildren(double x, double y, double width, double height)
|
|
{
|
|
foreach (View child in ChildrenInSolveOrder)
|
|
{
|
|
LayoutChildIntoBoundingRegion(child, SolveView(child));
|
|
}
|
|
}
|
|
|
|
protected override void OnAdded(View view)
|
|
{
|
|
BoundsConstraint boundsConstraint = GetBoundsConstraint(view);
|
|
if (boundsConstraint == null)
|
|
{
|
|
// user probably added the view through the strict Add method.
|
|
CreateBoundsFromConstraints(view, GetXConstraint(view), GetYConstraint(view), GetWidthConstraint(view), GetHeightConstraint(view));
|
|
}
|
|
|
|
_childrenInSolveOrder = null;
|
|
base.OnAdded(view);
|
|
}
|
|
|
|
protected override void OnRemoved(View view)
|
|
{
|
|
_childrenInSolveOrder = null;
|
|
base.OnRemoved(view);
|
|
}
|
|
|
|
[Obsolete("Use OnMeasure")]
|
|
protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
|
|
{
|
|
double mockWidth = double.IsPositiveInfinity(widthConstraint) ? ParentView.Width : widthConstraint;
|
|
double mockHeight = double.IsPositiveInfinity(heightConstraint) ? ParentView.Height : heightConstraint;
|
|
MockBounds(new Rectangle(0, 0, mockWidth, mockHeight));
|
|
|
|
var boundsRectangle = new Rectangle();
|
|
var set = false;
|
|
foreach (View child in ChildrenInSolveOrder)
|
|
{
|
|
Rectangle bounds = SolveView(child);
|
|
child.MockBounds(bounds);
|
|
if (!set)
|
|
{
|
|
boundsRectangle = bounds;
|
|
set = true;
|
|
}
|
|
else
|
|
{
|
|
boundsRectangle.Left = Math.Min(boundsRectangle.Left, bounds.Left);
|
|
boundsRectangle.Top = Math.Min(boundsRectangle.Top, bounds.Top);
|
|
boundsRectangle.Right = Math.Max(boundsRectangle.Right, bounds.Right);
|
|
boundsRectangle.Bottom = Math.Max(boundsRectangle.Bottom, bounds.Bottom);
|
|
}
|
|
}
|
|
|
|
foreach (View child in ChildrenInSolveOrder)
|
|
child.UnmockBounds();
|
|
|
|
UnmockBounds();
|
|
|
|
return new SizeRequest(new Size(boundsRectangle.Right, boundsRectangle.Bottom));
|
|
}
|
|
|
|
bool CanSolveView(View view, Dictionary<View, bool> solveTable)
|
|
{
|
|
BoundsConstraint boundsConstraint = GetBoundsConstraint(view);
|
|
var parents = new List<View>();
|
|
if (boundsConstraint == null)
|
|
{
|
|
throw new Exception("BoundsConstraint should not be null at this point");
|
|
}
|
|
parents.AddRange(boundsConstraint.RelativeTo);
|
|
// expressions probably referenced the base layout somewhere
|
|
while (parents.Remove(this)) // because winphone does not have RemoveAll...
|
|
;
|
|
|
|
if (!parents.Any())
|
|
return true;
|
|
|
|
for (var i = 0; i < parents.Count; i++)
|
|
{
|
|
View p = parents[i];
|
|
|
|
bool solvable;
|
|
if (!solveTable.TryGetValue(p, out solvable))
|
|
{
|
|
throw new InvalidOperationException("Views that have relationships to or from them must be kept in the RelativeLayout.");
|
|
}
|
|
|
|
if (!solvable)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CreateBoundsFromConstraints(View view, Constraint xConstraint, Constraint yConstraint, Constraint widthConstraint, Constraint heightConstraint)
|
|
{
|
|
var parents = new List<View>();
|
|
|
|
Func<double> x;
|
|
if (xConstraint != null)
|
|
{
|
|
x = () => xConstraint.Compute(this);
|
|
if (xConstraint.RelativeTo != null)
|
|
parents.AddRange(xConstraint.RelativeTo);
|
|
}
|
|
else
|
|
x = () => 0;
|
|
|
|
Func<double> y;
|
|
if (yConstraint != null)
|
|
{
|
|
y = () => yConstraint.Compute(this);
|
|
if (yConstraint.RelativeTo != null)
|
|
parents.AddRange(yConstraint.RelativeTo);
|
|
}
|
|
else
|
|
y = () => 0;
|
|
|
|
Func<double> width;
|
|
if (widthConstraint != null)
|
|
{
|
|
width = () => widthConstraint.Compute(this);
|
|
if (widthConstraint.RelativeTo != null)
|
|
parents.AddRange(widthConstraint.RelativeTo);
|
|
}
|
|
else
|
|
width = () => view.Measure(Width, Height, MeasureFlags.IncludeMargins).Request.Width;
|
|
|
|
Func<double> height;
|
|
if (heightConstraint != null)
|
|
{
|
|
height = () => heightConstraint.Compute(this);
|
|
if (heightConstraint.RelativeTo != null)
|
|
parents.AddRange(heightConstraint.RelativeTo);
|
|
}
|
|
else
|
|
height = () => view.Measure(Width, Height, MeasureFlags.IncludeMargins).Request.Height;
|
|
|
|
BoundsConstraint bounds = BoundsConstraint.FromExpression(() => new Rectangle(x(), y(), width(), height()), parents.Distinct().ToArray());
|
|
SetBoundsConstraint(view, bounds);
|
|
}
|
|
|
|
static Rectangle SolveView(View view)
|
|
{
|
|
BoundsConstraint boundsConstraint = GetBoundsConstraint(view);
|
|
|
|
if (boundsConstraint == null)
|
|
{
|
|
throw new Exception("BoundsConstraint should not be null at this point");
|
|
}
|
|
|
|
var result = boundsConstraint.Compute();
|
|
|
|
return result;
|
|
}
|
|
|
|
public interface IRelativeList<T> : IList<T> where T : View
|
|
{
|
|
void Add(T view, Expression<Func<Rectangle>> bounds);
|
|
|
|
void Add(T view, Expression<Func<double>> x = null, Expression<Func<double>> y = null, Expression<Func<double>> width = null, Expression<Func<double>> height = null);
|
|
|
|
void Add(T view, Constraint xConstraint = null, Constraint yConstraint = null, Constraint widthConstraint = null, Constraint heightConstraint = null);
|
|
}
|
|
|
|
class RelativeElementCollection : ElementCollection<View>, IRelativeList<View>
|
|
{
|
|
public RelativeElementCollection(ObservableCollection<Element> inner, RelativeLayout parent) : base(inner)
|
|
{
|
|
Parent = parent;
|
|
}
|
|
|
|
internal RelativeLayout Parent { get; set; }
|
|
|
|
public void Add(View view, Expression<Func<Rectangle>> bounds)
|
|
{
|
|
if (bounds == null)
|
|
throw new ArgumentNullException(nameof(bounds));
|
|
SetBoundsConstraint(view, BoundsConstraint.FromExpression(bounds));
|
|
|
|
base.Add(view);
|
|
}
|
|
|
|
public void Add(View view, Expression<Func<double>> x = null, Expression<Func<double>> y = null, Expression<Func<double>> width = null, Expression<Func<double>> height = null)
|
|
{
|
|
Func<double> xCompiled = x != null ? x.Compile() : () => 0;
|
|
Func<double> yCompiled = y != null ? y.Compile() : () => 0;
|
|
Func<double> widthCompiled = width != null ? width.Compile() : () => view.Measure(Parent.Width, Parent.Height, MeasureFlags.IncludeMargins).Request.Width;
|
|
Func<double> heightCompiled = height != null ? height.Compile() : () => view.Measure(Parent.Width, Parent.Height, MeasureFlags.IncludeMargins).Request.Height;
|
|
|
|
var parents = new List<View>();
|
|
parents.AddRange(ExpressionSearch.Default.FindObjects<View>(x));
|
|
parents.AddRange(ExpressionSearch.Default.FindObjects<View>(y));
|
|
parents.AddRange(ExpressionSearch.Default.FindObjects<View>(width));
|
|
parents.AddRange(ExpressionSearch.Default.FindObjects<View>(height));
|
|
|
|
BoundsConstraint bounds = BoundsConstraint.FromExpression(() => new Rectangle(xCompiled(), yCompiled(), widthCompiled(), heightCompiled()), parents.Distinct().ToArray());
|
|
|
|
SetBoundsConstraint(view, bounds);
|
|
|
|
base.Add(view);
|
|
}
|
|
|
|
public void Add(View view, Constraint xConstraint = null, Constraint yConstraint = null, Constraint widthConstraint = null, Constraint heightConstraint = null)
|
|
{
|
|
view.BatchBegin();
|
|
|
|
RelativeLayout.SetXConstraint(view, xConstraint);
|
|
RelativeLayout.SetYConstraint(view, yConstraint);
|
|
RelativeLayout.SetWidthConstraint(view, widthConstraint);
|
|
RelativeLayout.SetHeightConstraint(view, heightConstraint);
|
|
|
|
view.BatchCommit();
|
|
|
|
base.Add(view);
|
|
}
|
|
}
|
|
}
|
|
} |