[NUI] introduce RelativeLayout
authorYeongjong Lee <cleanlyj@naver.com>
Wed, 21 Oct 2020 06:45:02 +0000 (15:45 +0900)
committerdongsug-song <35130733+dongsug-song@users.noreply.github.com>
Tue, 15 Dec 2020 06:33:59 +0000 (15:33 +0900)
RelativeLayout calculates the size and position of all the
children based on their relationship to each other.

Please see confluence pages for more details.

src/Tizen.NUI/src/internal/Layouting/RelativeLayout.cs [new file with mode: 0644]
src/Tizen.NUI/src/public/Layouting/RelativeLayout.cs [new file with mode: 0644]

diff --git a/src/Tizen.NUI/src/internal/Layouting/RelativeLayout.cs b/src/Tizen.NUI/src/internal/Layouting/RelativeLayout.cs
new file mode 100644 (file)
index 0000000..0efc594
--- /dev/null
@@ -0,0 +1,280 @@
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Xaml;
+
+namespace Tizen.NUI
+{
+    // Methods and Properties for calculation.
+    public partial class RelativeLayout
+    {
+        readonly static Dictionary<View, Relative> HorizontalRelativeCache = new Dictionary<View, Relative>();
+        readonly static Dictionary<View, Relative> VerticalRelativeCache = new Dictionary<View, Relative>();
+        private static readonly RelativePropertyGetters RelativeHorizontalPropertyGetters = new RelativePropertyGetters
+        {
+            RelativeCache = HorizontalRelativeCache,
+            GetTargetFrom = GetLeftTarget,
+            GetTargetTo = GetRightTarget,
+            GetRelativeOffsetFrom = GetLeftRelativeOffset,
+            GetRelativeOffsetTo = GetRightRelativeOffset,
+            GetAlignment = GetHorizontalAlignment,
+            GetFill = GetFillHorizontal,
+            GetMeasuredSize = (item) => { return item.MeasuredWidth.Size.AsDecimal(); }
+        };
+        private static readonly RelativePropertyGetters RelativeVerticalPropertyGetters = new RelativePropertyGetters
+        {
+            RelativeCache = VerticalRelativeCache,
+            GetTargetFrom = GetTopTarget,
+            GetTargetTo = GetBottomTarget,
+            GetRelativeOffsetFrom = GetTopRelativeOffset,
+            GetRelativeOffsetTo = GetBottomRelativeOffset,
+            GetAlignment = GetVerticalAlignment,
+            GetFill = GetFillVertical,
+            GetMeasuredSize = (item) => { return item.MeasuredHeight.Size.AsDecimal(); }
+        };
+
+        delegate Relative GetRelative(View view, in float layoutSize);
+
+        private bool ValidateRelative(Relative relative, in float layoutSize, bool isFixedRelative)
+        {
+            float spaceSize = relative.spaceGeometry.Size;
+            float viewPosition = relative.viewGeometry.Position;
+            float viewSize = relative.viewGeometry.Size;
+            (float start, float end) space = (relative.spaceGeometry.Position, relative.spaceGeometry.Position + spaceSize);
+
+            if (spaceSize > float.Epsilon)
+            {
+                if (!isFixedRelative && spaceSize < viewSize)
+                    return false;
+
+                if (space.start < 0 && space.end <= layoutSize)
+                {
+                    if (viewPosition + viewSize > layoutSize)
+                        return false;
+                }
+                else if (0 <= space.start && layoutSize < space.end)
+                {
+                    if (viewPosition < 0)
+                        return false;
+                }
+                else if (viewPosition < 0 || viewPosition + viewSize > layoutSize)
+                {
+                    return false;
+                }
+            }
+            else if (space.start == 0.0)
+            {
+                if (viewPosition + viewSize > layoutSize)
+                    return false;
+            }
+            else if (space.start == layoutSize)
+            {
+                if (viewPosition < 0)
+                    return false;
+            }
+            else if (viewPosition < 0 || viewPosition + viewSize > layoutSize)
+            {
+                return false;
+            }
+            return true;
+        }
+
+        private bool ValidateRelatives(in float layoutSize, HashSet<View> fixedRelativeSet, GetRelative GetRelative)
+        {
+            foreach (LayoutItem childLayout in LayoutChildren.Where(item => item?.Owner != null))
+            {
+                Relative relative = GetRelative(childLayout.Owner, layoutSize);
+                if (!ValidateRelative(relative, layoutSize, fixedRelativeSet.Contains(childLayout.Owner)))
+                    return false;
+            }
+
+            return true;
+        }
+
+        private HashSet<View> GetFixedRelativeSet(in float layoutSize, GetRelative GetRelative)
+        {
+            HashSet<View> ignoreSet = new HashSet<View>();
+
+            foreach (LayoutItem childLayout in LayoutChildren.Where(item => item?.Owner != null))
+            {
+                Relative relative = GetRelative(childLayout.Owner, layoutSize);
+                if (relative.spaceGeometry.Size > float.Epsilon
+                    && relative.spaceGeometry.Size < relative.viewGeometry.Size)
+                {
+                    ignoreSet.Add(childLayout.Owner);
+                }
+            }
+
+            return ignoreSet;
+        }
+
+        private Relative GetHorizontalRelative(View view, in float layoutSize) => CalculateRelative(view, layoutSize, 0, RelativeHorizontalPropertyGetters);
+
+        private Relative GetVerticalRelative(View view, in float layoutSize) => CalculateRelative(view, layoutSize, 0, RelativeVerticalPropertyGetters);
+
+        private Relative CalculateRelative(View view, in float layoutSize, int depth, RelativePropertyGetters propertyGetters)
+        {
+            if (propertyGetters.RelativeCache.TryGetValue(view, out Relative cache))
+                return cache;
+
+            depth++;
+            if (depth > LayoutChildren.Count)
+            {
+                throw new InvalidOperationException("Circular dependency detected in RelativeLayout.");
+            }
+            Relative cacheFrom;
+            Relative cacheTo;
+            float RelativeOffsetFrom = propertyGetters.GetRelativeOffsetFrom(view);
+            float RelativeOffsetTo = propertyGetters.GetRelativeOffsetTo(view);
+            float viewSize = propertyGetters.GetMeasuredSize(view.Layout);
+
+            if (propertyGetters.GetTargetFrom(view) is View targetFrom && targetFrom != Owner)
+            {
+                cacheFrom = CalculateRelative(targetFrom, layoutSize, depth, propertyGetters);
+            }
+            else
+            {
+                cacheFrom = new Relative() { viewGeometry = new Geometry(0, layoutSize) };
+            }
+
+            if (propertyGetters.GetTargetTo(view) is View targetTo && targetTo != Owner)
+            {
+                cacheTo = CalculateRelative(targetTo, layoutSize, depth, propertyGetters);
+            }
+            else
+            {
+                cacheTo = new Relative() { viewGeometry = new Geometry(0, layoutSize) };
+            }
+
+            float startPosition = cacheFrom.viewGeometry.Position + cacheFrom.viewGeometry.Size * RelativeOffsetFrom;
+            float endPosition = cacheTo.viewGeometry.Position + cacheTo.viewGeometry.Size * RelativeOffsetTo;
+
+            float alignment = propertyGetters.GetAlignment(view).ToFloat();
+            bool fill = propertyGetters.GetFill(view);
+
+            Geometry viewGeometry = new Geometry(startPosition + ((endPosition - startPosition - viewSize) * alignment), viewSize);
+            Geometry spaceGeometry = new Geometry(startPosition, Math.Abs(endPosition - startPosition));
+            if (fill && spaceGeometry.Size > viewGeometry.Size)
+            {
+                viewGeometry = spaceGeometry;
+            }
+
+            Relative retCache = new Relative
+            {
+                viewGeometry = viewGeometry,
+                spaceGeometry = spaceGeometry
+            };
+            propertyGetters.RelativeCache.Add(view, retCache);
+
+            return retCache;
+        }
+
+        private (float, float) CalculateChildrenSize(float parentWidth, float parentHeight)
+        {
+            int minWidth = (int)SuggestedMinimumWidth.AsDecimal();
+            int maxWidth = (int)parentWidth;
+            int minHeight = (int)SuggestedMinimumHeight.AsDecimal();
+            int maxHeight = (int)parentHeight;
+
+            // Find minimum size that satisfy all constraints
+            float BinarySearch(int min, int max, Dictionary<View, Relative> Cache, GetRelative GetRelative)
+            {
+                int current = (min != 0) ? min : (min + max) / 2;
+
+                HashSet<View> fixedRelativeSet = GetFixedRelativeSet(max, GetRelative);
+                Cache.Clear();
+                do
+                {
+                    if (ValidateRelatives(current, fixedRelativeSet, GetRelative))
+                    {
+                        max = current;
+                    }
+                    else
+                    {
+                        min = current + 1;
+                    }
+                    current = (min + max) / 2;
+                    Cache.Clear();
+                } while (min < max);
+
+                return current;
+            }
+
+            float ChildrenWidth = BinarySearch(minWidth, maxWidth, HorizontalRelativeCache, GetHorizontalRelative);
+            float ChildrenHeight = BinarySearch(minHeight, maxHeight, VerticalRelativeCache, GetVerticalRelative);
+            return (ChildrenWidth, ChildrenHeight);
+        }
+
+        private Geometry GetHorizontalLayout(View view) => GetHorizontalRelative(view, MeasuredWidth.Size.AsDecimal()).viewGeometry;
+
+        private Geometry GetVerticalLayout(View view) => GetVerticalRelative(view, MeasuredHeight.Size.AsDecimal()).viewGeometry;
+
+        private struct Geometry
+        {
+            public float Position { get; }
+            public float Size { get; }
+
+            public Geometry(float position, float size)
+            {
+                Position = position;
+                Size = size;
+            }
+        }
+
+        private struct Relative
+        {
+            public Geometry viewGeometry;
+            public Geometry spaceGeometry;
+        }
+        private struct RelativePropertyGetters
+        {
+            public Dictionary<View, Relative> RelativeCache;
+            public Func<BindableObject, View> GetTargetFrom;
+            public Func<BindableObject, View> GetTargetTo;
+            public Func<View, float> GetRelativeOffsetFrom;
+            public Func<View, float> GetRelativeOffsetTo;
+            public Func<View, Alignment> GetAlignment;
+            public Func<View, bool> GetFill;
+            public Func<LayoutItem, float> GetMeasuredSize;
+        }
+
+        class RelativeTargetConverter : TypeConverter, IExtendedTypeConverter
+        {
+            object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
+            {
+                if (serviceProvider == null)
+                    throw new ArgumentNullException(nameof(serviceProvider));
+
+                object target = null;
+
+                if (serviceProvider.GetService(typeof(IReferenceProvider)) is IReferenceProvider referenceProvider)
+                    target = referenceProvider.FindByName(value);
+
+                return target ?? throw new ArgumentException($"Can't resolve name '{value}' on Element", nameof(value));
+            }
+
+            public override object ConvertFromInvariantString(string value) => throw new NotImplementedException();
+
+            public object ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider) => throw new NotImplementedException();
+        }
+    }
+}
diff --git a/src/Tizen.NUI/src/public/Layouting/RelativeLayout.cs b/src/Tizen.NUI/src/public/Layouting/RelativeLayout.cs
new file mode 100644 (file)
index 0000000..4b0c020
--- /dev/null
@@ -0,0 +1,414 @@
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI
+{
+    /// <summary>
+    /// RelativeLayout calculates the size and position of all the children based on their relationship to each other.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public partial class RelativeLayout : LayoutGroup
+    {
+        /// <summary>
+        /// LeftTargetProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty LeftTargetProperty = BindableProperty.CreateAttached("LeftTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// RightTargetProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty RightTargetProperty = BindableProperty.CreateAttached("RightTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// TopTargetProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty TopTargetProperty = BindableProperty.CreateAttached("TopTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// BottomTargetProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty BottomTargetProperty = BindableProperty.CreateAttached("BottomTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// LeftRelativeOffsetProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty LeftRelativeOffsetProperty = BindableProperty.CreateAttached("LeftRelativeOffset", typeof(float), typeof(RelativeLayout), 0.0f, propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// RightRelativeOffsetProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty RightRelativeOffsetProperty = BindableProperty.CreateAttached("RightRelativeOffset", typeof(float), typeof(RelativeLayout), 0.0f, propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// TopRelativeOffsetProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty TopRelativeOffsetProperty = BindableProperty.CreateAttached("TopRelativeOffset", typeof(float), typeof(RelativeLayout), 0.0f, propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// BottomRelativeOffsetProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty BottomRelativeOffsetProperty = BindableProperty.CreateAttached("BottomRelativeOffset", typeof(float), typeof(RelativeLayout), 0.0f, propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// HorizontalAlignmentProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty HorizontalAlignmentProperty = BindableProperty.CreateAttached("HorizontalAlignment", typeof(Alignment), typeof(RelativeLayout), default(Alignment), propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// VerticalAlignmentProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty VerticalAlignmentProperty = BindableProperty.CreateAttached("VerticalAlignment", typeof(Alignment), typeof(RelativeLayout), default(Alignment), propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// FillHorizontalProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty FillHorizontalProperty = BindableProperty.CreateAttached("FillHorizontal", typeof(bool), typeof(RelativeLayout), false, propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// FillVerticalProperty
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty FillVerticalProperty = BindableProperty.CreateAttached("FillVertical", typeof(bool), typeof(RelativeLayout), false, propertyChanged: OnChildPropertyChanged);
+
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RelativeLayout() { }
+
+        /// <summary>
+        /// Gets left target object whose size and position is being used as reference.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <returns>The object whose size and position is being used as reference.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        [Binding.TypeConverter(typeof(RelativeTargetConverter))]
+        public static View GetLeftTarget(BindableObject view) => GetAttachedValue<View>(view, LeftTargetProperty);
+
+        /// <summary>
+        /// Gets right target object whose size and position is being used as reference.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <returns>The object whose size and position is being used as reference.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        [Binding.TypeConverter(typeof(RelativeTargetConverter))]
+        public static View GetRightTarget(BindableObject view) => GetAttachedValue<View>(view, RightTargetProperty);
+
+        /// <summary>
+        /// Gets top target object whose size and position is being used as reference.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <returns>The object whose size and position is being used as reference.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        [Binding.TypeConverter(typeof(RelativeTargetConverter))]
+        public static View GetTopTarget(BindableObject view) => GetAttachedValue<View>(view, TopTargetProperty);
+
+        /// <summary>
+        /// Gets bottom target object whose size and position is being used as reference.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <returns>The object whose size and position is being used as reference.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        [Binding.TypeConverter(typeof(RelativeTargetConverter))]
+        public static View GetBottomTarget(BindableObject view) => GetAttachedValue<View>(view, BottomTargetProperty);
+
+        /// <summary>
+        /// Gets left relative offset.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <returns>The ratio between left and right of the <seealso cref="LeftTargetProperty"/>.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static float GetLeftRelativeOffset(View view) => GetAttachedValue<float>(view, LeftRelativeOffsetProperty);
+
+        /// <summary>
+        /// Gets right relative offset.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <returns>The ratio between left and right of the <seealso cref="RightTargetProperty"/>.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static float GetRightRelativeOffset(View view) => GetAttachedValue<float>(view, RightRelativeOffsetProperty);
+
+        /// <summary>
+        /// Gets top relative offset.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <returns>The ratio between top and bottom of the <seealso cref="TopTargetProperty"/>.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static float GetTopRelativeOffset(View view) => GetAttachedValue<float>(view, TopRelativeOffsetProperty);
+
+        /// <summary>
+        /// Gets bottom relative offset.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <returns>The ratio between top and bottom of the <seealso cref="BottomTargetProperty"/>.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static float GetBottomRelativeOffset(View view) => GetAttachedValue<float>(view, BottomRelativeOffsetProperty);
+
+        /// <summary>
+        /// Gets the horizontal alignment
+        /// </summary>
+        /// <param name="view">The child view.</param>
+        /// <returns>The horizontal alignment of <paramref name="view"/>.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static Alignment GetHorizontalAlignment(View view) => GetAttachedValue<Alignment>(view, HorizontalAlignmentProperty);
+
+        /// <summary>
+        /// Gets the vertical alignment
+        /// </summary>
+        /// <param name="view">The child view.</param>
+        /// <returns>The vertical alignment of <paramref name="view"/>.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static Alignment GetVerticalAlignment(View view) => GetAttachedValue<Alignment>(view, VerticalAlignmentProperty);
+
+        /// <summary>
+        /// Gets the boolean value whether child fills its horizontal space.
+        /// </summary>
+        /// <param name="view">The child view.</param>
+        /// <returns>True if to fill the space, false otherwise.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static bool GetFillHorizontal(View view) => GetAttachedValue<bool>(view, FillHorizontalProperty);
+
+        /// <summary>
+        /// Gets the boolean value whether child fills its vertical space.
+        /// </summary>
+        /// <param name="view">The child view.</param>
+        /// <returns>True if to fill the space, false otherwise.</returns>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static bool GetFillVertical(View view) => GetAttachedValue<bool>(view, FillVerticalProperty);
+
+        /// <summary>
+        /// Specifies the left side edge of the child view relative to the target view. <br/>
+        /// null <paramref name="reference"/> means parent relative layout.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <param name="reference">The object whose size and position is being used as reference.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetLeftTarget(View view, View reference) => SetAttachedValue(view, LeftTargetProperty, reference);
+
+        /// <summary>
+        /// Specifies the right side edge of the child view relative to the target view. <br/>
+        /// null <paramref name="reference"/> means parent relative layout.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <param name="reference">The object whose size and position is being used as reference.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetRightTarget(View view, View reference) => SetAttachedValue(view, RightTargetProperty, reference);
+
+        /// <summary>
+        /// Specifies the top side edge of the child view relative to the target view. <br/>
+        /// null <paramref name="reference"/> means parent relative layout.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <param name="reference">The object whose size and position is being used as reference.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetTopTarget(View view, View reference) => SetAttachedValue(view, TopTargetProperty, reference);
+
+        /// <summary>
+        /// Specifies the bottom side edge of the child view relative to the target view. <br/>
+        /// null <paramref name="reference"/> means parent relative layout.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <param name="reference">The object whose size and position is being used as reference.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetBottomTarget(View view, View reference) => SetAttachedValue(view, BottomTargetProperty, reference);
+
+        /// <summary>
+        /// Sets the relative offset for left target.
+        /// When <paramref name="value"/> is 0 the left edges of the left target and <paramref name="view"/> are aligned.<br/>
+        /// When <paramref name="value"/> is 1 the left edge of the <paramref name="view"/> is aligned to the right edge of the left target.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <param name="value">The ratio between left and right of the <seealso cref="LeftTargetProperty"/>.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetLeftRelativeOffset(View view, float value) => SetAttachedValue(view, LeftRelativeOffsetProperty, value);
+
+        /// <summary>
+        /// Sets the relative offset for right target.
+        /// When <paramref name="value"/> is 0 the right edge of the <paramref name="view"/> is aligned to the left edge of the right target.<br/>
+        /// When <paramref name="value"/> is 1 the right edges of the right target and <paramref name="view"/> are aligned.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <param name="value">The ratio between left and right of the <seealso cref="RightTargetProperty"/>.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetRightRelativeOffset(View view, float value) => SetAttachedValue(view, RightRelativeOffsetProperty, value);
+
+        /// <summary>
+        /// Sets the relative offset for top target.
+        /// When <paramref name="value"/> is 0 the top edges of the top target and <paramref name="view"/> are aligned.<br/>
+        /// When <paramref name="value"/> is 1 the top edge of the <paramref name="view"/> is aligned to the bottom edge of the top target.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <param name="value">The ratio between left and right of the <seealso cref="TopTargetProperty"/>.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetTopRelativeOffset(View view, float value) => SetAttachedValue(view, TopRelativeOffsetProperty, value);
+
+        /// <summary>
+        /// Sets the relative offset for bottom target.
+        /// When <paramref name="value"/> is 0 the bottom edge of the <paramref name="view"/> is aligned to the top edge of the right target.<br/>
+        /// When <paramref name="value"/> is 1 the bottom edges of the bottom target and <paramref name="view"/> are aligned.
+        /// </summary>
+        /// <param name="view">The child view whose size and position is being changed.</param>
+        /// <param name="value">The ratio between left and right of the <seealso cref="BottomTargetProperty"/>.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetBottomRelativeOffset(View view, float value) => SetAttachedValue(view, BottomRelativeOffsetProperty, value);
+
+        /// <summary>
+        /// Sets the horizontal alignment of this child view.
+        /// </summary>
+        /// <param name="view">The child view.</param>
+        /// <param name="value">The horizontal alignment of <paramref name="view"/>.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetHorizontalAlignment(View view, Alignment value) => SetAttachedValue(view, HorizontalAlignmentProperty, value);
+
+        /// <summary>
+        /// Sets the vertical alignment of this child view.
+        /// </summary>
+        /// <param name="view">The child view.</param>
+        /// <param name="value">The vertical alignment of <paramref name="view"/>.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetVerticalAlignment(View view, Alignment value) => SetAttachedValue(view, VerticalAlignmentProperty, value);
+
+        /// <summary>
+        /// Sets the boolean value whether child fills its horizontal space.
+        /// </summary>
+        /// <param name="view">The child view.</param>
+        /// <param name="value">True if to fill the space, false otherwise.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetFillHorizontal(View view, bool value) => SetAttachedValue(view, FillHorizontalProperty, value);
+
+        /// <summary>
+        /// Sets the boolean value whether child fills its vertical space.
+        /// </summary>
+        /// <param name="view">The child view.</param>
+        /// <param name="value">True if to fill the space, false otherwise.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void SetFillVertical(View view, bool value) => SetAttachedValue(view, FillVerticalProperty, value);
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
+        {
+            MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
+            MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
+
+            for (int i = 0; i < LayoutChildren.Count; i++)
+            {
+                LayoutItem childLayout = LayoutChildren[i];
+                if (childLayout != null)
+                {
+                    MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
+
+                    if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
+                    {
+                        childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
+                    }
+                    if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
+                    {
+                        childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
+                    }
+                }
+            }
+
+            (float childrenWidth, float childrenHeight) =  CalculateChildrenSize(widthMeasureSpec.Size.AsDecimal(), heightMeasureSpec.Size.AsDecimal());
+            SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(childrenWidth), widthMeasureSpec, childWidthState),
+                                  ResolveSizeAndState(new LayoutLength(childrenHeight), heightMeasureSpec, childHeightState));
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
+        {
+            for (int i = 0; i < LayoutChildren.Count; i++)
+            {
+                LayoutItem childLayout = LayoutChildren[i];
+                if (childLayout != null)
+                {
+                    Geometry horizontalGeometry = GetHorizontalLayout(childLayout.Owner);
+                    Geometry verticalGeometry = GetVerticalLayout(childLayout.Owner);
+
+                    LayoutLength childLeft =  new LayoutLength(horizontalGeometry.Position);
+                    LayoutLength childRight = new LayoutLength(horizontalGeometry.Position + horizontalGeometry.Size);
+                    LayoutLength childTop = new LayoutLength(verticalGeometry.Position);
+                    LayoutLength childBottom = new LayoutLength(verticalGeometry.Position + verticalGeometry.Size);
+
+                    childLayout.Layout(childLeft, childTop, childRight, childBottom);
+                }
+            }
+            HorizontalRelativeCache.Clear();
+            VerticalRelativeCache.Clear();
+        }
+
+        /// <summary>The alignment of the relative layout child.</summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public enum Alignment
+        {
+            /// <summary>At the start of the container.</summary>
+            Start = 0,
+            /// <summary>At the center of the container.</summary>
+            Center = 1,
+            /// <summary>At the end of the container.</summary>
+            End = 2,
+        }
+    }
+
+    // Extension Method of RelativeLayout.Alignment.
+    internal static partial class RelativeLayoutAlignmentExtension
+    {
+        public static float ToFloat(this RelativeLayout.Alignment align)
+        {
+            return 0.5f * (float)align;
+        }
+    }
+}