Создание масштабируемой, глянцевой / блестящей кнопки с помощью Qt Quick

Я хотел бы создать глянцевую кнопку ниже с помощью 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. Мне нужно, чтобы текст был под блеском, но сейчас он выше.

2 ответа

Решение

Это невозможно при использовании Rectangle. Вы можете использовать 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: эффект блеска

Канва может рисовать линейные, радиальные и конические градиенты. Мы будем использовать линейный градиент, чтобы нарисовать эффект "блеска" на нашей кнопке:

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, но на этот раз мы заполняем его прозрачным градиентом сверху вниз.

Шаг № 3 - скриншот

Хорошо выглядит, но пока не совсем. Эффект блеска останавливается на полпути вниз по кнопке, и чтобы добиться этого с помощью 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();

Шаг № 3-б скриншот

Вместо этого мы нарисуем кривую вручную, используя bezierCurveTo ().

Определение значений для передачи bezierCurveTo() это не легко, поэтому я бы предложил найти нужную кривую с помощью такого замечательного инструмента, как Крейг Баклер ( Canig Bézier Curve Example). Это позволит вам манипулировать кривыми до тех пор, пока вы не найдете то, что вам нужно, но лучше всего даст вам код, который создает эти кривые. Если вы хотите сделать обратное и отредактировать код, чтобы увидеть кривые в режиме реального времени, ознакомьтесь с учебником по HTML5 Canvas Bezier Curve.

Ниже я сделал небольшой пример, который обводит путь отсечения, чтобы упростить визуализацию:

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

Шаг № 3-с скриншот

Инверсия красной области - это область, в которой мы будем рисовать блеск.

Итак, код для отсечения выглядит следующим образом:

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

Шаг № 3-й скриншот

Теперь кнопка просматривает деталь, и ее можно щелкнуть, но она не имеет визуальной индикации взаимодействия с мышью. Давайте добавим это также.

Шаг 4: сделать его интерактивным

Для этого нужно всего две строки кода. Первая строка делает блеск холста частично прозрачным:

opacity: !button.pressed ? 1 : 0.75

Вторая увеличивает яркость текста при наведении на кнопку:

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

Вы можете пойти еще дальше и разделить стиль в своем собственном файле QML, предоставить свойство цвета и удобно использовать кнопки разного цвета.

QML Image изначально поддерживает SVG, тогда это должно быть так же просто, как создать изображение с помощью инструмента SVG...

Другие вопросы по тегам