如何在QML中设计多级流体布局

How to design a multi-level fluid layout in QML

我在 QML 中设计了一个布局,以了解更多关于其功能的信息,并且在设计此类布局时对 "Best Practices" 有一些疑问。这是:

它本质上是一个 ColumnLayout,由三个 RowLayout 组成,每个都有一些 Rectangles。每行和矩形的大小应计算如下:

我想出的 QML 正在运行,如下所示。我对此有一些疑问:

  1. 我使用 Layout.preferredHeight: x*parent.height 模式设置了宽度和高度百分比。其他选项导致了一些问题(例如 preferredHeight 导致绑定循环警告)。我的方法正确有效吗?
  2. 作为 hack,我为第 2 行和第 3 行的第一个元素设置了 Layout.fillWidth: true,这对我来说没有意义,但确实有效。如果我将它们的宽度设置为百分比(例如 Layout.preferredWidth: 0.2*parent.width),它们的行将折叠为宽度 0。这是预期的行为吗?有没有更好的解决方法?
  3. 您对布局有什么建议吗?我走的路对吗?

这是我的布局 QML 代码:

ApplicationWindow {
    x: 500
    y: 100
    width: 250
    height: 150
    visible: true

    ColumnLayout {
        anchors.fill: parent
        spacing: 0
        RowLayout {
            spacing: 0
            Layout.preferredHeight: 0.4*parent.height
            Layout.fillHeight: false
            Rectangle {
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "red"
            }
        }
        RowLayout {
            spacing: 0
            Layout.preferredHeight: 0.2*parent.height
            Layout.fillHeight: false
            Rectangle {
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "darkGreen"
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 0.8*parent.width
                color: "lightGreen"
            }
        }
        RowLayout {
            spacing: 0
            Layout.preferredHeight: 0.4*parent.height
            Layout.fillHeight: false
            Rectangle {
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "darkBlue"
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 0.2*parent.width
                color: "blue"
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 0.4*parent.width
                color: "lightBlue"
            }
        }
    }
}

更新:

我的方法似乎比我预期的更老套:

  1. 在此布局中将 Text 元素作为子元素会引发 binding loop 警告,例如:

QML QQuickLayoutAttached: Binding loop detected for property "preferredWidth"

如果文本在矩形内换行,警告消失。

  1. spacing: 0 似乎发挥了重要作用。省略它会导致绑定循环警告。

虽然我在 QML 中进行流体布局设计的方法有效,但它有一些严重的问题,可能不属于 "best practices"。

QtQuick.Layout 没有对经典锚定系统提供任何真正的改进。我会建议避免使用它们。您可以使用锚点更好地控制布局。

这是没有 QtQuick.Layout 的完全相同的设计:

ApplicationWindow {
    x: 500
    y: 100
    width: 250
    height: 150
    visible: true

    Column {
        anchors.fill: parent

        Row {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 0.4 * parent.height

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: parent.width
                color: "red"
            }
        }

        Row {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 0.2 * parent.height

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.2 * parent.width
                color: "darkGreen"
            }

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.8 * parent.width
                color: "lightGreen"
            }
        }

        Row {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 0.4 * parent.height

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.4 * parent.width
                color: "darkBlue"
            }
            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.2 * parent.width
                color: "blue"
            }
            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.4 * parent.width
                color: "lightBlue"
            }
        }
    }
}

到目前为止,我还没有遇到过没有QtQuick.Layout就无法完成的设计。

禁止(也没有必要)尝试从布局中的项目中引用父项的宽度和高度。

fillWidth(或 fillHeight)设置为 true 时,项目将按其指定的 preferredWidth(或 preferredHeight).

因此,创建布局的正确方法如下。我修改了外观只是为了说明间距和Text也可以随意设置。没有绑定循环。

ApplicationWindow {
    x: 500
    y: 100
    width: 250
    height: 150
    visible: true

    ColumnLayout {
        anchors.fill: parent
        spacing: 5
        RowLayout {
            spacing: 5
            Layout.preferredHeight: 40
            Layout.fillHeight: true
            Rectangle {
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "red"
            }
        }
        RowLayout {
            spacing: 5
            Layout.preferredHeight: 20
            Layout.fillHeight: true
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 20
                Layout.fillWidth: true
                color: "darkGreen"
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 80
                Layout.fillWidth: true
                color: "lightGreen"
            }
        }
        RowLayout {
            spacing: 5
            Layout.preferredHeight: 40
            Layout.fillHeight: true
            Text {
                Layout.fillHeight: true
                Layout.preferredWidth: 40
                Layout.fillWidth: true
                color: "darkBlue"
                text: "hello world!"
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 20
                Layout.fillWidth: true
                color: "blue"
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 40
                Layout.fillWidth: true
                color: "lightBlue"
            }
        }
    }
}

虽然其他两个答案都显示了有效的解决方案,但我认为提出的问题和两个解决方案都以某种方式忽略了使用布局的要点。

基本上,布局是为了将具有隐式大小 (implicitHeight/implicitWidth) 的项目组合在一起。 Layout.preferredWidth/Layout.preferredHeight 用于在极少数情况下覆盖这些内容,请参见下文。 Qt 附带的“Qt Quick Layouts - Basic Example”根本不使用 Layout.preferredWidth/Layout.preferredHeight (!) 并且看起来非常漂亮,而不会用锚点或 Layout 属性污染整个 qml 文件。自己做这件事需要一些学习,但是一旦你习惯了,布局是一种用更少的代码更直接地定义用户界面的方法。

一开始让我最困惑的是以下几点:

  • RowLayout/ColumnLayout/GridLayout 随 Layout.fillWidth/Layout.fillHeight 设置为 true,因此当将它们放在 Item/Rectangle 附近时 Items/Rectangles 突然消失,因为它们不没有设置这些值(即它们将 Layout.fillWidth/Layout.fillHeight 设置为 false)。
  • Items/Rectangles 的 implicitHeight/implicitWidth 为 0,这意味着它们与 Layouts 的并排效果并不好。最好的办法是从包含的子项中派生 implicitWidth/implicitHeight,就像 RowLayout/ColumnLayout 本身默认为其子项所做的那样。
  • Layout.preferredWidth/Layout.preferredHeight 可用于克服已定义且无法设置的隐式大小。一个这样的地方直接在布局项中,另一个是例如。也不允许您覆盖隐式大小的文本项。

考虑到这几点,我会按以下方式编写示例。我删除了不必要的项目以更好地说明何时需要 Layout.fillwidth/Layout.fillheight,以及在我看来何时最好使用 implicitWidth。

import QtQuick 2.9
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3

ApplicationWindow {
    width: 250
    height: 150
    visible: true

    ColumnLayout {
        spacing: 0
        anchors.fill: parent
        Rectangle {
            implicitHeight: 40
            Layout.fillHeight: true
            Layout.fillWidth: true
            color: "red"
        }
        RowLayout {
            spacing: 0
            Layout.preferredHeight: 20
            Rectangle {
                implicitWidth: 20
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "darkGreen"
            }
            Rectangle {
                implicitWidth: 80
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "lightGreen"
            }
        }
        RowLayout {
            spacing: 0
            Layout.preferredHeight: 40
            Rectangle {
                implicitWidth: 40
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "darkBlue"
            }
            Rectangle {
                implicitWidth: 20
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "blue"
            }
            Rectangle {
                implicitWidth: 40
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "lightBlue"
            }
        }
    }
}