[QML]彻底理解QML的implicitWidth属性

571 阅读6分钟

implicitWidth:隐式推荐宽度。

官方文档:

  • 当控件不是布局的子项时,在没有明确指定width时,控件希望占据的大小,当设置width属性时,会忽略掉implicitWidth。[后面示例1]
  • 如果一个组件是布局的子项,那么布局会使用组件的隐式大小(implicitWidth)属性来确定组件的首选大小(preferredSize),在这种情况下,如果组件具有显示的宽度或者高度设置,这些显示设置会被忽略。[后面示例2]
  • 大多数情况下,组件的默认隐式大小为0,即没有大小,但是有一些组件具有固定的隐式大小,这些大小是无法覆盖的,例如ImageText组件。[后面示例3]

示例1:

Window {
        id: container
        width: 600
        height: 600
        visible: true
        title: qsTr("MyQML")
        color: "white"
​
        Rectangle {
            x: 100
            y: 100
            // width: 100
            height: 100
            color: "red"
            implicitWidth: 200
        }
}

非布局子项时,这里没有设置width属性,会使用implicitWidth的值,效果:

image.png

同时设置widthimplicitWidth属性,

Window {
        id: container
        width: 600
        height: 600
        visible: true
        title: qsTr("MyQML")
        color: "white"
​
        Rectangle {
            x: 100
            y: 100
            width: 100
            height: 100
            color: "red"
            implicitWidth: 200
        }
}

这时width会起效,implicitWidth被忽略,效果:

image.png

示例2:

在布局中,同时设置widthimplicitWidth属性,这时优先使用implicitWidthwidth会被忽略,

Window {
        id: container
        width: 600
        height: 600
        visible: true
        title: qsTr("MyQML")
        color: "white"
​
        RowLayout {
            width: 600
            height: 150
            spacing: 0
​
            Rectangle {
                height: parent.height
                color: "blue"
                implicitWidth: 500
                width: 200
            }
        }
}        

效果图:

image.png

如果只设置width属性,

Window {
        id: container
        width: 600
        height: 600
        visible: true
        title: qsTr("MyQML")
        color: "white"
​
        RowLayout {
            width: 600
            height: 150
            spacing: 0
​
            Rectangle {
                height: parent.height
                color: "blue"
                // implicitWidth: 500
                width: 200
            }
        }
}        

效果如图:

image.png

如果width属性也没有设置,

Window {
        id: container
        width: 600
        height: 600
        visible: true
        title: qsTr("MyQML")
        color: "white"
​
        RowLayout {
            width: 600
            height: 150
            spacing: 0
​
            Rectangle {
                height: parent.height
                color: "blue"
                // implicitWidth: 500
                // width: 200
            }
        }
}        

那么这个控件将不会显示出来,因为默认情况下,RectangleimplicitWidth值为0。

示例3:

接下来我们就来看特例,比如Text,首先就是只设置width属性:

RowLayout {
            width: 600
            height: 150
            spacing: 0
​
            Text {
                text: qsTr("text")
                font.pixelSize: 30
                height: parent.height
                //为了让字体居中显示
                horizontalAlignment: Text.AlignHCenter
                onWidthChanged: console.log("width:", width)
                onImplicitWidthChanged: console.log("ImplicitWidth", implicitWidth)
                width: 200
                // implicitWidth: 200
                // Layout.fillWidth: true
                // Layout.preferredWidth: 300
            }
        }

运行结果:

image.png

日志打印:

qml: ImplicitWidth 54
qml: ImplicitWidth 54
qml: width: 54

可以发现width没有任何效果!! ,可以发现widthimplicitWidth都是54,而54就是该Text的真实宽度,我们直接不设置width属性看看,

RowLayout {
            width: 600
            height: 150
            spacing: 0
​
            Text {
                text: qsTr("text")
                font.pixelSize: 30
                height: parent.height
                //为了让字体居中显示
                horizontalAlignment: Text.AlignHCenter
                onWidthChanged: console.log("width:", width)
                onImplicitWidthChanged: console.log("ImplicitWidth", implicitWidth)
                // width: 200
                // implicitWidth: 200
                // Layout.fillWidth: true
                // Layout.preferredWidth: 300
            }
        }

效果和日志打印和前面一模一样,更进一步说明对于Text来说,当其在布局中时,width属性是被忽略的。这时我们参考前面示例2中的逻辑,可以使用implicitWidth来设置吗?

RowLayout {
            width: 600
            height: 150
            spacing: 0
​
            Text {
                text: qsTr("text")
                font.pixelSize: 30
                height: parent.height
                //为了让字体居中显示
                horizontalAlignment: Text.AlignHCenter
                onWidthChanged: console.log("width:", width)
                onImplicitWidthChanged: console.log("ImplicitWidth", implicitWidth)
                // width: 200
                implicitWidth: 200
                // Layout.fillWidth: true
                // Layout.preferredWidth: 300
            }
        }

上面代码会直接报错:qrc:/qt/qml/content/App.qml:31:32: Invalid property assignment: "implicitWidth" is a read-only property可以发现TextimplicitWidth属性是只读的,也就是说该属性的值是由其内部管理

那如果想让在布局中设置Text宽度该如何实现呢?就需要Layout属性了:

RowLayout {
            width: 600
            height: 150
            spacing: 0
​
            Text {
                text: qsTr("text")
                font.pixelSize: 30
                height: parent.height
                //为了让字体居中显示
                horizontalAlignment: Text.AlignHCenter
                onWidthChanged: console.log("width:", width)
                onImplicitWidthChanged: console.log("ImplicitWidth", implicitWidth)
                // width: 200
                // implicitWidth: 200
                // Layout.fillWidth: true
                Layout.preferredWidth: 300
            }
        }

效果图:

image.png

日志打印:

qml: width: 54
qml: ImplicitWidth 54
qml: ImplicitWidth 54
qml: width: 300

可以发现这时的Text宽度终于符合预期了。这里使用Layout.preferredWidth属性,简单说明:该属性用于设置处于布局中的组件的首选宽度,如果设置为-1,则布局会使用implicitWidth属性,默认值就是-1。

这里可以做个总结:当组件在布局中时,优先级来看,Layout.preferredWidth > implicitWidth > width

深入理解

问题1:为什么需要implicitWidth属性?直接使用width不可以吗?

简单说明implicitWidth属性的使用后,我们来思考一下该属性有什么用,帮助文档是这样说的:对于定义基于其内容具有首选大小的组件,设置隐式大小非常有用。

简单理解就是implicitWidth它反应了组件的自然宽度,对于布局管理器来说,需要依赖该属性。更通俗的理解:widthheight反映了组件的实际尺寸,而隐式尺寸则是组件固有的属性。

啥是自然尺寸?

比如对于Image组件来说,其自然尺寸就是将其像素点一对一地显示在屏幕上,但是由于我们可以缩放Image,这里缩放后地大小就是真实尺寸,这样侧面说明Image的自然尺寸不能被修改。

举个简单例子,假如现在需要开发一个相册,我们不知道图片的尺寸,同时我们不想缩小图片,但是在必要时可以放大图片,这时就需要Image的自然尺寸了,

Image {
    width: Math.max(150, implicitWidth)
    height: Math.max(150, implicitHeight)
}

在这个例子中,如果图片原来大小为300x300,则会为300x300,而小于150x150的会放大。

自然尺寸在布局的作用?

在自定义组件中,如果定义组件大小有多个选择,其中一个方式就是基于父节点:

Item {
    id: root
    Rectangle {
        width: root.width * 0.2
        height: root.height * 0.2
        color: 'red'
    }
    Rectangle {
        x: 0.2 * root.width
        y: 0.2 * root.height
        width: root.width * 0.8
        height: root.height * 0.8
        color: 'green'
    }
}

在这种情况下,只要给组件设置了宽高,它都可以正常运行,这种就是没有自然尺寸的情况,组件的大小完全取决于父控件设置的宽高。

那如果完全不需要自然尺寸,会有什么问题呢?假如我们尝试在类似于布局的组件上设置宽度属性,那么该组件会根据子项的宽度而不是隐式宽度来计算其自身宽度,但是子项又受父节点的影响,这样最终会形成一个绑定循环

设置组件的隐式大小就可以打破这种循环依赖的关系,这里也就解释了在前面的示例中,对于RowLayout为什么会优先采用implicitWidth,而不是width

问题2:有什么使用场景?

  • 对于一个可重用的自定义组件来说,当需要设置跟节点尺寸时,使用implicitWidth/implicitHeight而不是width/height可以更加方便的在布局中使用。
  • 如果新建一个自定义组件时,其大小可以根据内容自行调整,则设置隐式宽高属性。

我们来看一下Qt帮助文档的例子,

 // Label.qml
 import QtQuick 2.0
​
 Item {
     property alias icon: image.source
     property alias label: text.text
     implicitWidth: text.implicitWidth + image.implicitWidth
     implicitHeight: Math.max(text.implicitHeight, image.implicitHeight)
     Image { id: image }
     Text {
         id: text
         wrapMode: Text.Wrap
         anchors.left: image.right; anchors.right: parent.right
         anchors.verticalCenter: parent.verticalCenter
     }
 }
​

这里自定义组件包含2个水平放置的ImageText,当我们设置iconlabel属性时,该自定义控件就有了隐式尺寸,基控件的自然大小,这时放入到布局中,也可以正常显示了。