Win10开发:瀑布流布局

参考文章:通过Measure & Arrange实现UWP瀑布流布局

“所谓瀑布流布局,是多列布局的一种形式,列中元素等比缩放使得自身与列等宽,每列再以StackPanel的形式布局,下一个元素自动排布到最短的那一列上。”

效果图:链接

参考文章中做了许多讲解,本文就不做重复工作了。但是原文并没有一个完整的Demo示例

下文将一步步带你实现,代码部分基本与参考文章一样,不同的地方会做讲解

具体实现

1、新建项目工程WaterfallDemo(废话,没有工程怎么show demo)

2、新建类WaterfallPanel,继承自Panel。这个类设计好之后以后可以多次使用

3、在类WaterfallPanel中重载MeasureOverride函数,代码如下:

protected override Size MeasureOverride(Size availableSize)
        {
            // 记录每个流的长度。因为我们用选取最短的流来添加下一个元素。
            KeyValuePair<double, int>[] flowLens = new KeyValuePair<double, int>[ColumnNum];
            foreach (int idx in Enumerable.Range(0, ColumnNum))
            {
                flowLens[idx] = new KeyValuePair<double, int>(0.0, idx);
            }

            // 我们就用2个纵向流来演示,获取每个流的宽度。
            double flowWidth = availableSize.Width / ColumnNum;

            // 为子控件提供沿着流方向上,无限大的空间
            Size elemMeasureSize = new Size(flowWidth, double.PositiveInfinity);

            foreach (UIElement elem in Children)
            {
                // 让子控件计算它的大小。
                elem.Measure(elemMeasureSize);
                Size elemSize = elem.DesiredSize;

                double elemLen = elemSize.Height;
                var pair = flowLens[0];

                // 子控件添加到最短的流上,并重新计算最短流。
                // 因为我们为了求得流的长度,必须在计算大小这一步时就应用一次布局。但实际的布局还是会在Arrange步骤中完成。
                flowLens[0] = new KeyValuePair<double, int>(pair.Key + elemLen, pair.Value);
                flowLens = flowLens.OrderBy(p => p.Key).ToArray();
            }
            return new Size(availableSize.Width, flowLens.Last().Key);
        }

4、在类WaterfallPanel中重载ArrangeOverride函数,代码如下:

protected override Size ArrangeOverride(Size finalSize)
        {
            // 同样记录流的长度。
            KeyValuePair<double, int>[] flowLens = new KeyValuePair<double, int>[ColumnNum];

            double flowWidth = finalSize.Width / ColumnNum;

            // 要用到流的横坐标了,我们用一个数组来记录(其实最初是想多加些花样,用数组来方便索引横向偏移。不过本例中就只进行简单的乘法了)
            double[] xs = new double[ColumnNum];

            foreach (int idx in Enumerable.Range(0, ColumnNum))
            {
                flowLens[idx] = new KeyValuePair<double, int>(0.0, idx);
                xs[idx] = idx * flowWidth;
            }

            foreach (UIElement elem in Children)
            {
                // 直接获取子控件大小。
                Size elemSize = elem.DesiredSize;
                double elemLen = elemSize.Height;

                var pair = flowLens[0];
                double chosenFlowLen = pair.Key;
                int chosenFlowIdx = pair.Value;

                // 此时,我们需要设定新添加的空间的位置了,其实比measure就多了一个Point信息。接在流中上一个元素的后面。
                Point pt = new Point(xs[chosenFlowIdx], chosenFlowLen);

                // 调用Arrange进行子控件布局。并让子控件利用上整个流的宽度。
                elem.Arrange(new Rect(pt, new Size(flowWidth, elemSize.Height)));

                // 重新计算最短流。
                flowLens[0] = new KeyValuePair<double, int>(chosenFlowLen + elemLen, chosenFlowIdx);
                flowLens = flowLens.OrderBy(p => p.Key).ToArray();
            }

            // 直接返回该方法的参数。
            return finalSize;
        }

5、步骤3和4的代码与参考文章中有一处不同,认真阅读代码的应该可以发现。

没错,就是多了ColumnNum变量,因为我们要让这个控件扩张性更高,不能局限于两列布局,因此把列数作为变量ColumnNum

public int ColumnNum
        {
            get { return (int)GetValue(ColumnCountProperty); }
            set { SetValue(ColumnCountProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ColumnCount.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColumnCountProperty =
            DependencyProperty.Register("ColumnNum", typeof(int), typeof(WaterfallPanel), new PropertyMetadata(2));


这么复杂的代码记不住怎么办,因为我们用的是全宇宙最强IDE,所以这里有个小技巧:

在空白处输入propdp,然后双击键盘Tab键,就会默认出现一堆代码,在这些代码上做些修改就搞定了。

MyProperty替换成你自定义的名称;ownerclass替换成当前类名,此处为WaterfallPanel;PropertyMetadata填写默认值,我填的是2

以上,自定义的Panel类就完成了。

6、新建类:MyItem,新增三个属性

public double Height { get; set; }
public string Text { get; set; }
public string Url { get; set; }

7、XAML页面布局:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ItemsControl x:Name="ic">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <!-- 使用我们的自定义布局 -->
                    <local:WaterfallPanel ColumnNum="3"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.Template>
                <ControlTemplate>
                    <ScrollViewer>
                        <ItemsPresenter/>
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border Margin="10" Height="{Binding Height}"
                    BorderBrush="{ThemeResource SystemControlBackgroundAccentBrush}"
                    BorderThickness="1" HorizontalAlignment="Stretch">
                        <TextBlock Text="{Binding Text}"/>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>

可以看到我们的自定义控件WaterfallPanel的ColumnNum属性是可以设置的,我们设为3,当然也可以是其他值


8、构造函数中为ItemControl控件设置ItemSource

 public MainPage()
        {
            this.InitializeComponent();

            Random r = new Random(DateTime.Now.Millisecond);
            ic.ItemsSource = Enumerable.Range(0, 30).Select(i => new MyItem
            {
                Text = i.ToString(),
                Height = r.Next(100, 300),
                Url = string.Format("ms-appx:///Assets/Images/{0}.jpg", i)
            });
        }

Height属性我们用一个随机数产生,是为了让每个Item的高度不同,体现瀑布流的效果

Url是后面我们用来展示图片的效果,这里暂时没用

9、运行程序,效果如图



10、下面接着来实现图片的瀑布流展示。有的读者应该能自己实现了,还不会的就继续看下去吧。

我反正是遇到一些小困难,听我慢慢道来

首先先修改一下XAML,把DataTemplate中的TextBlock换成Image

<Image Source="{Binding Url}"/>

由于上例的Height属性是用一个随机数产生,如果我们要展示图片,自然不能用这个随机的Height

否则效果就成了这样——每张图片的宽度参差不齐



11、首先想到的方法是获取原始图片的宽和高,然后把宽设置为每个流的宽度,高度根据宽度等比例缩放

一开始想用如下代码来获取某张图片的宽和高

string url = "ms-appx:///Assets/Images/1.jpg";
BitmapImage bmp = new BitmapImage(new Uri(url));
//bmp.PixelWidth
//bmp.PixelHeight
结果并不能如愿,PixelWidth和PixelHeight都为0
搜索网络在stackoverflow中找到同样的问题: 问题链接

问题下的回答给了个方法

var bitmapImage = new BitmapImage(uri);
bitmapImage.ImageOpened += (sender, e) => 
{
    Debug.WriteLine("Width: {0}, Height: {1}",
        bitmapImage.PixelWidth, bitmapImage.PixelHeight);
};
image.Source = bitmapImage;
但是并不好用,还是靠自己吧(不知道读者们有没有其他的办法)

既然无法设置合适的图片高度值,那就干脆不用。去掉Border的Height属性就大功告成

请看效果图



12、如果把把ItemsControl换成ListView,再进行简单的Style设置,就可以让瀑布流与ListView的特性融合。这里就不做讲解了。


Demo源码下载: https://github.com/hebecherish/WaterfallDemo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值