Xamarin.Forms以Xamarin.Android和Xamarin.iOS等为基础,自己实现了一整套比较完整的UI框架,包含了绝大多数常用的控件,如下图

虽然XF(Xamarin.Forms简称XF,下同)为我们提供大这么多的控件,但在实际使用中,会发现这些控件的可定制性特别差,基本上都需要里利用Renderer来做一些修改。为了实现我们的需求,有两种办法:

  1. Renderer
  2. 自定义控件/布局

1.Renderer

XF中的所有控件,实际都是通过Renderer来实现的,利用Renderer,直接实例化相应的原生控件,每一个XF控件在各个平台都对应一个原生控件,具体可以查看这儿:RendererBase

利用Renderer,需要你了解原生控件的使用,所以引用一句话就是:

跨平台不代表不用学各个平台

笔者也是对安卓和iOS了解不多,正在摸索学习中

2.自定义控件/布局

这种相对来说比较简单,却比较繁琐,并且最终效果不会太好,包括性能和UI两方面。但是还是能适应一些常用场景。

关于布局基础知识方面可以查看这位作者的一片文章:Xamarin.Forms自定义布局基础

在使用中会发现XF的自定义布局和UWP的非常相似,常用的方法有两个

public SizeRequest Measure(double widthConstraint, double heightConstraint, MeasureFlags flags = MeasureFlags.None); //计算元素大小

public void Layout(Rectangle bounds);//为元素实际布局,确定其位置和大小

Measure方法的两个参数,表示父元素能为子元素提供的空间大小,返回值则表示子元素计算出自己实际需要的空间大小。

Layout方法的参数表示父元素给子元素提供的布局位置,包含XY坐标和大小四个参数。

现在考虑瀑布流布局的特点:
  1. 父元素大小确定,至少宽度和高度中有一个值确定(通常表现为整个页面大小)
  2. 子元素排列表现为按行排列或者按列排列
  • 按行排列时:子元素的高是一个定值,宽度跟具具体情况可变

  • 按列排列时:子元素的宽是一个定值,高度跟具具体情况可变

瀑布流的常用场景
  1. 图片展示

下面以的Demo展示一个按列布局的图片展示瀑布流布局

主要有两个方法

    private double _maxHeight;

    /// <summary>
/// 计算父元素需要的空间大小
/// </summary>
/// <param name="widthConstraint">可供布局的宽度</param>
/// <param name="heightConstraint">可供布局的高度</param>
/// <returns>实际需要的布局大小</returns>
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
double[] colHeights = new double[Column];
double allColumnSpacing = ColumnSpacing * (Column - 1);
columnWidth = (widthConstraint - allColumnSpacing) / Column;
foreach (var item in this.Children)
{
var measuredSize = item.Measure(columnWidth, heightConstraint, MeasureFlags.IncludeMargins);
int col = 0;
for (int i = 1; i < Column; i++)
{
if (colHeights[i] < colHeights[col])
{
col = i;
}
}
colHeights[col] += measuredSize.Request.Height + RowSpacing;
}
_maxHeight = colHeights.OrderByDescending(m => m).First();
return new SizeRequest(new Size(widthConstraint, _maxHeight));
}

OnMeasured方法在布局开始前被调用,在这个方法中,我们遍历所有的子元素,通过调用子元素的Measure方法,计算出所有子元素需要的布局大小,然后按列累加所有的高度,最后选取高度的最大值,这个最大值就是父元素的布局高度,在按列布局中,宽度是确定的。

    protected override void LayoutChildren(double x, double y, double width, double height)
{ double[] colHeights = new double[Column];
double allColumnSpacing = ColumnSpacing * (Column - 1);
columnWidth = (width- allColumnSpacing )/ Column;
foreach (var item in this.Children)
{
var measuredSize=item.Measure(columnWidth, height, MeasureFlags.IncludeMargins);
int col = 0;
for (int i = 1; i < Column; i++)
{
if (colHeights[i] < colHeights[col])
{
col = i;
}
}
item.Layout(new Rectangle(col * (columnWidth + ColumnSpacing), colHeights[col], columnWidth, measuredSize.Request.Height)); colHeights[col] += measuredSize.Request.Height+RowSpacing;
}
}

LayoutChildren方法在OnMeasured方法后调用,通过调用子元素的Layou方法,用于对所有子元素布局。

至此,瀑布流和的新逻辑基本完成了,实际很简单。接下来就是让瀑布流支持数据绑定,实现动态添加删除子元素。

为了支持数据绑定,实现一个依赖属性ItemsSource,当ItemsSource被赋值或者值发生变化的时候,重新布局,根据ItemsSource的内容重新布局

    public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IList), typeof(FlowLayout), null,propertyChanged: ItemsSource_PropertyChanged);
public IList ItemsSource
{
get { return (IList)this.GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
} private static void ItemsSource_PropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var flowLayout = (FlowLayout)bindable;
var newItems = newValue as IList;
var oldItems = oldValue as IList;
var oldCollection = oldValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= flowLayout.OnCollectionChanged;
} if (newValue == null)
{
return;
} if (newItems == null)
return;
if(oldItems == null||newItems.Count!= oldItems.Count)
{
flowLayout.Children.Clear();
for (int i = 0; i < newItems.Count; i++)
{
var child = flowLayout.ItemTemplate.CreateContent();
((BindableObject)child).BindingContext = newItems[i];
flowLayout.Children.Add((View)child);
} } var newCollection = newValue as INotifyCollectionChanged;
if (newCollection != null)
{
newCollection.CollectionChanged += flowLayout.OnCollectionChanged;
} flowLayout.UpdateChildrenLayout();
flowLayout.InvalidateLayout();
} protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
} private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
this.Children.RemoveAt(e.OldStartingIndex);
this.UpdateChildrenLayout();
this.InvalidateLayout();
} if (e.NewItems == null)
{
return;
}
for (int i = 0; i < e.NewItems.Count; i++)
{
var child = this.ItemTemplate.CreateContent();
((BindableObject)child).BindingContext = e.NewItems[i];
this.Children.Add((View)child);
} this.UpdateChildrenLayout();
this.InvalidateLayout();
}
}

ItemsSource_PropertyChanged方法在ItemsSource属性被赋值的时候调用,在此方法中,根据自定义的DataTemplate,创建一个视图(View),设置其数据绑定上下文为对应的Item,然后添加到瀑布流布局的Children中。

var child = this.ItemTemplate.CreateContent(); ((BindableObject)child).BindingContext = e.NewItems[i]; this.Children.Add((View)child);

注意到,在数据绑定中,更加常见的场景是:ItemsSource只赋值一次,以后ItemsSource中的值修改,直接能在布局中表现出来。

这就要求ItemsSource的数据源必须实现INotifyCollectionChanged这个接口,在.Net中,ObservableCollection是已经封装好的,实现了这个接口的一个开箱即用的集合类。所以在ItemsSource的值改变的时候,需要订阅对数据源CollectionChanged事件,以便于在集合中元素添加或删除的时候重新布局。

项目地址:Github

Xamarin自定义布局系列——瀑布流布局的更多相关文章

  1. Xamarin自定义布局系列——ListView的一个自定义实现ItemsControl(横向列表)

    在以前写UWP程序的时候,了解到在ListView或者ListBox这类的列表空间中,有一个叫做ItemsPannel的属性,它是所有列表中子元素实际的容器,如果要让列表进行横向排列,只需要在Xaml ...

  2. Xamarin自定义布局系列——支持无限滚动的自动轮播视图CarouselView

    背景简述 自动轮播视图(CarouselView)在现在App中的地位不言而喻,绝大多数的App中都有类似的视图,无论是WebApp还是Native App.在安卓.iOS以及Windows(UWP) ...

  3. Xamarin自定义布局系列——PivotPage,多页面切换控件

    PivotPage ---- 多页面切换控件 PivotPage是一个多页面切换控件,类似安卓中的ViewPager和UWP中的Pivot枢轴控件. 起初打算直接通过ScrollView+StackL ...

  4. OC - 29.自定义布局实现瀑布流

    概述 瀑布流是电商应用展示商品通常采用的一种方式,如图示例 瀑布流的实现方式,通常有以下几种 通过UITableView实现(不常用) 通过UIScrollView实现(工作量较大) 通过UIColl ...

  5. iOS开发进阶 - 自定义UICollectionViewLayout实现瀑布流布局

    移动端访问不佳,请访问我的个人博客 最近项目中需要用到瀑布流的效果,但是用UICollectionViewFlowLayout又达不到效果,自己动手写了一个瀑布流的layout,下面是我的心路路程 先 ...

  6. 详细分享UICollectionView的自定义布局(瀑布流, 线性, 圆形…)

    前言: 本篇文章不是分享collectionView的详细使用教程, 而是属于比较’高级’的collectionView使用技巧, 阅读之前, 我想你已经很熟悉collectionView的基本使用, ...

  7. 详细分享UICollectionView的自定义布局(瀑布流, 线性, 圆形...)

    前言: 本篇文章不是分享collectionView的详细使用教程, 而是属于比较'高级'的collectionView使用技巧, 阅读之前, 我想你已经很熟悉collectionView的基本使用, ...

  8. collectionView布局原理及瀑布流布局方式

    一直以来都想研究瀑布流的具体实现方法(起因是因为一则男女程序员应聘的笑话,做程序的朋友应该都知道).最近学习到了瀑布流的实现方法,瀑布流的实现方式有多种,这里应用collectionView来重写其U ...

  9. collectionView布局原理及瀑布流布局方式--备用

    最近学习到了瀑布流的实现方法,瀑布流的实现方式有多种,这里应用collectionView来重写其UICollectionViewLayout进行布局是最为简单方便的.但再用其布局之前必须了解其布局原 ...

随机推荐

  1. 采用highchart js+flot+rrd生成cpu、mem状态监控图

    HTML <script type="text/javascript" src="../static/js/jquery-1.8.0.min.js"> ...

  2. mysql数据库update时只更新部分数据方法

    需求:更新url中最一个字符的'-1'改为'-5',前面的内容保持不变 url列的内容如下:http://h5game.ecs.cedarmg.com/a/captal/dispther.do?dev ...

  3. 使用 GitHub, Jekyll 打造自己的免费独立博客

    使用 GitHub, Jekyll 打造自己的免费独立博客 GitHub是一个代码托管网站,现在很多开源项目都放在GitHub上. 利用GitHub,可以让全球各地的程序员们一起协作开发.GitHub ...

  4. HTML5 JavaScript API

    W3C官方指定的HTML5规范已经修订了很多次,HTML5这个概念是与javascript API相捆绑的语义标记.在过去这些年中,HTML5这个词所指代的范围正以惊人的的速度膨胀,某种程度上已经成为 ...

  5. Hadoop权威指南:HDFS-目录,查询文件系统,删除文件

    Hadoop权威指南:HDFS-目录,查询文件系统,删除文件 [TOC] 目录 FileSystem实例提供了创建目录的方法 public boolean mkdirs(Path f) throws ...

  6. linux 进程间通信 之fifo

    上一篇博客已经介绍了一种进程间通信的方式,但是那只是针对于有血缘关系的进程,即父子进程间的通信,那对于没有血缘关系的进程,那要怎么通信呢?  这就要创建一个有名管道,来解决无血缘关系的进程通信, fi ...

  7. 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数

    关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...

  8. Baidu图表插件--Eharts使用(柱状图)

    官网链接:http://echarts.baidu.com/index.html 官网的demo,api都很详细:我就分享下我的学习步骤 首先定义一个显示图标的区域: <div id=" ...

  9. Python package install血泪史

    [前言][絮絮叨叨篇]:说实话,不是第一次安装Python库了,但是貌似没有特别顺利的时候,可能还是遇到的困难不够多咯.配置环境真是个糟心的事儿,不过作为菜鸟,还是得磨练磨练,毕竟某人云:" ...

  10. Redis之Sentinel高可用安装部署

    背景: 之前通过Redis Sentinel 高可用实现说明和Redis 复制.Sentinel的搭建和原理说明大致已经介绍了sentinel的原理和实现,本文再次做个简单的回顾.具体的信息见前面的两 ...