使用Qt Quick创建可伸缩,有光泽/闪亮的按钮

时间:2014-08-23 12:57:49

标签: qt button qml qt-quick qtquickcontrols

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

Glossy button

它需要是可扩展的,所以我不能使用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
            }
        }
    }
}

Attempt screenshot

我有两个问题:

  1. 我不知道如何创造弯曲的光泽效果。
  2. 我需要将文字置于光辉之下,但目前它已高于它。

2 个答案:

答案 0 :(得分:19)

使用Rectangle无法做到这一点。但是,您可以使用Canvas。我将引导您完成整个过程。

第1步:平面颜色

由于有几个"层",我们必须创建一个Item来包含它们。我们将根据它们的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%。绘制圆角矩形时必须考虑到这一点,否则笔划将隐藏在画布外部。我们可以通过绘制边距等于线宽一半的矩形来实现。

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

此步骤的结果是:

enter image description here

第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。这是因为我们不希望文本高于其他所有内容。如果ButtonStyleforeground个组件,则无需这样做。相反,我们将Label项目添加为background的子项。

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

第3步:光泽效果

Canvas可以绘制linearradialconical渐变。我们将使用线性渐变来绘制" shine"效果对我们的按钮:

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相同的圆角矩形,除了这次,我们用从上到下的透明渐变填充它。

Step #3-a screenshot

看起来不错,但还不是很好。闪光效果在按钮的中间停止,为了实现Canvas的效果,我们需要在绘制渐变矩形之前进行一些修剪。您可以将Canvas视为类似于"减法" Photoshop中的Rectangular Marquee Tool,但使用您定义的任何形状除外。

如果我们很幸运并且光泽的曲线是凹的,我们可以在绘制渐变矩形之前添加以下几行:

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();

Step #3-b screenshot

相反,我们将使用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
        }
    }
}

Step #3-c screenshot

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

因此,剪辑的代码如下:

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
        }
    }
}

Step #3-d screenshot

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

第4步:让它看起来更具互动性

只需两行代码就可以实现这一目标。第一行使光泽画布部分透明:

opacity: !button.pressed ? 1 : 0.75

当按钮悬停时,第二个增加文本的亮度:

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

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

答案 1 :(得分:2)

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