Skip to content

Instantly share code, notes, and snippets.

@KirillAshikhmin
Created January 19, 2019 20:45
Show Gist options
  • Save KirillAshikhmin/c016e57d4997dada4fe61f1c85eb87ce to your computer and use it in GitHub Desktop.
Save KirillAshikhmin/c016e57d4997dada4fe61f1c85eb87ce to your computer and use it in GitHub Desktop.
Base layout for Xamarin.Forms
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Instamart.Helpers.FastUi.Layouts;
using Xamarin.Forms;
namespace Project.UI.Layouts {
public static class BaseLayoutExtensions {
public static T SetLayoutViewType<T, TU>(this T view, TU type) where T : View {
BaseLayout<TU>.SetViewType(view, type);
return view;
}
public static void Add<T>(this Dictionary<T, Rectangle> dict, T type, double x, double y, double width, double height) {
dict.Add(type, new Rectangle(x, y, width, height));
}
public static void Add<T>(this Dictionary<T, Rectangle> dict, T type, double x, double y, Size size) {
dict.Add(type, new Rectangle(x, y, size.Width, size.Height));
}
}
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public class BaseLayout<T> : LayoutF<View> {
public Dictionary<T, View> Views { get; private set; } = new Dictionary<T, View>();
internal Dictionary<T, Rectangle> ViewsRects = new Dictionary<T, Rectangle>();
public static readonly BindableProperty ViewTypeProperty = BindableProperty.CreateAttached(@"ViewType", typeof(T), typeof(BaseLayout<T>), default(T));
public static T GetViewType(BindableObject bindable) {
var type = (T) bindable.GetValue(ViewTypeProperty);
return type;
}
public static void SetViewType(BindableObject bindable, T type) {
bindable.SetValue(ViewTypeProperty, type);
}
public BaseLayout<T> Add(T type, View view) {
if (Views.ContainsKey(type)) return this;
SetViewType(view, type);
Children.Add(view);
Views.Add(type, view);
return this;
}
public BaseLayout<T> Add(params Tuple<T, View>[] views) {
foreach (var view in views) {
Add(view.Item1, view.Item2);
}
return this;
}
protected override void OnChildAdded(Element child) {
base.OnChildAdded(child);
child.PropertyChanged += ChildOnPropertyChanged;
}
void ChildOnPropertyChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == IsVisibleProperty.PropertyName) {
InvalidateLayout();
}
}
protected override void OnChildRemoved(Element child) {
child.PropertyChanged -= ChildOnPropertyChanged;
base.OnChildRemoved(child);
}
public virtual bool ProcessChildMeasureInvalidated { get; set; } = true;
protected override void OnChildMeasureInvalidated() {
base.OnChildMeasureInvalidated();
if (ProcessChildMeasureInvalidated) InvalidateLayout();
}
protected override void OnPropertyChanged(string propertyName = null) {
base.OnPropertyChanged(propertyName);
if (propertyName == IsVisibleProperty.PropertyName) {
InvalidateLayout();
}
}
internal double MaxWidth { get; set; }
internal double MaxHeight { get; set; }
internal double MinWidth { get; set; }
internal double MinHeight { get; set; }
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) {
Prepare(widthConstraint, heightConstraint);
if (double.IsPositiveInfinity(MinWidth)) MinWidth = 1;
if (double.IsPositiveInfinity(MinHeight)) MinHeight = 1;
if (double.IsPositiveInfinity(MaxWidth)) MaxWidth = 1;
if (double.IsPositiveInfinity(MaxHeight)) MaxHeight = 1;
return new SizeRequest(new Size(MinWidth, MinHeight), new Size(MaxWidth, MaxHeight));
}
protected override void LayoutChildren(double x, double y, double width, double height) {
Prepare(width, height);
var viewsRects = ViewsRects;
try {
foreach (var viewPair in Views) {
if (!viewsRects.ContainsKey(viewPair.Key)) continue;
var view = Views[viewPair.Key];
if (!view.IsVisible) continue;
var rect = viewsRects[viewPair.Key];
rect.X += x;
rect.Y += y;
if (double.IsNaN(rect.Width)) continue;
if (double.IsNaN(rect.Height)) continue;
LayoutChildIntoBoundingRegion(Views[viewPair.Key], rect);
}
}
catch {
// ignored
}
}
void Prepare(double width, double height) {
if (Views == null || !Views.Any() || Views.Count != Children.Count) Views = Children.ToDictionary(GetViewType);
ViewsRects = CalculateRects(width, height, Views);
}
public virtual Dictionary<T, Rectangle> CalculateRects(double width, double height, Dictionary<T, View> views) {
return new Dictionary<T, Rectangle>();
}
public SizeRequest GetViewSize(T type, double width, double height = double.PositiveInfinity, MeasureFlags flags = MeasureFlags.IncludeMargins) {
return GetViewByType(type)?.Measure(width, height, flags) ?? new SizeRequest();
}
public Size GetViewSizeRequest(T type) {
var view = GetViewByType(type);
return view == null ? new Size() : new Size(view.WidthRequest, view.HeightRequest);
}
public bool GetViewTypeVisible(T type) {
var view = GetViewByType(type);
return view?.IsVisible ?? false;
}
public bool HasView(T type) {
var view = GetViewByType(type);
return view != null && view.IsVisible;
}
public bool HasLabel(T type) {
return GetViewByType(type) is Label view && view.IsVisible && !string.IsNullOrWhiteSpace(view.Text);
}
public bool ContainView(T type) {
return Views.ContainsKey(type);
}
public void RemoveViewByType(T type) {
var view = Children?.FirstOrDefault(v => GetViewType(v)?.Equals(type) ?? false);
if (view != null) {
Views.Remove(type);
ViewsRects.Remove(type);
Children.Remove(view);
}
}
public View GetViewByType(T type) {
var view = Children?.FirstOrDefault(v => GetViewType(v)?.Equals(type) ?? false);
return view;
}
}
}
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace Project.UI.Layouts
{
public class RightIconLayout : BaseLayout<RightIconLayout.ViewTypes> {
public enum ViewTypes {
Default,
Icon
}
public static readonly BindableProperty IsOverlapProperty = BindableProperty.Create(nameof(IsOverlap), typeof(bool), typeof(RightIconLayout), true, BindingMode.Default);
public bool IsOverlap
{
get => (bool) GetValue(IsOverlapProperty);
set => SetValue(IsOverlapProperty, value);
}
public override Dictionary<ViewTypes, Rectangle> CalculateRects(double width, double height, Dictionary<ViewTypes, View> views) {
var rects = base.CalculateRects(width, height, views);
var viewSize = GetViewSize(ViewTypes.Default, width);
var iconSize = GetViewSize(ViewTypes.Icon, width);
var h = Math.Max(viewSize.Request.Height, iconSize.Request.Height);
var viewWidth = IsOverlap ? width : width - iconSize.Request.Width;
rects.Add(ViewTypes.Default, new Rectangle(0,0,viewWidth, h));
rects.Add(ViewTypes.Icon, new Rectangle(width- iconSize.Request.Width, h/2d- iconSize.Request.Height/2d, iconSize.Request.Width, iconSize.Request.Height));
MinHeight = h;
MaxHeight = h;
MinWidth = width;
MaxWidth = width;
return rects;
}
}
}
@KirillAshikhmin
Copy link
Author

Так базовый лейаут рассчитан на известное количество вьюх с определенными типами. Но так почти всегда и бывает. Каждой вьюхе задается свойство ViewType, далее при CalculateRects считается/указывается размер который эта вьха должна занимать (можно захардкодить значение, а можно у вьюхи запросить сколько ей надо) и задается значение в возвращаемый словарь rects.
Значения MinHeight MaxHeight MinWidth MaxWidth нужны для того, что бы лейаут вернул их (в методе OnMeasure), для того, что бы другие лейауты, которые запрашивают размер этого получали верный размер (особенно для ScrollView это важно).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment