Создание масштабируемой, глянцевой / блестящей кнопки с помощью 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
}
}
}
}
У меня две проблемы:
- Я не знаю, как создать эффект изогнутого блеска.
- Мне нужно, чтобы текст был под блеском, но сейчас он выше.
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, но на этот раз мы заполняем его прозрачным градиентом сверху вниз.
Хорошо выглядит, но пока не совсем. Эффект блеска останавливается на полпути вниз по кнопке, и чтобы добиться этого с помощью 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();
Вместо этого мы нарисуем кривую вручную, используя 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
}
}
}
Инверсия красной области - это область, в которой мы будем рисовать блеск.
Итак, код для отсечения выглядит следующим образом:
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 Image изначально поддерживает SVG, тогда это должно быть так же просто, как создать изображение с помощью инструмента SVG...