使用Qt Quick创建可扩展的、有光泽的按钮

7

我想使用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
            }
        }
    }
}

尝试截图

我有两个问题:

  1. 我不知道如何创建弯曲的光泽效果。
  2. 我需要在光泽下面放置文本,但目前它在上面。
2个回答

23
这是使用矩形不可能实现的。但是您可以使用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%。当绘制我们的圆角矩形时,我们必须考虑到这一点,否则描边将被隐藏在画布外面。我们可以通过将矩形的边距设置为线宽的一半来实现这一点。
在定义了圆角矩形路径之后,我们需要描边和填充路径。
这一步的结果是:

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。这是因为我们不希望文本在其他所有内容上方。如果ButtonStyle有一个foreground组件,则不需要此操作。相反,我们将Label项目作为background的子项添加。

该代码的视觉效果与上一步相同。

第三步:闪亮效果

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

我们需要画一个与第一步相同的圆角矩形,但这次我们要用从上到下的透明渐变来填充它。

Step #3-a screenshot

看起来不错,但还差一点。光泽效果只到按钮的一半,在Canvas中实现这个效果需要在绘制渐变矩形之前进行一些裁剪。你可以将Canvas中的裁剪类比为Photoshop中的“减去”矩形选框工具,只不过使用你定义的任何形状。
如果我们很幸运,而且光泽的曲线是凹的,那么我们只需要在绘制渐变矩形之前添加以下几行代码:
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
我只是在Google上随便搜索一些关于是否可以仅使用QML实现渐变按钮的内容。然后我来到了这里。哦天啊!这是一个神圣的QML代码。非常感谢分享,Mitch。 - anupx73

2

QML Image原生支持SVG格式,使用SVG工具创建图像非常简单...


1
尽管被采纳的答案确实详细说明了如何精确地完成OP所要求的操作,但这种方法将更加简单和易于维护。 - Ayberk Özgür

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接