QML动画实战指南:打造华丽且高性能的用户界面动效

这篇具有很好参考价值的文章主要介绍了QML动画实战指南:打造华丽且高性能的用户界面动效。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引言

QML与Qt Quick简介

QML(Qt Meta-Object Language)是一种声明式编程语言,用于设计和实现基于Qt的用户界面。它允许开发者以更简单、高效的方式创建动态、响应式的UI。Qt Quick是Qt的一个子模块,提供了一套基于QML的UI开发框架,可以方便地创建出跨平台的图形用户界面。

动画在现代UI设计中的重要性

动画在现代UI设计中占据着举足轻重的地位。通过使用动画,可以实现流畅、自然的交互,增强用户体验。动画还可以用于吸引用户注意力、传达状态变化以及指示操作反馈等。在许多情况下,动画已经成为现代UI设计中不可或缺的一部分。

QML Animation模块概述

QML提供了一套强大的动画模块,用于创建和控制动画效果。这些模块包括:

  1. 基本动画类型:QML提供了多种基本动画类型,如NumberAnimation、ColorAnimation、RotationAnimation等,分别用于对数值、颜色、旋转等属性进行动画操作。
  2. 动画组合与控制:QML支持将多个动画组合在一起,形成更复杂的动画效果。例如,可以使用ParallelAnimation同时播放多个动画,或使用SequentialAnimation按顺序播放动画。此外,还可以使用AnimationController、PauseAnimation等组件对动画进行精确控制。
  3. 路径动画:通过PathAnimation,可以让动画沿着指定的路径进行。路径可以是简单的线段、曲线,也可以是更复杂的形状。使用PathInterpolator可以对路径上的值进行插值操作,以实现平滑的动画效果。
  4. 状态与过渡:QML支持使用状态(State)和过渡(Transition)来管理UI元素的不同状态及其之间的过渡动画。通过定义多个状态和相应的过渡动画,可以实现复杂的交互逻辑和视觉效果。
  5. 属性动画:PropertyAnimation可以对任意QML属性进行动画操作。它允许开发者为属性指定起始值、终止值、持续时间等参数,从而实现自定义的动画效果。还可以使用Easing对象定义动画的缓动函数,以实现不同的动画速度曲线。

通过结合这些模块和技术,开发者可以在QML中创建出丰富、生动的动画效果,为用户带来更加出色的交互体验。

QML动画基础概念

在QML中,动画是用于修改可视对象属性的强大工具。QML提供了许多内置的动画类型,可以实现丰富的视觉效果和交互。以下是一些QML动画的基本概念:

属性动画(PropertyAnimation)

PropertyAnimation是QML中最基本的动画类型,用于修改可视对象的属性值。例如,可以使用PropertyAnimation来改变对象的位置、大小、透明度等。在定义PropertyAnimation时,需要指定要修改的目标对象、属性名、起始值、结束值、持续时间等参数。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    width: 100; height: 100
    color: "blue"

    PropertyAnimation {
        id: moveAnimation
        target: rect
        property: "x"
        from: 0
        to: 200
        duration: 1000
    }

    MouseArea {
        anchors.fill: parent
        onClicked: moveAnimation.start()
    }
}

父动画(ParentAnimation)

ParentAnimation允许在动画过程中修改对象的父项。这在某些情况下非常有用,如将对象从一个容器移动到另一个容器。在定义ParentAnimation时,需要指定目标对象、新的父项以及父项变更的起始值和结束值。

示例:

import QtQuick 2.15

Item {
    width: 300; height: 300

    Rectangle {
        id: rect1
        width: 100; height: 100
        color: "red"
    }

    Rectangle {
        id: rect2
        x: 200; y: 200
        width: 100; height: 100
        color: "green"
    }

    Rectangle {
        id: rect3
        width: 50; height: 50
        color: "blue"
    }

    ParentAnimation {
        id: changeParentAnimation
        target: rect3
        newParent: rect2
        duration: 1000
    }

    MouseArea {
        anchors.fill: parent
        onClicked: changeParentAnimation.start()
    }
}

颜色动画(ColorAnimation)

ColorAnimation用于修改对象的颜色属性,例如背景颜色、边框颜色等。在定义ColorAnimation时,需要指定目标对象、属性名、起始颜色、结束颜色以及持续时间。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    width: 100; height: 100
    color: "red"

    ColorAnimation {
        id: colorAnimation
        target: rect
        property: "color"
        from: "red"
        to: "blue"
        duration: 1000
    }

    MouseArea {
        anchors.fill: parent
        onClicked: colorAnimation.start()
    }
}

数字动画(NumberAnimation)

NumberAnimation用于修改对象的数值属性,如位置、大小、透明度等。在定义NumberAnimation时,需要指定目标对象、属性名、起始值、结束值以及持续时间。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    width: 100; height: 100
    color: "blue"

    NumberAnimation {
        id: sizeAnimation
        target: rect
        property: "width"
        from: 100
        to: 200
        duration: 1000
    }

    MouseArea {
        anchors.fill: parent
        onClicked: sizeAnimation.start()
    }
}

矩形动画(RectAnimation)

RectAnimation用于修改对象的矩形属性,如位置和大小。在定义RectAnimation时,需要指定目标对象、属性名、起始矩形、结束矩形以及持续时间。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    x: 0; y: 0
    width: 100; height: 100
    color: "blue"

    RectAnimation {
        id: rectAnimation
        target: rect
        property: "x"
        from: Qt.rect(0, 0, 100, 100)
        to: Qt.rect(200, 200, 200, 200)
        duration: 1000
    }

    MouseArea {
        anchors.fill: parent
        onClicked: rectAnimation.start()
    }
}

路径动画(PathAnimation)

PathAnimation用于沿指定路径修改对象的位置。在定义PathAnimation时,需要指定目标对象、路径数据以及持续时间。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    x: 0; y: 0
    width: 50; height: 50
    color: "blue"

    PathAnimation {
        id: pathAnimation
        target: rect
        path: Path {
            startX: 0; startY: 0
            PathCubic {
                x: 300; y: 0
                control1X: 100; control1Y: 200
                control2X: 200; control2Y: -200
            }
        }
        duration: 1000
    }

    MouseArea {
        anchors.fill: parent
        onClicked: pathAnimation.start()
    }
}

通过组合和嵌套这些动画类型,可以实现丰富的视觉效果和交互。可以使用SequentialAnimation和ParallelAnimation对动画进行顺序和并行组合,创建更复杂的动画序列。

QML动画进阶技巧

在QML中,除了基本的动画类型,还可以使用高级动画技巧来创建更复杂的动画效果。以下是两个常用的动画进阶技巧:

并行动画(ParallelAnimation)

ParallelAnimation用于同时播放多个动画。在定义ParallelAnimation时,需要将多个子动画添加到animations属性中。当ParallelAnimation启动时,所有子动画将同时播放。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    width: 100; height: 100
    color: "blue"

    ParallelAnimation {
        id: parallelAnimation

        NumberAnimation {
            target: rect
            property: "x"
            from: 0
            to: 200
            duration: 1000
        }

        ColorAnimation {
            target: rect
            property: "color"
            from: "blue"
            to: "red"
            duration: 1000
        }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: parallelAnimation.start()
    }
}

顺序动画(SequentialAnimation)

SequentialAnimation用于按顺序播放多个动画。在定义SequentialAnimation时,需要将多个子动画添加到animations属性中。当SequentialAnimation启动时,子动画将按照添加顺序依次播放。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    width: 100; height: 100
    color: "blue"

    SequentialAnimation {
        id: sequentialAnimation

        NumberAnimation {
            target: rect
            property: "x"
            from: 0
            to: 200
            duration: 1000
        }

        ColorAnimation {
            target: rect
            property: "color"
            from: "blue"
            to: "red"
            duration: 1000
        }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: sequentialAnimation.start()
    }
}

配合状态使用的动画(State & Transition)

状态是描述对象属性值的集合,过渡是描述从一个状态到另一个状态的动画效果。通过在states属性中定义多个State对象,并在transitions属性中定义相应的Transition对象,可以为对象的状态更改创建动画效果。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    width: 100; height: 100
    color: "blue"

    states: [
        State {
            name: "state1"
            PropertyChanges {
                target: rect
                x: 200
                color: "red"
            }
        }
    ]

    transitions: [
        Transition {
            from: ""; to: "state1"
            NumberAnimation {
                target: rect
                property: "x"
                duration: 1000
            }
            ColorAnimation {
                target: rect
                property: "color"
                duration: 1000
            }
        }
    ]

    MouseArea {
        anchors.fill: parent
        onClicked: rect.state = rect.state === "state1" ? "" : "state1"
    }
}

动画行为(Behavior)

Behavior是一种自动将动画应用于属性更改的方法。在定义Behavior时,需要指定要应用动画的属性名以及要使用的动画类型。当指定属性的值发生更改时,将自动播放关联的动画。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    x: 0; y: 0
    width: 100; height: 100
    color: "blue"

    Behavior on x {
        NumberAnimation {
            duration: 1000
        }
    }

    Behavior on color {
        ColorAnimation {
            duration: 1000
        }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            rect.x = rect.x === 0 ? 200 : 0;
            rect.color = rect.color === "blue" ? "red" : "blue";
        }
    }
}

通过结合状态、过渡和动画行为,可以实现基于状态的动画效果以及为属性更改自动应用动画。这可以简化动画的创建和维护,提高开发效率。同时,需要关注性能问题,确保动画效果不会导致应用的响应缓慢或卡顿。

QML动画特效

在QML中,除了前面介绍的基本动画类型,还有一些专门用于创建特效的动画类型。以下是两个常用的动画特效类型:

透明度动画(OpacityAnimation)

OpacityAnimation用于修改对象的透明度属性,实现淡入淡出效果。在定义OpacityAnimation时,需要指定目标对象、起始透明度、结束透明度以及持续时间。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    width: 100; height: 100
    color: "blue"
    opacity: 1.0

    OpacityAnimation {
        id: opacityAnimation
        target: rect
        from: 1.0
        to: 0.0
        duration: 1000
    }

    MouseArea {
        anchors.fill: parent
        onClicked: opacityAnimation.start()
    }
}

旋转动画(RotationAnimation)

RotationAnimation用于修改对象的旋转属性,实现旋转效果。在定义RotationAnimation时,需要指定目标对象、起始角度、结束角度以及持续时间。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    width: 100; height: 100
    color: "blue"
    anchors.centerIn: parent
    rotation: 0

    RotationAnimation {
        id: rotationAnimation
        target: rect
        from: 0
        to: 360
        duration: 1000
        loops: Animation.Infinite
    }

    MouseArea {
        anchors.fill: parent
        onClicked: rotationAnimation.running ? rotationAnimation.pause() : rotationAnimation.resume()
    }
}

通过使用这些特效动画类型,可以为对象添加更多丰富的视觉效果和交互。你可以灵活地组合不同类型的动画,包括顺序、并行、状态、过渡以及行为动画,实现各种复杂的动画效果。在设计动画时,注意关注性能问题,确保动画效果不会导致应用的响应缓慢或卡顿。

缩放动画(ScaleAnimation)

ScaleAnimation 用于修改对象的缩放属性,实现缩放效果。在定义 ScaleAnimation 时,需要指定目标对象、起始缩放值、结束缩放值以及持续时间。

示例:

import QtQuick 2.15

Rectangle {
    id: rect
    width: 100; height: 100
    color: "blue"
    anchors.centerIn: parent
    scale: 1

    ScaleAnimation {
        id: scaleAnimation
        target: rect
        from: 1
        to: 2
        duration: 1000
    }

    MouseArea {
        anchors.fill: parent
        onClicked: scaleAnimation.start()
    }
}

高级视觉效果(Qt Graphical Effects)

Qt Graphical Effects 库为 QML 应用提供了一组高级视觉效果,如模糊、阴影、色调映射等。为了使用这些效果,需要在 QML 文件中导入 Qt Graphical Effects 模块。请注意,这些效果可能会对性能产生较大影响,因此在性能受限的设备上使用时要谨慎。

示例(应用高斯模糊效果):

import QtQuick 2.15
import QtGraphicalEffects 1.15

Rectangle {
    id: rect
    width: 100; height: 100
    color: "blue"
    anchors.centerIn: parent
}

GaussianBlur {
    id: blurEffect
    anchors.fill: rect
    source: rect
    radius: 8
}

在开发 QML 应用时,可以利用这些动画类型和高级视觉效果库为用户提供更丰富的体验。请注意,在设计动画和视觉效果时,需要关注性能问题,确保动画效果不会导致应用的响应缓慢或卡顿。

QML动画实战案例

制作一个简单的按钮动画效果

在这个实战案例中,我们将使用 QML 制作一个简单的按钮动画效果。当用户单击按钮时,按钮将逐渐变大,然后恢复到原始大小。这个例子将涉及缩放动画(ScaleAnimation)和顺序动画(SequentialAnimation)。

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: "Button Animation Example"

    Button {
        id: button
        text: "Click me!"
        anchors.centerIn: parent
        scale: 1.0

        SequentialAnimation {
            id: buttonAnimation
            target: button
            ScaleAnimation {
                from: 1.0
                to: 1.2
                duration: 200
            }
            ScaleAnimation {
                from: 1.2
                to: 1.0
                duration: 200
            }
        }

        onClicked: {
            buttonAnimation.start();
        }
    }
}

在这个例子中,我们首先导入了 QtQuick 和 QtQuick.Controls 模块。然后,我们创建了一个 ApplicationWindow,其内部包含一个 Button。该 Button 的缩放比例(scale)初始化为 1.0。

我们为按钮创建了一个顺序动画(SequentialAnimation),并将其 target 属性设置为按钮。这个顺序动画包含两个缩放动画(ScaleAnimation),分别用于按钮的放大和缩小。首个缩放动画将按钮的缩放比例从 1.0 变为 1.2,持续时间为 200 毫秒。接下来的缩放动画将缩放比例从 1.2 变回 1.0,同样持续 200 毫秒。

最后,我们为 Button 的 onClicked 事件关联动画。当按钮被单击时,顺序动画将启动,实现按钮的放大和缩小效果。

实现一个复杂的界面切换动画

在这个实战案例中,我们将使用 QML 制作一个复杂的界面切换动画。我们将创建两个不同的界面,当用户点击一个按钮时,当前界面将向左滑出,新界面将从右侧滑入。这个例子将涉及平移动画(PropertyAnimation)和并行动画(ParallelAnimation)。

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: "Complex Transition Animation Example"

    Rectangle {
        id: screen1
        width: parent.width
        height: parent.height
        color: "lightblue"
        anchors.left: parent.left

        Text {
            text: "Screen 1"
            font.pixelSize: 36
            anchors.centerIn: parent
        }
    }

    Rectangle {
        id: screen2
        width: parent.width
        height: parent.height
        color: "lightgreen"
        anchors.right: parent.right
        x: parent.width

        Text {
            text: "Screen 2"
            font.pixelSize: 36
            anchors.centerIn: parent
        }
    }

    Button {
        id: button
        text: "Switch Screens"
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter

        onClicked: {
            screenTransition.start()
        }
    }

    ParallelAnimation {
        id: screenTransition
        running: false

        PropertyAnimation {
            target: screen1
            properties: "x"
            from: 0
            to: -screen1.width
            duration: 500
            easing.type: Easing.InOutQuad
        }

        PropertyAnimation {
            target: screen2
            properties: "x"
            from: screen2.width
            to: 0
            duration: 500
            easing.type: Easing.InOutQuad
        }
    }
}

在这个例子中,我们首先导入了 QtQuick 和 QtQuick.Controls 模块。然后,我们创建了一个 ApplicationWindow,其内部包含两个 Rectangle 作为界面,以及一个 Button 用于切换界面。

我们为界面切换创建了一个并行动画(ParallelAnimation),并将其运行状态设置为 false。并行动画包含两个平移动画(PropertyAnimation),分别用于界面 1 和界面 2 的平移。

为界面 1 的平移动画设置了 target 为 screen1,属性为 “x”,起始值为 0,结束值为 -screen1.width,持续时间为 500 毫秒。这样界面 1 将向左滑出。同样地,为界面 2 的平移动画设置了 target 为 screen2,属性为 “x”,起始值为 screen2.width,结束值为 0,持续时间为 500 毫秒。这样界面 2 将从右侧滑入。我们还为两个平移动画设置了缓动类型(easing.type)为 InOutQuad,使得动画在开始和结束时具有平滑的加速和减速效果。

最后,我们为 Button 的 onClicked 事件关联动画。

用动画实现自定义进度条

在这个实战案例中,我们将使用 QML 制作一个自定义进度条,它将具有平滑的动画效果。当进度值发生变化时,进度条的长度将随之改变。这个例子将涉及数字动画(NumberAnimation)。

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: "Custom Progress Bar with Animation"

    Rectangle {
        id: progressBarBackground
        width: parent.width * 0.8
        height: 30
        color: "lightgray"
        anchors.centerIn: parent
        radius: 15

        Rectangle {
            id: progressBarFill
            width: progressBarBackground.width * 0.3
            height: progressBarBackground.height
            color: "blue"
            radius: 15
        }
    }

    Button {
        id: button
        text: "Increase Progress"
        anchors.bottom: progressBarBackground.top
        anchors.horizontalCenter: parent.horizontalCenter
        onClicked: {
            progressBarAnimation.to = progressBarFill.width + progressBarBackground.width * 0.1;
            progressBarAnimation.start();
        }
    }

    NumberAnimation {
        id: progressBarAnimation
        target: progressBarFill
        property: "width"
        easing.type: Easing.OutCubic
        duration: 500
    }
}

在这个例子中,我们首先导入了 QtQuick 和 QtQuick.Controls 模块。然后,我们创建了一个 ApplicationWindow,其内部包含一个背景矩形作为进度条的底色,一个填充矩形表示进度,以及一个 Button 用于增加进度。

我们设置了 progressBarBackground 的宽度为父容器宽度的 80%,高度为 30,颜色为 lightgray。我们为 progressBarFill 设置了颜色为蓝色,并设置其高度与 progressBarBackground 相同,初始宽度为 progressBarBackground 的 30%。

接下来,我们为按钮创建了一个 onClicked 事件处理函数。当按钮被点击时,我们首先将 progressBarAnimation 的目标宽度设置为当前宽度加上 progressBarBackground 宽度的 10%,然后启动动画。

最后,我们创建了一个数字动画(NumberAnimation),将其目标设置为 progressBarFill,并修改其 “width” 属性。动画的缓动类型设置为 OutCubic,使得动画在结束时具有平滑的减速效果。动画的持续时间设置为 500 毫秒。

这样,每当用户点击按钮时,进度条将平滑地增加 10% 的长度,直到达到最大值。

制作具有交互性的卡片翻转动画

在这个实战案例中,我们将使用 QML 制作一个具有交互性的卡片翻转动画。当用户点击卡片时,卡片将翻转,显示另一面的内容。这个例子将涉及旋转动画(RotationAnimation)。

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: "Interactive Card Flip Animation"

    Item {
        id: card
        width: 200
        height: 300
        anchors.centerIn: parent

        Rectangle {
            id: front
            anchors.fill: parent
            color: "lightblue"
            opacity: card.rotationY <= 90 ? 1 : 0
            radius: 15

            Text {
                text: "Front"
                font.pixelSize: 36
                anchors.centerIn: parent
            }

            MouseArea {
                anchors.fill: parent
                onClicked: flipCard.start()
            }
        }

        Rectangle {
            id: back
            anchors.fill: parent
            color: "lightgreen"
            opacity: card.rotationY > 90 ? 1 : 0
            radius: 15

            Text {
                text: "Back"
                font.pixelSize: 36
                anchors.centerIn: parent
            }

            MouseArea {
                anchors.fill: parent
                onClicked: flipCard.start()
            }
        }

        RotationAnimation {
            id: flipCard
            target: card
            property: "rotationY"
            from: 0
            to: 180
            duration: 1000
            easing.type: Easing.InOutCubic
            running: false

            onStopped: {
                if (to === 180) {
                    to = 0
                } else {
                    to = 180
                }
                from = card.rotationY
            }
        }
    }
}

在这个例子中,我们首先导入了 QtQuick 和 QtQuick.Controls 模块。然后,我们创建了一个 ApplicationWindow,其内部包含一个 Item 作为卡片容器。

我们为卡片容器创建了两个 Rectangle 作为卡片的前后两面,同时为它们分别创建了一个 MouseArea 用于检测点击事件。当卡片被点击时,将启动翻转动画。

然后,我们使用 opacity 属性为卡片的前后两面设置了不同的可见状态。当卡片的 Y 轴旋转角度小于等于 90 度时,前面可见;当卡片的 Y 轴旋转角度大于 90 度时,后面可见。

接下来,我们创建了一个旋转动画(RotationAnimation),并将其 target 设置为卡片容器。我们修改其 “rotationY” 属性以实现卡片的翻转效果。动画的起始值设置为 0,结束值设置为 180,持续时间为 1000 毫秒。缓动类型设置为 InOutCubic,使得动画在开始和结束时具有平滑的加速和减速效果。

最后,我们为旋转动画.

QML动画性能优化

在使用 QML 制作动画时,性能优化是非常重要的。这里有一些建议可以帮助你在创建 QML 动画时提高性能:

  1. 使用批处理渲染(Batch Rendering):批处理渲染是一种减少绘制调用次数的方法。尽可能地将相似的绘制操作组合在一起,从而减少图形驱动程序的状态切换。这可以通过在项目中使用相同的材质、纹理和着色器等方式实现。
  2. 合理安排动画执行顺序:在一个复杂的动画场景中,某些动画可能并不需要同时运行。考虑使用 SequentialAnimation 将动画分解为顺序执行的部分,以减少同时运行的动画数量。避免在同一时间运行大量动画,以降低性能消耗。
  3. 避免过度绘制(Overdrawing):过度绘制是指在同一个像素区域多次绘制不同的图形元素。这可能会导致渲染性能下降。使用透明度遮罩、剪辑或者分层渲染来减少不必要的绘制操作。
  4. 合理使用动画缓存:某些动画类型(如 PathAnimation 和 PropertyAnimation)可以在内存中缓存已计算的值,以便在后续帧中重复使用。这可以减少每帧所需的计算量,提高动画性能。
  5. 优化 JavaScript 代码:在 QML 项目中使用 JavaScript 时,注意优化代码以降低运行时性能消耗。避免在动画的关键帧中使用复杂的计算,尽量将计算移到动画开始前进行。
  6. 利用 GPU 加速:确保启用了硬件加速,以便充分利用 GPU 的性能。在支持 OpenGL 的设备上运行 QML 应用时,动画和图形渲染将自动使用 GPU 加速。
  7. 优化纹理和材质:在动画中使用高分辨率纹理和材质会增加 GPU 的负担。尽量使用低分辨率纹理和简单的材质,或者在可能的情况下使用平铺和组合较小纹理。
  8. 减少不可见元素的渲染:确保只渲染可见的图形元素。使用 Loader 组件按需加载和卸载不可见的元素,或者使用 visible 属性来手动控制元素的可见性。

QML动画与C++集成

使用C++与QML交互实现动画控制

在许多应用场景中,我们需要在 QML 动画与 C++ 代码之间实现交互。例如,某些应用程序可能需要在 C++ 中处理复杂数学计算并传递结果给 QML 动画。为了实现这一目标,我们可以利用 Qt 提供的 QML 与 C++ 的集成特性。

首先,让我们创建一个简单的 QML 文件,该文件定义了一个简单的动画:

// MyAnimation.qml
import QtQuick 2.15

Rectangle {
    id: animatedRect
    width: 100
    height: 100
    color: "red"

    PropertyAnimation {
        id: myAnimation
        target: animatedRect
        properties: "x"
        from: 0
        to: 400
        duration: 2000
        running: false
    }

    MouseArea {
        anchors.fill: parent
        onClicked: myAnimation.start()
    }
}

然后,我们将在 C++ 代码中创建一个 QObject 派生类,用于与 QML 动画进行交互:

// animationcontroller.h
#ifndef ANIMATIONCONTROLLER_H
#define ANIMATIONCONTROLLER_H

#include <QObject>

class AnimationController : public QObject
{
    Q_OBJECT
public:
    explicit AnimationController(QObject *parent = nullptr);

public slots:
    void startAnimation(QObject *animation);

};

#endif // ANIMATIONCONTROLLER_H
// animationcontroller.cpp
#include "animationcontroller.h"
#include <QQmlProperty>

AnimationController::AnimationController(QObject *parent) : QObject(parent)
{
}

void AnimationController::startAnimation(QObject *animation)
{
    if (animation) {
        QQmlProperty::write(animation, "running", true);
    }
}

AnimationController 类中,我们实现了一个名为 startAnimation 的槽,该槽接收一个 QObject * 参数,用于启动 QML 动画。

接下来,我们需要在主程序中将 C++ 类的实例与 QML 上下文关联:

// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "animationcontroller.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    AnimationController animationController;
    engine.rootContext()->setContextProperty("animationController", &animationController);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

最后,在 QML 文件中,我们可以通过访问 animationController 实例来调用 startAnimation() 方法:

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: "QML Animation with C++ Integration"

    MyAnimation {
        id: animationComponent
        anchors.centerIn: parent
    }

    Button {
        text: "Start Animation from C++"
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        onClicked: animationController.startAnimation(animationComponent.myAnimation)
    }
}

用C++实现自定义动画类型

要使用 C++ 实现自定义动画类型,可以从 QAbstractAnimation 或其中一个现有的派生类(如 QVariantAnimation 或 QAnimationGroup)进行扩展。在这个示例中,我们将创建一个简单的自定义动画,它可以控制一个 QML 矩形的宽度。

首先,创建一个从 QVariantAnimation 派生的 C++ 类:

// customanimation.h
#ifndef CUSTOMANIMATION_H
#define CUSTOMANIMATION_H

#include <QVariantAnimation>

class CustomAnimation : public QVariantAnimation
{
    Q_OBJECT
public:
    explicit CustomAnimation(QObject *parent = nullptr);

protected:
    void updateCurrentValue(const QVariant &value) override;

};

#endif // CUSTOMANIMATION_H

在 customanimation.cpp 文件中,实现类的方法。在 updateCurrentValue 方法中,我们可以访问动画的当前值,并根据需要对其进行处理:

// customanimation.cpp
#include "customanimation.h"
#include <QDebug>

CustomAnimation::CustomAnimation(QObject *parent) : QVariantAnimation(parent)
{
}

void CustomAnimation::updateCurrentValue(const QVariant &value)
{
    qDebug() << "Current animation value:" << value;
}

接下来,在主程序中创建 CustomAnimation 的实例,并将其与 QML 上下文关联:

// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "customanimation.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    CustomAnimation customAnimation;
    engine.rootContext()->setContextProperty("customAnimation", &customAnimation);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

最后,在 QML 文件中使用自定义动画。我们需要将动画的起始值、结束值、持续时间等属性设置为所需的值,并在适当的时机调用 start() 方法启动动画:

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: "QML with Custom Animation in C++"

    Rectangle {
        id: animatedRect
        width: 100
        height: 100
        color: "red"
        anchors.centerIn: parent
    }

    Button {
        text: "Start Custom Animation"
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        onClicked: {
            customAnimation.target = animatedRect;
            customAnimation.propertyName = "width";
            customAnimation.startValue = 100;
            customAnimation.endValue = 300;
            customAnimation.duration = 2000;
            customAnimation.start();
        }
    }
}

在这个示例中,当用户点击按钮时,自定义动画将启动并控制矩形的宽度。这只是一个简单的例子,你可以根据需求实现更复杂的自定义动画类型。

动画的数据绑定与性能提升

在 QML 中,数据绑定可以让我们轻松地将动画与 UI 元素关联。然而,数据绑定也可能导致性能问题,尤其是在大量动画、复杂场景或低性能设备上。在这种情况下,我们需要采取一些策略来提高动画性能。

  1. 避免不必要的数据绑定:尽量减少对数据绑定的依赖,仅在确实需要时使用。例如,如果一个属性值始终保持恒定,我们可以直接为其赋值,而无需使用数据绑定。如果某个值的变化对动画不产生影响,那么不要将其绑定到动画属性中。
  2. 使用批处理渲染(Batch Rendering):当渲染许多相似的元素(如具有相同材质的多个几何体)时,使用批处理渲染可以大幅减少渲染调用次数。这样做可以降低 CPU 负载,提高渲染性能。为了实现批处理渲染,你需要确保使用相同的材质、着色器和渲染设置。
  3. 使用 Loader 延迟加载动画:使用 Loader 组件按需加载动画元素,这样可以减少在初始化阶段的计算负担。这对于很多动画或大型场景特别有用。
Loader {
    id: animationLoader
    source: ""
    onLoaded: {
        item.start();
    }
}

// 按需加载动画
function loadAnimation() {
    animationLoader.source = "SomeAnimation.qml";
}
  1. 使用动画行为(Behavior):通过将 Behavior 与动画属性相关联,我们可以在属性发生变化时自动触发动画。这可以减少手动触发动画的需要,并提高动画的流畅性。
Rectangle {
    id: myRect
    width: 100
    height: 100
    color: "red"

    Behavior on width {
        NumberAnimation { duration: 300 }
    }
}
  1. 分离动画逻辑与渲染逻辑:将动画逻辑与渲染逻辑分离,这样可以降低复杂度,提高性能。例如,可以将计算动画属性值的逻辑放在 C++ 代码中,然后将结果传递给 QML 动画。这可以减轻 QML 引擎的负担,从而提高性能。
  2. 尽量减少同时运行的动画数量:有时候可以通过重新设计 UI,避免同时运行大量动画,以降低性能消耗。

QML动画的跨平台适应性

在不同平台上调整动画性能

在开发跨平台的 QML 应用时,需要考虑不同设备和操作系统之间的性能差异。为了在各种平台上提供良好的用户体验,我们需要确保动画能够在各种设备上流畅运行。以下是一些调整动画性能以适应不同平台的技巧和建议:

  1. 使用动态持续时间:为了在性能较差的设备上保持流畅的动画效果,可以动态地根据设备性能调整动画持续时间。例如,可以根据设备的屏幕刷新率或硬件性能来设置动画持续时间。这样,即使在低性能设备上,动画仍然能够流畅地运行。
NumberAnimation {
    duration: isHighPerformanceDevice ? 300 : 500
}
  1. 降低动画复杂度:在低性能设备上,可以适当降低动画的复杂度,例如减少并行动画的数量、简化动画的效果等。可以使用条件语句在不同平台上加载不同的动画效果。
Loader {
    id: animationLoader
    source: isHighPerformanceDevice ? "ComplexAnimation.qml" : "SimpleAnimation.qml"
}
  1. 采用硬件加速:尽可能使用硬件加速技术,如 OpenGL。许多现代设备都支持 OpenGL,使用这类技术可以有效地提高动画性能。在 Qt Quick 中,默认使用 OpenGL 进行渲染,所以大部分情况下无需额外处理。
  2. 根据设备特性选择动画类型:针对不同设备的特性选择合适的动画类型。例如,对于具有触摸屏的移动设备,可以优先使用更适合触摸操作的动画,如拖动、缩放等。
  3. 优化资源:减少动画所需的资源数量,以降低内存占用。这可以通过减少纹理尺寸、使用压缩纹理格式、优化 3D 模型等方法实现。
  4. 测试和调优:在各种目标平台上进行充分的测试和调优,以确保动画能够在各种设备上正常运行。可以使用性能分析工具(如 Qt Creator 自带的性能分析器)来监控应用程序的性能,并根据实际测试结果进行调优。

通过以上方法,我们可以确保 QML 动画在不同平台上具有良好的适应性。同时,针对性地进行调整和优化,确保在不同设备上提供最佳的用户体验。

跨平台的动画兼容性问题

在开发跨平台应用时,动画兼容性问题可能会出现。这些问题可能源于不同操作系统、硬件配置、渲染引擎和性能特点等方面的差异。为了解决这些兼容性问题,以下是一些建议和技巧:

  1. 保持动画简单:尽量使用简单的动画效果,避免使用复杂或平台特定的效果。这样可以降低兼容性问题的出现概率。
  2. 遵循 Qt Quick 标准:遵循 Qt Quick 动画和视觉效果的标准和建议,避免使用不兼容或过时的技术。
  3. 避免平台特定代码:在编写动画代码时,避免使用平台特定的语法和功能。如果确实需要针对特定平台编写代码,可以使用条件编译或运行时条件判断。
#if defined(Q_OS_ANDROID)
// Android specific code
#elif defined(Q_OS_IOS)
// iOS specific code
#else
// Default code for other platforms
#endif
  1. 使用自适应布局:为了保证动画在不同分辨率和屏幕尺寸的设备上表现一致,使用自适应布局进行设计。在 Qt Quick 中,可以使用锚布局(Anchors)、网格布局(GridLayout)等布局方法。
  2. 进行充分的跨平台测试:在开发过程中,在各种目标平台上进行充分的测试,以确保动画能够在不同设备上正常运行。对于发现的兼容性问题,及时进行修复和调整。
  3. 监控并优化性能:使用性能监控工具,如 Qt Creator 的性能分析器,以评估在各个平台上的动画性能。针对性能瓶颈和兼容性问题,进行调优和优化。
  4. 使用开源库和社区资源:可以利用开源库和社区资源来解决兼容性问题。许多开源库针对跨平台问题进行了优化,使用这些库可以提高兼容性。同时,可以查找社区中关于类似问题的解决方案。

通过考虑以上建议,我们可以最大限度地减少跨平台的动画兼容性问题,确保应用程序在各种设备上提供一致且高质量的用户体验。

为不同平台提供定制化动画效果

为了在不同平台上提供更好的用户体验,可以根据各个平台的特点和用户习惯,为每个平台提供定制化的动画效果。以下是一些为不同平台提供定制化动画效果的方法和技巧:

  1. 使用条件语句:在 QML 代码中使用条件语句,针对不同平台加载不同的动画效果。例如,可以根据操作系统类型或设备类型选择相应的动画文件。
Loader {
    id: animationLoader
    source: isAndroid ? "AndroidAnimation.qml" : (isIOS ? "iOSAnimation.qml" : "DefaultAnimation.qml")
}
  1. 设备特性检测:根据设备的特性,如触摸屏、鼠标和键盘输入等,选择适合的动画效果。例如,为触摸屏设备提供更适合触摸操作的动画,如拖动、缩放等。
  2. 遵循平台设计指南:为每个平台遵循相应的设计指南,确保动画效果与平台的视觉和交互风格一致。例如,Android 设备上遵循 Material Design,而 iOS 设备上遵循 Human Interface Guidelines。
  3. 使用平台特定的动画库:可以考虑使用针对特定平台的动画库或框架,如 Android 的 Lottie。这些库通常提供了丰富的预定义动画效果,可以方便地实现平台特定的动画效果。
  4. 定义平台相关的属性和方法:在 QML 代码中定义平台相关的属性和方法,根据平台特点调整动画参数。例如,可以根据平台设置不同的动画持续时间、缓动类型等。
NumberAnimation {
    duration: isAndroid ? 300 : 400
    easing.type: isAndroid ? Easing.InOutQuad : Easing.InOutCubic
}
  1. 使用平台特定的视觉元素和资源:使用平台特定的视觉元素和资源,如图标、字体和颜色。这样可以确保动画效果与平台风格保持一致,提高用户体验。
  2. 进行充分的跨平台测试:在各种目标平台上进行充分的测试,以确保定制化的动画效果在不同设备上正常运行。对于发现的兼容性问题,及时进行修复和调整。

通过以上方法,我们可以为不同平台提供定制化的动画效果,确保应用程序在各种设备上提供最佳的用户体验。

QML动画和Qt动画的权衡和对比

QML(Qt Meta-Object Language)和Qt(C++)都提供了创建动画的功能。QML是一种基于JavaScript的脚本语言,主要用于构建跨平台的用户界面。而Qt动画则是在C++中使用Qt库实现的动画。以下是QML动画和Qt动画的权衡和对比:

  1. 易用性与学习曲线:QML动画具有更简洁的语法和更高的易用性。QML中的动画可以用几行代码轻松创建,而在C++中创建Qt动画则需要更多的代码和配置。此外,QML动画对于具有前端开发或Web开发经验的开发者来说,学习曲线较为平缓。
  2. 性能:Qt动画在C++中实现,因此通常具有较高的性能,尤其是在对性能要求较高的应用程序中。而QML动画基于JavaScript,性能可能稍逊于C++实现。然而,在大多数情况下,QML动画的性能已足够满足一般应用程序的需求。
  3. 跨平台兼容性:QML和Qt都具有很好的跨平台兼容性,可以在不同平台和设备上运行。但QML更加关注界面的自适应和响应式设计,因此在处理各种屏幕尺寸和分辨率时,QML动画可能表现得更好。
  4. 可维护性与模块化:QML动画通常与用户界面元素紧密相关,这有助于将动画与应用程序逻辑分离,提高代码的可维护性。而在C++中实现的Qt动画可能使代码变得更加复杂,导致维护困难。此外,QML易于与C++后端代码集成,实现前后端分离和模块化设计。
  5. 灵活性与控制:Qt动画在C++中实现,具有更高的灵活性和控制力。开发者可以使用C++的强大功能对动画进行微调和优化。相比之下,QML动画可能在某些高度定制化的需求上存在局限性。

总结:

  • QML动画:更适合快速构建跨平台用户界面,简洁的语法和易用性使得创建动画更加轻松。QML动画在大多数情况下的性能已足够满足需求,并更注重响应式设计。
  • Qt动画:在对性能要求较高的应用程序中,C++实现的Qt动画可能更为合适。Qt动画提供了更高的灵活性和控制力,允许开发者充分利用C++的功能对动画进行微调和优化。

在选择QML动画和Qt动画时,需要根据项目的需求、目标平台和开发团队的技能进行权衡。以下是一些建议:

  • 如果项目主要关注用户界面的设计和跨平台兼容性,且对性能要求不是非常高,可以考虑使用QML动画。QML动画更适合快速构建具有高度自适应性的用户界面。
  • 如果项目对性能有较高要求,或需要高度定制化的动画效果,可以考虑使用Qt动画。Qt动画允许开发者充分利用C++的功能,提供了更高的灵活性和控制力。
  • 在某些情况下,可以考虑将QML动画和Qt动画结合使用。例如,可以使用QML动画创建基本的用户界面和动画效果,同时使用Qt动画处理性能关键部分或实现高度定制化的效果。

根据项目的实际需求和团队的技能,选择合适的动画框架,以便更好地平衡易用性、性能、可维护性和跨平台兼容性。

总结与展望

  • QML动画在UI设计中的作用与优势

QML动画在UI设计中具有重要作用和优势。它提供了一套简洁、高效的动画编程方式,让开发者能够轻松地实现丰富的动画效果。QML动画的主要优势包括:

  1. 声明式编程:QML的声明式语法使得编写动画代码更为简洁和直观,降低了开发难度。
  2. 跨平台:QML动画可跨平台运行,无需针对不同设备或操作系统编写额外的代码。
  3. 强大的动画控制:QML提供了丰富的动画组件和控制手段,支持复杂的动画组合和定制。
  4. 与UI元素紧密结合:QML动画可以直接应用于UI元素的属性,实现与界面的无缝整合。
  5. 性能优化:QML动画可以充分利用硬件加速和优化技术,提供高性能的动画效果。
  • QML动画在未来发展的趋势

随着UI设计的不断演进和技术的发展,QML动画在未来可能会呈现出以下趋势:

  1. 更高的性能:QML动画将继续优化性能,提供更流畅、更快速的动画效果。
  2. 更丰富的动画效果:随着图形技术的进步,QML动画将支持更多种类、更高级的动画效果。
  3. 与3D图形的融合:QML动画可能与3D图形技术(如Qt 3D)更紧密地结合,实现更加立体、逼真的UI设计。
  4. 人工智能与动画的结合:利用人工智能技术,QML动画可能实现更加智能、自适应的动画表现。
  5. 物联网与多设备协同:QML动画将更好地适应物联网和多设备协同的场景,为用户提供一致的交互体验。
  • 提高QML动画编程技巧与创意的建议

要提高QML动画编程技巧和创意,可以采取以下措施:

  1. 深入学习QML和Qt Quick文档,了解各种动画组件和技巧的使用方法。
  2. 关注行业动态和设计趋势,了解最新的UI设计理念和动画技术。
  3. 多实践,尝试使用QML编写各种动画效果,不断提高编程技巧和创意水平。
  4. 分析优秀的UI设计案例,学习其中的动画设计和实现方法。
  5. 参与社区和开源项目,与其他开发者交流和合作,共同提高QML动画编程能力。
  6. 创新思维,尝试将QML动画与其他技术(如3D图形、人工智能等)结合,探索新的动画表现形式。
  7. 关注用户体验,始终以用户为中心,优化动画效果和交互设计。

通过以上方法,开发者可以不断提高QML动画编程技巧和创意,为现代UI设计带来更加丰富、生动、高效的动画效果。在未来的发展中,QML动画将继续发挥重要作用,推动UI设计的创新和进步。文章来源地址https://www.toymoban.com/news/detail-486239.html

到了这里,关于QML动画实战指南:打造华丽且高性能的用户界面动效的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 高性能MySQL实战(三):性能优化

    大家好,我是 方圆 。这篇主要介绍对慢 SQL 优化的一些手段,而在讲解具体的优化措施之前,我想先对 EXPLAIN 进行介绍,它是我们在分析查询时必要的操作,理解了它输出结果的内容更有利于我们优化 SQL。为了方便大家的阅读,在下文中规定类似 key1 的表示二级索引,key_

    2024年02月11日
    浏览(46)
  • LLM 模型融合实践指南:低成本构建高性能语言模型

    LLM 模型融合实践指南:低成本构建高性能语言模型

    编者按 :随着大语言模型技术的快速发展,模型融合成为一种低成本但高性能的模型构建新途径。本文作者 Maxime Labonne 利用 mergekit 库探索了四种模型融合方法:SLERP、TIES、DARE和passthrough。通过配置示例和案例分析,作者详细阐释了这些算法的原理及实践操作。 作者的核

    2024年02月22日
    浏览(9)
  • 磊科高性能路由器上网行为管理配置指南[图文]

    每个公司都会因工作的需要而开通网络。但是问题来了,员工经常在上班时间聊QQ,浏览与工作无关的网页。有些公司曾经实施过惩罚制度,但效果不理想。为此,公司高层反对开通网络。这样一来,员工也有意见,认为如果不开通网络工作起来不方便。现在很疑惑,如果开

    2024年02月05日
    浏览(15)
  • 高性能MySQL实战(一):表结构

    最近因需求改动新增了一些数据库表,但是在定义表结构时,具体列属性的选择有些不知其所以然,索引的添加也有遗漏和不规范的地方,所以我打算为创建一个高性能表的过程以实战的形式写一个专题,以此来学习和巩固这些知识。 我使用的 MySQL 版本是 5.7,建表 DDL 语句

    2024年02月12日
    浏览(33)
  • “探索Redis:高性能键值存储数据库的实用指南“

    标题:探索Redis:高性能键值存储数据库的实用指南 引言: Redis是一种高性能的键值存储数据库,它通过将数据存储在内存中,提供了快速的读写操作。本文将介绍Redis的基本概念和常用功能,并提供示例代码帮助读者更好地理解和应用Redis。 Redis的基本概念 Redis是一个开源的

    2024年02月15日
    浏览(11)
  • Redis学习指南(28)-Redis高性能特性之单线程模型

    Redis是一种高性能、非关系型的内存数据库,被广泛应用于缓存、消息队列、任务队列等场景。Redis之所以能够达到如此高的性能,其中一个重要的原因就是其采用了单线程模型。 Redis使用单线程模型指的是主要的工作线程只有一个,这个线程负责处理所有的客户端请求和对数

    2024年01月24日
    浏览(14)
  • Redis核心数据结构实战与高性能解析

    Redis核心数据结构实战与高性能解析

    目录 一、安装Redis 二、Redis线程与高性能 2.1 Redis是单线程么? 2.2 Redis读写是单线程为何这么快? 2.3 Redis如何处理并发操作命令? 三、核心数据结构实战 3.1 字符串常用操作实战 SET 存入键值对 SETNX SETEX MSET 批量存入键值对 MSETNX DECR 原子减1 DECRBY 原子减 INCR 原子加1 INCRBY 原子

    2024年02月07日
    浏览(10)
  • 云原生 | 从零开始,Minio 高性能分布式对象存储快速入手指南

    云原生 | 从零开始,Minio 高性能分布式对象存储快速入手指南

    [ 点击 👉 关注「 全栈工程师修炼指南」公众号 ] 希望各位看友多多支持【关注、点赞、评论、收藏、投币】,助力每一个梦想。 【 WeiyiGeek Blog\\\'s - 花开堪折直须折,莫待无花空折枝  】 作者主页: 【 https://weiyigeek.top 】 博客地址: 【 https://blog.weiyigeek.top 】 作者答疑学习交

    2024年02月08日
    浏览(14)
  • HarmonyOS从基础到实战-高性能华为在线答题元服务

    HarmonyOS从基础到实战-高性能华为在线答题元服务

    最近看到美团、新浪、去哪儿多家互联网企业启动鸿蒙原生应用开发,这个HarmonyOS NEXT越来越引人关注。奈何当前不面向个人开发者开放,但是我们可以尝试下鸿蒙新的应用形态——元服务的开发。 元服务是基于HarmonyOS提供的一种面向未来的服务提供方式,有独立入口、免安

    2024年02月04日
    浏览(9)
  • 高性能分布式对象存储——MinIO实战操作(MinIO扩容)

    高性能分布式对象存储——MinIO实战操作(MinIO扩容)

    MinIO的基础概念和环境部署可以参考我之前的文章:高性能分布式对象存储——MinIO(环境部署) 官方文档:https://docs.min.io/docs/minio-admin-complete-guide.html MinIO Client (mc) 为 UNIX 命令(如 ls、cat、cp、mirror、diff、find 等)提供了现代替代方案。它支持文件系统和兼容 Amazon S3 的云存

    2023年04月26日
    浏览(10)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包