首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

Qml 实现瀑布流布局

编程知识
2024年09月10日 17:49

【写在前面】

最近在刷掘金的时候看到一篇关于瀑布流布局的文章,然鹅他们的实现都是前端的那套,就想着 Qml 有没有类似实现。

结果百度了一圈也没有( T_T Qml 凉了凉了 ),于是,我按照自己理解,简单实现了一个 Qml 版的瀑布流布局。

关于瀑布流:

瀑布流布局(Waterfall Layout),也被称为瀑布式布局或多栏自适应布局,是一种网页布局技术,它允许内容以多列的形式显示,类似于瀑布一样从上到下流动。这种布局方式特别适合于展示图片或卡片式内容,如图片库、新闻摘要、商品列表等。

瀑布流布局的特点包括:

  1. 多列显示:内容被分割成多列,每列可以独立滚动,使得页面可以展示更多的信息。
  2. 动态宽度:每列的宽度通常是固定的,而内容块(如图片或卡片)的宽度可以是动态的,以适应不同的屏幕大小。
  3. 不等高:内容块的高度可以不同,这样可以使布局看起来更加自然和有吸引力。
  4. 响应式:布局可以根据用户的屏幕尺寸自动调整,以提供最佳的浏览体验。
  5. 灵活性:内容块可以自由地在列之间流动,不需要严格的对齐。

【正文开始】

一个经典的瀑布流布局来自小红书:

image

而我们实现的 Qml 版效果图如下:

image

现在开始讲解思路:

首先考虑屏幕宽度,竖屏两列,横屏可以三列或者更多,应当根据宽度动态改变,然后便可以计算出列宽:

width: (flickable.width - flickable.spacing) / flickable.column

因此,其实未知的仅有卡片高度:

image

如图所示,卡片高度由三部分组成:【封面图片高度】+【标题高度】+【卡片信息高度】

height: coverRealHeight + titleHeight + infoHeight

现在有了宽高,接下来只要计算出 位置 (x, y) 即可:

    if (flickable.currentColumn == flickable.column) {
        flickable.currentColumn = 0;
        flickable.currentX = 0;
        for (let i = 0; i < flickable.column; i++) {
            flickable.currentY[i] += flickable.prevHeight[i];
        }
    }

    x = flickable.currentX;
    y = flickable.currentY[flickable.currentColumn];

    flickable.prevHeight[flickable.currentColumn] = Math.round(height + flickable.spacing);

    print(flickable.currentColumn, flickable.currentX, flickable.prevHeight, flickable.currentY);

    flickable.currentX += coverRealWidth + flickable.spacing;

    flickable.currentColumn++;

    let max = 0;
    for (let j = 0; j < flickable.column; j++) {
        max = Math.max(flickable.prevHeight[j] + flickable.currentY[j]);
    }

    flickable.contentHeight = max;
    

x 坐标计算思路是:从左往右依次增加一个卡片宽度,到达本行最后一个卡片时置零即可。

y 坐标计算思路是:记录下本行卡片高度数组 prevHeight[column],到达本行最后一个卡片时计算下行卡片 y 坐标数组 currentY[column],而首行则为 0。

至此,Rect (x, y, width, height) 全部已知,我们可以直接利用 Repeater 轻松实例化出来:

Repeater {
    id: repeater
    model: ListModel {
        id: listModel
        Component.onCompleted: {
            flickable.loadMore();
        }
    }
    delegate: Rectangle {
        id: rootItem
        width: (flickable.width - flickable.spacing) / flickable.column
        height: coverRealHeight + titleHeight + infoHeight
        radius: 4
        clip: true

        property real aspectRatio: coverWidth / coverHeight
        property real coverRealWidth: width
        property real coverRealHeight: width / aspectRatio
        property real titleWidth: width
        property real titleHeight: titleText.height
        property real infoWidth: width
        property real infoHeight: 50

        Component.onCompleted: {
            if (flickable.currentColumn == flickable.column) {
                flickable.currentColumn = 0;
                flickable.currentX = 0;
                for (let i = 0; i < flickable.column; i++) {
                    flickable.currentY[i] += flickable.prevHeight[i];
                }
            }

            x = flickable.currentX;
            y = flickable.currentY[flickable.currentColumn];

            flickable.prevHeight[flickable.currentColumn] = Math.round(height + flickable.spacing);

            print(flickable.currentColumn, flickable.currentX, flickable.prevHeight, flickable.currentY);

            flickable.currentX += coverRealWidth + flickable.spacing;

            flickable.currentColumn++;

            let max = 0;
            for (let j = 0; j < flickable.column; j++) {
                max = Math.max(flickable.prevHeight[j] + flickable.currentY[j]);
            }

            flickable.contentHeight = max;
        }

        Column {
            Item {
                id: coverPort
                width: coverRealWidth
                height: coverRealHeight

                Image {
                    anchors.fill: parent
                    anchors.topMargin: rootItem.radius
                    source: cover
                }
            }

            Item {
                id: titlePort
                width: titleWidth
                height: titleText.height

                Text {
                    id: titleText
                    width: parent.width
                    wrapMode: Text.WrapAnywhere
                    text: title
                    font.family: "微软雅黑"
                    font.pointSize: 14
                }
            }

            Item {
                id: infoPort
                width: infoWidth
                height: infoHeight

                RowLayout {
                    anchors.fill: parent

                    CircularImage {
                        id: head
                        Layout.preferredWidth: parent.height - 5
                        Layout.preferredHeight: parent.height - 5
                        Layout.leftMargin: 5
                        Layout.alignment: Qt.AlignVCenter
                        source: "file:/C:/Users/mps95/Desktop/head.jpg"
                    }

                    Text {
                        Layout.fillWidth: true
                        Layout.fillHeight: true
                        text: "用户" + user
                        font.pointSize: 14
                        verticalAlignment: Text.AlignVCenter
                        elide: Text.ElideRight
                    }

                    Text {
                        Layout.preferredWidth: 100
                        Layout.preferredHeight: parent.height
                        Layout.rightMargin: 5
                        text: (like ? "🩷" : "🤍") + " " + Math.round(Math.random() * 1000)
                        font.pointSize: 14
                        horizontalAlignment: Text.AlignRight
                        verticalAlignment: Text.AlignVCenter
                        property int like: Math.round(Math.random())
                    }
                }
            }
        }
    }
}

loadMore() 是向后台请求更多的卡片数据,这部分需要根据实际需求进行改造,我这里就简单生成了一些模拟数据:

function loadMore() {
    //这部分来自后台请求, 必须知道封面宽高
    let titleList = [
            "单行标题: 测试测试测试测试",
            "双行标题: 测试测试测试测试测试测测试测试测试测试测试测试",
            "三行标题: 测试测试测试测试测试测测试测试测试测试测试测试测试测试测试测试测试测试测试"
        ];
    for (let i = 0; i < 10; i++) {
        let userId = Math.round(Math.random() * 100000);
        let type = Math.round(Math.random());  //0 image / 1 video
        let cover = "file:/C:/Users/mps95/Desktop/素材/动漫图片/img2" + i + ".jpg"; //封面, 无论视频还是图片都需要有
        let url = cover;
        if (type == 1) {
            //url = "file:/test.mp4";
        }

        let object = {
            type: type,
            cover: cover,
            user: userId,
            url: url,
            title: titleList[Math.round(Math.random() * 2)],
            coverWidth: 300,
            coverHeight: (type + 2) * 100 + Math.round(Math.random() * 3) * 80
        };

        jsonData.push(object);
        listModel.append(object);
    }
}

【结语】

最后:项目链接(多多star呀..⭐_⭐):

Github 的 WaterfallFlow 瀑布流视图(并且可以自适应),类似小红书

注意: 测试用的图片没有包含在内,请改为自己的测试集。

From:https://www.cnblogs.com/mengps/p/18406941
本文地址: http://shuzixingkong.net/article/1924
0评论
提交 加载更多评论
其他文章 manim边学边做--常用多边形
多边形是常见的几何结构,它的形状看似千变万化,其实都可以由几种常用的多边形组合而成。 本篇介绍manim中提供的几个绘制常用多边形的模块。 Triangle:等边三角形 Square:正方形 Rectangle:长方形 RoundedRectangle:圆角的长方形 Star:没有相交线的正多边形,
manim边学边做--常用多边形 manim边学边做--常用多边形 manim边学边做--常用多边形
RS485与ModbusRTU
前言 大家好!我是付工。 每次听到别人说RS485通信协议,就很想去纠正他。 今天跟大家聊聊关于RS485的那些事。 接口标准 首先明确一点,RS485不是通信协议,而是一种接口标准,它还有2个兄弟:RS232和RS422。 RS是Recommend Standard的缩写,对于串口通信,目前工业领
RS485与ModbusRTU RS485与ModbusRTU RS485与ModbusRTU
C++:使自定义类支持迭代器
概述 在 C++ 中,链表迭代器是一种用来遍历链表(如 std::list)元素的工具。链表是一种数据结构,其中每个元素(节点)包含一个数据值和一个指向下一个节点的指针。链表迭代器允许以类似于数组的方式访问链表中的元素,但不需要直接操作指针。 链表迭代器的作用 访问元素:链表迭代器使你能够顺序访问链
Activity启动模式
Activity启动模式 1. Activity启动模式介绍 1.1 任务栈 在Android开发中,任务栈(Task Stack)是一个非常重要的概念,主要用于管理应用程序中的Activity及其启动模式。它帮助开发者了解当用户在不同应用之间切换,或者应用内部不同Activity之间跳转时,系统如
Activity启动模式 Activity启动模式 Activity启动模式
Go runtime 调度器精讲(一):Go 程序初始化
原创文章,欢迎转载,转载请注明出处,谢谢。 0. 前言 本系列将介绍 Go runtime 调度器。要学好 Go 语言,runtime 运行时是绕不过去的,它相当于一层“操作系统”对我们的程序做“各种类型”的处理。其中,调度器作为运行时的核心,是必须要了解的内容。本系列会结合 Go plan9 汇编
Go runtime 调度器精讲(一):Go 程序初始化 Go runtime 调度器精讲(一):Go 程序初始化 Go runtime 调度器精讲(一):Go 程序初始化
以太坊Rollup方案之 arbitrum(1)
title: 以太坊Rollup方案之 arbitrum(1) author: ivhu date: 2024-09-10 20:56:05 categories: - 区块链 - 以太坊 tags: - 以太坊 - L2 - arbitrum description: 什么是Rollup? 以太坊
以太坊Rollup方案之 arbitrum(1) 以太坊Rollup方案之 arbitrum(1) 以太坊Rollup方案之 arbitrum(1)
不升级 POI 版本,如何生成符合新版标准的Excel 2007文件
开心一刻 记得小时候,家里丢了钱,是我拿的,可爸妈却一口咬定是弟弟拿的 爸爸把弟弟打的遍体鳞伤,弟弟气愤的斜视着我 我不敢直视弟弟,目光转向爸爸说到:爸爸,你看他,好像还不服 问题描述 项目基于 POI 4.1.2 生成 Excel 2007 文件,已经对接了很多客户,也稳定运行了好几年了;就在前两
不升级 POI 版本,如何生成符合新版标准的Excel 2007文件 不升级 POI 版本,如何生成符合新版标准的Excel 2007文件 不升级 POI 版本,如何生成符合新版标准的Excel 2007文件
前端使用 Konva 实现可视化设计器(22)- 绘制图形(矩形、直线、折线)
本章分享一下如何使用 Konva 绘制基础图形:矩形、直线、折线,希望大家继续关注和支持哈! 请大家动动小手,给我一个免费的 Star 吧~ 大家如果发现了 Bug,欢迎来提 Issue 哟~ github源码 gitee源码 示例地址 矩形 先上效果! 实现方式基本和《前端使用 Konva 实现可
前端使用 Konva 实现可视化设计器(22)- 绘制图形(矩形、直线、折线) 前端使用 Konva 实现可视化设计器(22)- 绘制图形(矩形、直线、折线) 前端使用 Konva 实现可视化设计器(22)- 绘制图形(矩形、直线、折线)