【问题标题】:Creating a scalable, glossy/shiny button with Qt Quick使用 Qt Quick 创建一个可扩展、有光泽/有光泽的按钮
【发布时间】:2014-08-23 12:57:49
【问题描述】:

我想使用 Qt Quick 创建下面的光泽按钮(最好使用纯 QML,没有 C++):

它需要可扩展,所以我不能使用 PNG 等。

到目前为止我的代码:

import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2

ApplicationWindow {
    id: window
    color: "#cccccc"
    width: 200
    height: 200

    Button {
        id: button
        width: Math.min(window.width, window.height) - 20
        height: width * 0.3
        anchors.centerIn: parent
        text: "Button"

        style: ButtonStyle {
            background: Rectangle {
                gradient: Gradient {
                    GradientStop {
                        position: 0
                        color: "#bbffffff"
                    }
                    GradientStop {
                        position: 0.6
                        color: "#00c0f5"
                    }
                }

                border.color: "grey"
                border.width: height * 0.05
                radius: height / 5
            }

            label: Label {
                text: button.text
                color: "#ddd"
                font.pixelSize: button.height * 0.5
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }
        }
    }
}

我有两个问题:

  1. 我不知道如何创建弯曲的光泽效果。
  2. 我需要将文本置于光芒之下,但目前它在其上方。

【问题讨论】:

    标签: qt button qml qt-quick qtquickcontrols


    【解决方案1】:

    使用矩形是不可能的。但是,您可以使用Canvas。我将引导您完成整个过程。

    第 1 步:纯色

    由于有几个“层”,我们必须创建一个项目来包含它们。我们将按照 Z 顺序添加图层,从平面颜色开始:

    import QtQuick 2.3
    import QtQuick.Controls 1.2
    import QtQuick.Controls.Styles 1.2
    
    ApplicationWindow {
        id: window
        color: "#cccccc"
        width: 200
        height: 200
    
        Button {
            id: button
            width: Math.min(window.width, window.height) - 20
            height: width * 0.3
            anchors.centerIn: parent
            text: "Button"
    
            readonly property real radius: height / 5
    
            style: ButtonStyle {
                background: Item {
                    Canvas {
                        anchors.fill: parent
    
                        onPaint: {
                            var ctx = getContext("2d");
                            ctx.reset();
    
                            ctx.beginPath();
                            ctx.lineWidth = height * 0.1;
                            ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2,
                                width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius);
                            ctx.strokeStyle = "grey";
                            ctx.stroke();
                            ctx.fillStyle = "#00c0f5";
                            ctx.fill();
                        }
                    }
                }
    
                label: Label {
                    text: button.text
                    color: "white"
                    font.pixelSize: button.height * 0.5
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                }
            }
        }
    }
    

    Canvas 项应该填充按钮,所以我们写anchors.fill: parent

    然后我们获得了用于在画布上绘制的 2D 上下文。我们还调用reset(),它会在每次绘制之前清除画布。

    按钮有圆角,因此我们定义了只读的radius 属性并将其设置为我们想要的值,在本例中为按钮高度的 20%。

    接下来,我们调用beginPath()。这会启动一条新路径,同时也会关闭之前的所有路径。

    我们将笔划的线宽设置为按钮高度的 10%。

    Canvas 在内部使用QPainter。 QPainter 在目标内侧描边 50%,在外侧描边 50%。我们在绘制圆角矩形时必须考虑到这一点,否则笔画将隐藏在画布之外。我们可以通过绘制边距等于线宽一半的矩形来做到这一点。

    定义圆角矩形路径后,我们就剩下一条需要描边和填充的路径。

    这一步的结果是:

    第 2 步:标签

    由于我们希望文本在按钮的光芒下,我们必须接下来定义它:

    import QtQuick 2.3
    import QtQuick.Controls 1.2
    import QtQuick.Controls.Styles 1.2
    
    ApplicationWindow {
        id: window
        color: "#cccccc"
        width: 200
        height: 200
    
        Button {
            id: button
            width: Math.min(window.width, window.height) - 20
            height: width * 0.3
            anchors.centerIn: parent
            text: "Button"
    
            readonly property real radius: height / 5
    
            style: ButtonStyle {
                background: Item {
                    Canvas {
                        anchors.fill: parent
    
                        onPaint: {
                            var ctx = getContext("2d");
                            ctx.reset();
    
                            ctx.beginPath();
                            ctx.lineWidth = height * 0.1;
                            ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2,
                                width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius);
                            ctx.strokeStyle = "grey";
                            ctx.stroke();
                            ctx.fillStyle = "#00c0f5";
                            ctx.fill();
                        }
                    }
    
                    Label {
                        text: button.text
                        color: "white"
                        font.pixelSize: button.height * 0.5
                        anchors.centerIn: parent
                    }
                }
    
                label: null
            }
        }
    }
    

    请注意,样式的label 组件设置为null。这是因为我们不希望文本高于一切。如果ButtonStyle 有一个foreground 组件,则没有必要这样做。相反,我们将 Label 项添加为 background 的子项。

    此代码的视觉结果与上一步相同。

    第 3 步:发光效果

    Canvas 可以绘制linearradialconical 渐变。我们将使用线性渐变在按钮上绘制“闪耀”效果:

    import QtQuick 2.3
    import QtQuick.Controls 1.2
    import QtQuick.Controls.Styles 1.2
    
    ApplicationWindow {
        id: window
        color: "#cccccc"
        width: 200
        height: 200
    
        Button {
            id: button
            width: Math.min(window.width, window.height) - 20
            height: width * 0.3
            anchors.centerIn: parent
            text: "Button"
    
            readonly property real radius: height / 5
    
            style: ButtonStyle {
                background: Item {
                    Canvas {
                        anchors.fill: parent
    
                        onPaint: {
                            var ctx = getContext("2d");
                            ctx.reset();
    
                            ctx.beginPath();
                            ctx.lineWidth = height * 0.1;
                            ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2,
                                width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius);
                            ctx.strokeStyle = "grey";
                            ctx.stroke();
                            ctx.fillStyle = "#00c0f5";
                            ctx.fill();
                        }
                    }
    
                    Label {
                        text: button.text
                        color: "white"
                        font.pixelSize: button.height * 0.5
                        anchors.centerIn: parent
                    }
    
                    Canvas {
                        anchors.fill: parent
                        onPaint: {
                            var ctx = getContext("2d");
                            ctx.reset();
    
                            ctx.beginPath();
                            ctx.lineWidth = height * 0.1;
                            ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2,
                                width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius);
                            ctx.moveTo(0, height * 0.4);
                            ctx.bezierCurveTo(width * 0.25, height * 0.6, width * 0.75, height * 0.6, width, height * 0.4);
                            ctx.lineTo(width, height);
                            ctx.lineTo(0, height);
                            ctx.lineTo(0, height * 0.4);
                            ctx.clip();
    
                            ctx.beginPath();
                            ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2,
                                width - ctx.lineWidth, height - ctx.lineWidth,
                                button.radius, button.radius);
                            var gradient = ctx.createLinearGradient(0, 0, 0, height);
                            gradient.addColorStop(0, "#bbffffff");
                            gradient.addColorStop(0.6, "#00ffffff");
                            ctx.fillStyle = gradient;
                            ctx.fill();
                        }
                    }
                }
    
                label: null
            }
        }
    }
    

    我们绘制与第 1 步相同的圆角矩形,但这次我们用从上到下的透明渐变填充它。

    看起来不错,但还不够。闪耀效果在按钮的一半停止,为了使用 Canvas 实现这一点,我们需要在绘制渐变矩形之前进行一些剪辑。您可以认为使用 Canvas 进行剪切类似于 Photoshop 中的“减法”Rectangular Marquee Tool,除了使用您定义的任何形状。

    如果我们很幸运,shine 的曲线是凹的,我们可以在绘制渐变矩形之前简单地添加以下几行:

    ctx.beginPath();
    ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2, width - ctx.lineWidth, height - ctx.lineWidth,
        button.radius, button.radius);
    ctx.moveTo(0, height / 2);
    ctx.ellipse(-width / 2, height / 2, width * 2, height);
    ctx.clip();
    

    相反,我们将使用bezierCurveTo() 手动绘制曲线。

    确定要传递给 bezierCurveTo() 的值并不容易,这就是为什么我建议使用像 Craig Buckler 的 Canvas Bézier Curve Example 这样的出色工具来查找所需曲线的原因。这将让您操纵曲线,直到找到您所追求的,但最重要的是,它会为您提供创建这些曲线的代码。如果您想反其道而行之,并编辑代码以实时查看曲线,请查看HTML5 Canvas Bezier Curve Tutorial

    下面,我做了一个小例子,描边剪切路径,使其更容易可视化:

    import QtQuick 2.3
    import QtQuick.Controls 1.2
    import QtQuick.Controls.Styles 1.2
    
    ApplicationWindow {
        id: window
        color: "#cccccc"
        width: 200
        height: 200
    
        Button {
            id: button
            width: Math.min(window.width, window.height) - 20
            height: width * 0.3
            anchors.centerIn: parent
            text: "Button"
    
            readonly property real radius: height / 5
    
            style: ButtonStyle {
                background: Item {
                    Rectangle {
                        anchors.fill: parent
                        color: "transparent"
                        border.color: "black"
                        opacity: 0.25
                    }
    
                    Canvas {
                        anchors.fill: parent
                        onPaint: {
                            var ctx = getContext("2d");
                            ctx.reset();
    
                            var cornerRadius = height / 5;
    
                            ctx.beginPath();
                            ctx.moveTo(0, height * 0.4);
                            ctx.bezierCurveTo(width * 0.25, height * 0.6, width * 0.75, height * 0.6, width, height * 0.4);
                            ctx.lineTo(width, height);
                            ctx.lineTo(0, height);
                            ctx.lineTo(0, height * 0.4);
                            ctx.strokeStyle = "red";
                            ctx.stroke();
                        }
                    }
                }
    
                label: null
            }
        }
    }
    

    红色区域的反面是我们将在其中绘制光泽的区域。

    所以,进行剪辑的代码如下:

    import QtQuick 2.3
    import QtQuick.Controls 1.2
    import QtQuick.Controls.Styles 1.2
    
    ApplicationWindow {
        id: window
        color: "#cccccc"
        width: 200
        height: 200
    
        Button {
            id: button
            width: Math.min(window.width, window.height) - 20
            height: width * 0.3
            anchors.centerIn: parent
            text: "Button"
    
            readonly property real radius: height / 5
    
            style: ButtonStyle {
                background: Item {
                    Canvas {
                        anchors.fill: parent
    
                        onPaint: {
                            var ctx = getContext("2d");
                            ctx.reset();
    
                            ctx.beginPath();
                            ctx.lineWidth = height * 0.1;
                            ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2,
                                width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius);
                            ctx.strokeStyle = "grey";
                            ctx.stroke();
                            ctx.fillStyle = "#00c0f5";
                            ctx.fill();
                        }
                    }
    
                    Label {
                        text: button.text
                        color: "#ddd"
                        font.pixelSize: button.height * 0.5
                        anchors.centerIn: parent
                    }
    
                    Canvas {
                        anchors.fill: parent
                        onPaint: {
                            var ctx = getContext("2d");
                            ctx.reset();
    
                            ctx.beginPath();
                            ctx.lineWidth = height * 0.1;
                            ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2,
                                width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius);
                            ctx.moveTo(0, height * 0.4);
                            ctx.bezierCurveTo(width * 0.25, height * 0.6, width * 0.75, height * 0.6, width, height * 0.4);
                            ctx.lineTo(width, height);
                            ctx.lineTo(0, height);
                            ctx.lineTo(0, height * 0.4);
                            ctx.clip();
    
                            ctx.beginPath();
                            ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2,
                                width - ctx.lineWidth, height - ctx.lineWidth,
                                button.radius, button.radius);
                            var gradient = ctx.createLinearGradient(0, 0, 0, height);
                            gradient.addColorStop(0, "#bbffffff");
                            gradient.addColorStop(0.6, "#00ffffff");
                            ctx.fillStyle = gradient;
                            ctx.fill();
                        }
                    }
                }
    
                label: null
            }
        }
    }
    

    按钮现在看起来像部件,并且可以单击,但它没有任何鼠标交互的视觉指示。让我们也添加它。

    第 4 步:使其看起来具有交互性

    只需两行代码即可实现。第一行使闪耀画布部分透明:

    opacity: !button.pressed ? 1 : 0.75
    

    第二个增加按钮悬停时文字的亮度:

    color: button.hovered && !button.pressed ? "white" : "#ddd"
    

    您可以更进一步,将样式分离到自己的 QML 文件中,提供颜色属性并方便地允许使用不同颜色的按钮。

    【讨论】:

    • 我只是在一些谷歌搜索中漫游,是否可以仅使用 QML 来实现渐变按钮。我降落在这里。我的天啊!这是一个神圣的 QML 代码。很棒的分享米奇。
    【解决方案2】:

    QML Image 原生支持 SVG,然后应该像使用 SVG 工具创建图像一样简单...

    【讨论】:

    • 虽然接受的答案确实提供了关于如何完全按照 OP 要求执行的大量详细信息,但这种方法会更容易且更易于维护。
    猜你喜欢
    • 1970-01-01
    • 2023-04-01
    • 2016-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多