Skip to content

Commit

Permalink
Update RelativeLayout to make it respond to constraint changes (#425)
Browse files Browse the repository at this point in the history
* Update RelativeLayout to make it respond to constraint changes

Constraints of a RelativeLayout are bindable properties but the layout
does not update when the constraints are updated.

This change will invalidate the layout whenever  XConstraint,
YConstraint, WidthConstraint or HeightConstraint is changed (either in
code or through a change in the bound property)

* Specified changed handler as named property

* Adding attached property accessors for layout properties

Since the constraint attached properties can now be updated at runtime,
setters are required for those properties. Also, when adding a child
view at runtime using the Add() method with x/y/w/h constraints,
generating the bounds constraints is deferred to the layout phase.

* Unit tests for runtime constraints updates in RelativeLayout

* Rename unit test method

Rename LayoutChangesAtRuntim() to LayoutIsUpdatedWhenConstraintsChange()

* Wrap RelativeLayout update setters in BatchBegin/Commit

* Update documentation of RelativeLayout

Added SetXConstraint(), SetYConstraint(), SetWidthConstraint() and
SetHeightConstraint()
  • Loading branch information
activa authored and Jason Smith committed Feb 2, 2017
1 parent 9a6c9a0 commit 01a56f9
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 8 deletions.
41 changes: 41 additions & 0 deletions Xamarin.Forms.Core.UnitTests/RelativeLayoutTests.cs
Expand Up @@ -74,6 +74,47 @@ public void SimpleLayout ()
Assert.AreEqual (new Rectangle (30, 20, 50, 25), child.Bounds);
}

[Test]
public void LayoutIsUpdatedWhenConstraintsChange()
{
var relativeLayout = new RelativeLayout
{
Platform = new UnitPlatform(),
IsPlatformEnabled = true
};

var child = new View
{
IsPlatformEnabled = true
};

relativeLayout.Children.Add(child,
Constraint.Constant(30),
Constraint.Constant(20),
Constraint.RelativeToParent(parent => parent.Height / 2),
Constraint.RelativeToParent(parent => parent.Height / 4));

relativeLayout.Layout(new Rectangle(0, 0, 100, 100));

Assert.AreEqual(new Rectangle(30, 20, 50, 25), child.Bounds);

RelativeLayout.SetXConstraint(child, Constraint.Constant(40));

Assert.AreEqual(new Rectangle(40, 20, 50, 25), child.Bounds);

RelativeLayout.SetYConstraint(child, Constraint.Constant(10));

Assert.AreEqual(new Rectangle(40, 10, 50, 25), child.Bounds);

RelativeLayout.SetWidthConstraint(child, Constraint.RelativeToParent(parent => parent.Height / 4));

Assert.AreEqual(new Rectangle(40, 10, 25, 25), child.Bounds);

RelativeLayout.SetHeightConstraint(child, Constraint.RelativeToParent(parent => parent.Height / 2));

Assert.AreEqual(new Rectangle(40, 10, 25, 50), child.Bounds);
}

[Test]
public void SimpleExpressionLayout ()
{
Expand Down
62 changes: 54 additions & 8 deletions Xamarin.Forms.Core/RelativeLayout.cs
Expand Up @@ -8,13 +8,13 @@ namespace Xamarin.Forms
{
public class RelativeLayout : Layout<View>
{
public static readonly BindableProperty XConstraintProperty = BindableProperty.CreateAttached("XConstraint", typeof(Constraint), typeof(RelativeLayout), null);
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);
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);
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);
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);

Expand Down Expand Up @@ -72,6 +72,25 @@ IEnumerable<View> 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);
Expand Down Expand Up @@ -102,6 +121,26 @@ public static void SetBoundsConstraint(BindableObject bindable, BoundsConstraint
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)
Expand Down Expand Up @@ -248,13 +287,13 @@ void CreateBoundsFromConstraints(View view, Constraint xConstraint, Constraint y
static Rectangle SolveView(View view)
{
BoundsConstraint boundsConstraint = GetBoundsConstraint(view);
var result = new Rectangle();

if (boundsConstraint == null)
{
throw new Exception("BoundsConstraint should not be null at this point");
}
result = boundsConstraint.Compute();

var result = boundsConstraint.Compute();

return result;
}
Expand All @@ -280,7 +319,7 @@ public RelativeElementCollection(ObservableCollection<Element> inner, RelativeLa
public void Add(View view, Expression<Func<Rectangle>> bounds)
{
if (bounds == null)
throw new ArgumentNullException("bounds");
throw new ArgumentNullException(nameof(bounds));
SetBoundsConstraint(view, BoundsConstraint.FromExpression(bounds));

base.Add(view);
Expand Down Expand Up @@ -308,7 +347,14 @@ public void Add(View view, Expression<Func<double>> x = null, Expression<Func<do

public void Add(View view, Constraint xConstraint = null, Constraint yConstraint = null, Constraint widthConstraint = null, Constraint heightConstraint = null)
{
Parent.CreateBoundsFromConstraints(view, xConstraint, yConstraint, widthConstraint, heightConstraint);
view.BatchBegin();

RelativeLayout.SetXConstraint(view, xConstraint);
RelativeLayout.SetYConstraint(view, yConstraint);
RelativeLayout.SetWidthConstraint(view, widthConstraint);
RelativeLayout.SetHeightConstraint(view, heightConstraint);

view.BatchCommit();

base.Add(view);
}
Expand Down
84 changes: 84 additions & 0 deletions docs/Xamarin.Forms.Core/Xamarin.Forms/RelativeLayout.xml
Expand Up @@ -446,6 +446,90 @@ public class RelativeLayoutExample : ContentPage
<remarks>To be added.</remarks>
</Docs>
</Member>
<Member MemberName="SetHeightConstraint">
<MemberSignature Language="C#" Value="public static void SetHeightConstraint (Xamarin.Forms.BindableObject bindable, Xamarin.Forms.Constraint value);" />
<MemberSignature Language="ILAsm" Value=".method public static hidebysig void SetHeightConstraint(class Xamarin.Forms.BindableObject bindable, class Xamarin.Forms.Constraint value) cil managed" />
<MemberType>Method</MemberType>
<AssemblyInfo>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
</AssemblyInfo>
<ReturnValue>
<ReturnType>System.Void</ReturnType>
</ReturnValue>
<Parameters>
<Parameter Name="bindable" Type="Xamarin.Forms.BindableObject" />
<Parameter Name="value" Type="Xamarin.Forms.Constraint" />
</Parameters>
<Docs>
<param name="bindable">The <see cref="T:Xamarin.Forms.BindableObject" /> to which the constraint will be applied.</param>
<param name="value">The <see cref="T:Xamarin.Forms.Constraint" /> on the height of the <paramref name="bindable" />.</param>
<summary>Sets <paramref name="value" /> as a constraint on the height of the <paramref name="bindable" />.</summary>
<remarks>To be added.</remarks>
</Docs>
</Member>
<Member MemberName="SetWidthConstraint">
<MemberSignature Language="C#" Value="public static void SetWidthConstraint (Xamarin.Forms.BindableObject bindable, Xamarin.Forms.Constraint value);" />
<MemberSignature Language="ILAsm" Value=".method public static hidebysig void SetWidthConstraint(class Xamarin.Forms.BindableObject bindable, class Xamarin.Forms.Constraint value) cil managed" />
<MemberType>Method</MemberType>
<AssemblyInfo>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
</AssemblyInfo>
<ReturnValue>
<ReturnType>System.Void</ReturnType>
</ReturnValue>
<Parameters>
<Parameter Name="bindable" Type="Xamarin.Forms.BindableObject" />
<Parameter Name="value" Type="Xamarin.Forms.Constraint" />
</Parameters>
<Docs>
<param name="bindable">The <see cref="T:Xamarin.Forms.BindableObject" /> to which the constraint will be applied.</param>
<param name="value">The <see cref="T:Xamarin.Forms.Constraint" /> on the width of the <paramref name="bindable" />.</param>
<summary>Sets <paramref name="value" /> as a constraint on the width of the <paramref name="bindable" />.</summary>
<remarks>To be added.</remarks>
</Docs>
</Member>
<Member MemberName="SetXConstraint">
<MemberSignature Language="C#" Value="public static void SetXConstraint (Xamarin.Forms.BindableObject bindable, Xamarin.Forms.Constraint value);" />
<MemberSignature Language="ILAsm" Value=".method public static hidebysig void SetXConstraint(class Xamarin.Forms.BindableObject bindable, class Xamarin.Forms.Constraint value) cil managed" />
<MemberType>Method</MemberType>
<AssemblyInfo>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
</AssemblyInfo>
<ReturnValue>
<ReturnType>System.Void</ReturnType>
</ReturnValue>
<Parameters>
<Parameter Name="bindable" Type="Xamarin.Forms.BindableObject" />
<Parameter Name="value" Type="Xamarin.Forms.Constraint" />
</Parameters>
<Docs>
<param name="bindable">The <see cref="T:Xamarin.Forms.BindableObject" /> to which the constraint will be applied.</param>
<param name="value">The <see cref="T:Xamarin.Forms.Constraint" /> on the X position of the <paramref name="bindable" />.</param>
<summary>Sets <paramref name="value" /> as a constraint on the X position of the <paramref name="bindable" />.</summary>
<remarks>To be added.</remarks>
</Docs>
</Member>
<Member MemberName="SetYConstraint">
<MemberSignature Language="C#" Value="public static void SetYConstraint (Xamarin.Forms.BindableObject bindable, Xamarin.Forms.Constraint value);" />
<MemberSignature Language="ILAsm" Value=".method public static hidebysig void SetYConstraint(class Xamarin.Forms.BindableObject bindable, class Xamarin.Forms.Constraint value) cil managed" />
<MemberType>Method</MemberType>
<AssemblyInfo>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
</AssemblyInfo>
<ReturnValue>
<ReturnType>System.Void</ReturnType>
</ReturnValue>
<Parameters>
<Parameter Name="bindable" Type="Xamarin.Forms.BindableObject" />
<Parameter Name="value" Type="Xamarin.Forms.Constraint" />
</Parameters>
<Docs>
<param name="bindable">The <see cref="T:Xamarin.Forms.BindableObject" /> to which the constraint will be applied.</param>
<param name="value">The <see cref="T:Xamarin.Forms.Constraint" /> on the Y position of the <paramref name="bindable" />.</param>
<summary>Sets <paramref name="value" /> as a constraint on the Y position of the <paramref name="bindable" />.</summary>
<remarks>To be added.</remarks>
</Docs>
</Member>
<Member MemberName="WidthConstraintProperty">
<MemberSignature Language="C#" Value="public static readonly Xamarin.Forms.BindableProperty WidthConstraintProperty;" />
<MemberSignature Language="ILAsm" Value=".field public static initonly class Xamarin.Forms.BindableProperty WidthConstraintProperty" />
Expand Down

0 comments on commit 01a56f9

Please sign in to comment.