第二十六章:自定义布局(十)

简介: 不允许无约束的尺寸!有时您希望在屏幕上看到所有内容,可能是一系列大小统一的行和列。您可以使用带有星号定义的所有行和列定义的Grid执行类似的操作,以使它们具有相同的大小。唯一的问题是您可能还希望行数和列数基于子节点数,并针对屏幕空间的最佳使用进行了优化。

不允许无约束的尺寸!
有时您希望在屏幕上看到所有内容,可能是一系列大小统一的行和列。您可以使用带有星号定义的所有行和列定义的Grid执行类似的操作,以使它们具有相同的大小。唯一的问题是您可能还希望行数和列数基于子节点数,并针对屏幕空间的最佳使用进行了优化。
让我们编写一个名为UniformGridLayout的自定义布局。与WrapLayout一样,UniformGridLayout需要Orientation,RowSpacing和ColumnSpacing属性,所以让我们通过从WrapLayout派生UniformGridLayout来消除重新定义属性所涉及的一些工作。
因为UniformGridLayout对无约束维度没有意义,所以OnSizeRequest覆盖检查无限约束并在遇到这样的事情时引发异常。
为了帮助UniformGridLayout优化屏幕空间的使用,我们给它一个名为AspectRatio的属性AspectRatio。此属性指示子项的预期宽高比为double值。例如,值1.33表示纵横比为4:3,宽度比高度长33%。但是,默认情况下,UniformGridLayout将计算其子项的平均宽高比。
此AspectRatio结构类似于为Grid类定义的GridLength结构,因为它允许双值和“自动”选项强制UniformGridLayout计算平均宽高比:

namespace Xamarin.FormsBook.Toolkit
{
    [TypeConverter(typeof(AspectRatioTypeConverter))]
    public struct AspectRatio
    {
        public AspectRatio(double value)
        {
            if (value < 0)
                throw new FormatException("AspectRatio value must be greater than 0, " +
                "or set to 0 to indicate Auto");
            Value = value;
        }
        public static AspectRatio Auto
        {
            get
            {
                return new AspectRatio();
            }
        }
        public double Value { private set; get; }
        public bool IsAuto { get { return Value == 0; } }
        public override string ToString()
        {
            return Value == 0 ? "Auto" : Value.ToString();
        }
    }
}

“自动”选项由Value属性0指示。使用UniformGridLayout的应用程序可以使用无参数构造函数创建此类AspectRatio值,或者将0传递给定义的构造函数,或者使用静态Auto属性。
我确定您希望能够在XAML中设置AspectRatio属性,因此结构将使用TypeConverter属性进行标记。 AspectRatioTypeConverter类可以处理带有“Auto”或double的字符串:

namespace Xamarin.FormsBook.Toolkit
{
    public class AspectRatioTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(Type sourceType)
        {
            return sourceType == typeof(string);
        }
        public override object ConvertFrom(CultureInfo culture, object value)
        {
            string str = value as string;
            if (String.IsNullOrWhiteSpace(str))
                return null;
            str = str.Trim();
            double aspectValue;
            if (String.Compare(str, "auto", StringComparison.OrdinalIgnoreCase) == 0)
                return AspectRatio.Auto;
            if (Double.TryParse(str, NumberStyles.Number,
            CultureInfo.InvariantCulture, out aspectValue))
                return new AspectRatio(aspectValue);
            throw new FormatException("AspectRatio must be Auto or numeric");
        }
    }
}

UniformGridLayout类派生自WrapLayout,仅用于继承WrapLayout定义的三个可绑定属性。 对于这些属性,UniformGridLayout添加了AspectRatio属性:

namespace Xamarin.FormsBook.Toolkit
{
    public class UniformGridLayout : WrapLayout
    {
        public static readonly BindableProperty AspectRatioProperty =
        BindableProperty.Create(
        "AspectRatio",
        typeof(AspectRatio),
        typeof(UniformGridLayout),
        AspectRatio.Auto,
        propertyChanged: (bindable, oldvalue, newvalue) =>
        {
            ((UniformGridLayout)bindable).InvalidateLayout();
        });
        public AspectRatio AspectRatio
        {
            set { SetValue(AspectRatioProperty, value); }
            get { return (AspectRatio)GetValue(AspectRatioProperty); }
        }
    __
    }
}

OnSizeRequest覆盖首先检查约束是否是无限的,如果是这种情况则引发异常。 否则,它会请求整个区域,除非它没有可见的孩子:

namespace Xamarin.FormsBook.Toolkit
{
    public class UniformGridLayout : WrapLayout
    {
        __
        protected override SizeRequest OnSizeRequest(double widthConstraint,
        double heightConstraint)
        {
            if (Double.IsInfinity(widthConstraint) || Double.IsInfinity(heightConstraint))
                throw new InvalidOperationException(
                "UniformGridLayout cannot be used with unconstrained dimensions.");
            // Just check to see if there aren't any visible children.
            int childCount = 0;
            foreach (View view in Children)
                childCount += view.IsVisible ? 1 : 0;
            if (childCount == 0)
                return new SizeRequest();
            // Then request the entire (noninfinite) size.
            return new SizeRequest(new Size(widthConstraint, heightConstraint));
        }
        __
    }
}

困难的部分是LayoutChildren覆盖,它有三个主要部分:

namespace Xamarin.FormsBook.Toolkit
{
    public class UniformGridLayout : WrapLayout
    {
        __
        protected override void LayoutChildren(double x, double y, double width, double height)
        {
            int childCount = 0;
            foreach (View view in Children)
                childCount += view.IsVisible ? 1 : 0;
            if (childCount == 0)
                return;
            double childAspect = AspectRatio.Value;
            // If AspectRatio is Auto, calculate an average aspect ratio
            if (AspectRatio.IsAuto)
            {
                int nonZeroChildCount = 0;
                double accumAspectRatio = 0;
                foreach (View view in Children)
                {
                    if (view.IsVisible)
                    {
                        SizeRequest sizeRequest = view.GetSizeRequest(Double.PositiveInfinity,

                        Double.PositiveInfinity);
                        if (sizeRequest.Request.Width > 0 && sizeRequest.Request.Height > 0)
                        {
                            nonZeroChildCount++;
                            accumAspectRatio += sizeRequest.Request.Width /
                            sizeRequest.Request.Height;
                        }
                    }
                }
                if (nonZeroChildCount > 0)
                {
                    childAspect = accumAspectRatio / nonZeroChildCount;
                }
                else
                {
                    childAspect = 1;
                }
            }
            int bestRowsCount = 0;
            int bestColsCount = 0;
            double bestUsage = 0;
            double bestChildWidth = 0;
            double bestChildHeight = 0;
            // Test various possibilities of the number of columns.
            for (int colsCount = 1; colsCount <= childCount; colsCount++)
            {
                // Find the number of rows for that many columns.
                int rowsCount = (int)Math.Ceiling((double)childCount / colsCount);
                // Determine if we have more rows or columns than we need.
                if ((rowsCount - 1) * colsCount >= childCount ||
                rowsCount * (colsCount - 1) >= childCount)
                {
                    continue;
                }
                // Get the aspect ratio of the resultant cells.
                double cellWidth = (width - ColumnSpacing * (colsCount - 1)) / colsCount;
                double cellHeight = (height - RowSpacing * (rowsCount - 1)) / rowsCount;
                double cellAspect = cellWidth / cellHeight;
                double usage = 0;
                // Compare with the average aspect ratio of the child.
                if (cellAspect > childAspect)
                {
                    usage = childAspect / cellAspect;
                }
                else
                {
                    usage = cellAspect / childAspect;
                }
                // If we're using more space, save the numbers.
                if (usage > bestUsage)
                {
                    bestRowsCount = rowsCount;
                    bestColsCount = colsCount;
                    bestUsage = usage;
                    bestChildWidth = cellWidth;
                    bestChildHeight = cellHeight;
                }
            }
            int colIndex = 0;
            int rowIndex = 0;
            double xChild = x;
            double yChild = y;
            foreach (View view in Children)
            {
                // Position and size the child.
                LayoutChildIntoBoundingRegion(view,
                new Rectangle(xChild, yChild, bestChildWidth, bestChildHeight));
                // Increment the coordinates and indices.
                if (Orientation == WrapOrientation.HorizontalThenVertical)
                {
                    xChild += bestChildWidth + ColumnSpacing;
                    if (++colIndex == bestColsCount)
                    {
                        colIndex = 0;
                        xChild = x;
                        yChild += bestChildHeight + RowSpacing;
                    }
                }
                else // Orientation == WrapOrientation.VerticalThenHorizontal
                {
                    yChild += bestChildHeight + RowSpacing;
                    if (++rowIndex == bestRowsCount)
                    {
                        rowIndex = 0;
                        xChild += bestChildWidth + ColumnSpacing;
                        yChild = y;
                    }
                }
            }
        }
    }
}

如果已指定“自动”选项,则第一部分计算子项的平均纵横比。
第二部分循环遍历行和列的不同组合,并确定哪种组合可以最好地利用可用空间。 关键的计算是这样的:

if (cellAspect > childAspect)
{
    usage = childAspect / cellAspect;
}
else
{
    usage = cellAspect / childAspect;
}

例如,假设基于所有子项的平均值计算的childAspect为1.5,对于行和列的特定组合,cellAspect值为2.纵横比为1.5的子项将仅占用75% 一个纵横比为2的单元格。如果cellAspect是0.75,那么该子单元将只占该单元格的50%。
然后,第三部分为每个孩子提供网格中的大小和位置。 这需要基于Orientation属性的不同处理。
我们来试试吧。 PhotoGrid XAML文件使用UniformGridLayout填充页面(iPhone上的顶部填充除外),其中设置了两个属性:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit=
                 "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
             x:Class="PhotoGrid.PhotoGridPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    <toolkit:UniformGridLayout x:Name="uniformGridLayout"
                               Orientation="VerticalThenHorizontal"
                               AspectRatio="Auto" />
</ContentPage>

代码隐藏文件几乎与PhotoWrap中的相同,结果如下:
2019_05_21_094518
同样,当Image元素加载位图时,您可能会看到行和列的某些移位。
在Windows桌面上运行它并更改窗口的大小和宽高比以查看位图如何重新排序为行和列是很有趣的。 这也是检查代码中的一些错误的好方法。

目录
相关文章
|
6月前
|
定位技术 iOS开发
自动布局xib页面的机型匹配精典问题及解决方案
自动布局xib页面的机型匹配精典问题及解决方案
42 0
|
iOS开发
iOS动画开发之三——UIView的转场切换
iOS动画开发之三——UIView的转场切换
378 0
|
存储 缓存 JavaScript
第二十六章:自定义布局(九)
编码的一些规则从上面的讨论中,您可以为自己的Layout 衍生物制定几个规则:规则1:如果布局类定义了诸如间距或方向等属性,则这些属性应由可绑定属性支持。 在大多数情况下,这些可绑定属性的属性更改处理程序应调用InvalidateLayout。
2070 0
|
JavaScript Android开发
第二十六章:自定义布局(八)
失效假设您已在页面上组装了一些布局和视图,并且由于某种原因,代码隐藏文件(或者可能是触发器或行为)会更改Button的文本,或者可能只是字体大小或属性。 该更改可能会影响按钮的大小,这可能会对页面其余部分的布局更改产生连锁反应。
3421 0
|
JavaScript Android开发
第二十六章:自定义布局(七)
垂直和水平定位简化在VerticalStack中,LayoutChildren覆盖的末尾是一个switch语句,它有助于根据子级的HorizontalOptions属性设置水平定位每个子级。 这是整个方法: public class VerticalStack : Layout<View> { ...
872 0
|
JavaScript Android开发
第二十六章:自定义布局(十二)
更多附加的可绑定属性附加的可绑定属性也可以在XAML中设置并使用Style设置。 为了了解它是如何工作的,让我们检查一个名为CartesianLayout的类,它模仿一个二维的,四象限的笛卡尔坐标系。
540 0
|
JavaScript Android开发
第二十六章:自定义布局(六)
从Layout派生 我们现在拥有足够的知识来创建我们自己的布局类。布局中涉及的大多数公共和受保护方法都是由非泛型布局类定义的。 Layout 类派生自Layout,并将泛型类型约束为View及其派生类。
765 0
|
JavaScript Android开发
第二十六章:自定义布局(十一)
重叠的子项Layout 类可以在其子项上调用Layout方法,以便子项重叠吗?是的,但这可能会在你的脑海中提出另一个问题:什么决定孩子们的呈现顺序?哪些孩子看似坐在前台,可能部分或完全掩盖了背景中显示的其他孩子?在某些图形环境中,程序员可以访问名为Z-index的值。
643 0
|
JavaScript Android开发 iOS开发
第二十六章:自定义布局(五)
内视过程中本章到目前为止提供的大部分信息都是从包含派生自各种元素(如StackLayout,ScrollView和Label)的类的测试程序汇编而来,覆盖虚拟方法(如GetSizeRequest,OnSizeRequest,OnSizeAllocated和LayoutChildren) ,并使用System.Diagnostics命名空间中的Debug.WriteLine方法在Visual Studio或Xamarin Studio的“输出”窗口中显示信息。
762 0