Simulate Android CircularReveal with Qml
Unsolved
QML and Qt Quick
-
Hi.
How can I create CircularReveal [1] in Qml?
I tried using both OpacityMask and ShaderEffect, but with no luck.
Using CircularReveal2, transition is not circle but an ellipse, because width and height of Item are not equal.
Using CircularReveal3, OpacityMask is anchored in Item and quality of item is improved over time.Thanks
[1] : https://github.com/ozodrukh/CircularReveal/
main.qml
import QtQuick 2.7 import QtQuick.Window 2.2 import QtQuick.Controls 1.4 Window { visible: true width: 640 height: 480 title: qsTr("Hello World") Rectangle { anchors.fill: parent color: "#55a" } CircularReveal2 { id: cr2 width: 500 height: 100 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.verticalCenter Button { text: "Click me" anchors.fill: parent } } CircularReveal3 { id: cr3 width: 500 height: 100 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.verticalCenter Button { text: "Click me" anchors.fill: parent } } }
CircularReveal2.qml
import QtQuick 2.0 Item { id: root function hypot(x, y) { return Math.sqrt(x*x + y*y) } function startAnimation() { // TODO start reveal animation } function resetAnimation() { // TODO reverse reveal animation } layer.enabled: true layer.effect: CircularShader { } }
CircularReveal3.qml
import QtQuick 2.0 import QtQuick.Controls 1.4 import QtGraphicalEffects 1.0 Item { id: root Component.onCompleted: { mask.width = mask.height = hypot(root.width, root.height) } function hypot(x, y) { return Math.sqrt(x*x + y*y) } function startAnimation() { // TODO start reveal animation } function resetAnimation() { // TODO reverse reveal animation } Rectangle { id: mask visible: false x: (root.width - mask.width) / 2 y: (root.height - mask.height) / 2 radius: width Behavior on width { NumberAnimation { duration: 5000 } } Behavior on height { NumberAnimation { duration: 5000 } } } layer.enabled: true layer.effect: OpacityMask { maskSource: mask } }
CircularShader.qml
import QtQuick 2.0 ShaderEffect { id: shader property variant source property real centerX: 0.5 // [0,1] property real centerY: 0.5 // [0,1] property bool forward: true property real smoothness: 0.1 property real progress: 0.0 Component.onCompleted: doForward() function doForward() { forwardAnimation.start() } function doBackward() { backwardAnimation.start() } function hypot(x, y) { return Math.sqrt(x*x + y*y) } NumberAnimation { id: forwardAnimation target: shader property: "progress" duration: 5000 from: 0 to: 1 // running: false } NumberAnimation { id: backwardAnimation target: shader property: "progress" duration: 5000 from: 1 to: 0 // running: false } fragmentShader: " varying highp vec2 qt_TexCoord0; uniform sampler2D source; uniform lowp float qt_Opacity; uniform highp float width; uniform highp float height; uniform highp float centerX; uniform highp float centerY; uniform float smoothness; uniform bool forward; uniform float progress; const float SQRT_2 = 1.414213562373; //float hypot(float x, float y) { // return sqrt(x*x + y*y); //} void main() { vec2 center = vec2(centerX, centerY); float x = forward ? progress : 1.-progress; gl_FragColor = distance(qt_TexCoord0, center) < progress ? texture2D(source, qt_TexCoord0) : vec4(0, 0, 0, 0); // float x = forward ? progress : 1.-progress; // float m = smoothstep(-smoothness, 0.0, SQRT_2*distance(center, qt_TexCoord0) - x*(1.+smoothness)); // gl_FragColor = mix(vec4(0, 0, 0, 0), texture2D(source, qt_TexCoord0), forward ? 1.-m : m) * qt_Opacity; } " }