美文网首页
PyQt/PySide6快速入门 - 4 QML天气小程序(a)

PyQt/PySide6快速入门 - 4 QML天气小程序(a)

作者: 非梦nj | 来源:发表于2022-06-15 09:12 被阅读0次

    上一篇:PyQt/PySide6快速入门 - 3 QML简介与Qt Creator开发环境 - 简书 (jianshu.com)

    本期知识点:

    QML项目目录框架,PathView动画,StackView页面切换,信号,transform动画

    天气小应用介绍:

    主界面:各个城市之间可划动选择,也可以直接点图标选择。点击某个城市开始查询天气。这类操作适合在可穿戴设备上,比如手表、手环。


    image.png

    子页面:显示天气详情。


    image.png

    上下导航条:“返回”到主界面,返回上一级

    项目目录框架:

    /images  === 图片
    /js  === js脚本
    main.py  === Python主程序 
    main.qml === QML主入口
    HomePage.qml  === Homepage主界面
    NaviButton.qml === 组件:导航返回
    WeatherPage.qml === 天气页面
    weather_qml.qrc === 资源文件
    

    步骤:

    • 使用Qt Creator,新建一个Qt for Python - Quick应用,选PySide6

    • App主程序 main.py

    • 由于使用了Qt.labs.settings,这里需要添加两行参数:QCoreApplication.setXXX

    • 创建qml engine,引用QML主文件 main.qml

    if __name__ == "__main__":
        os.environ["QML_XHR_ALLOW_FILE_READ"] = "1"
        QCoreApplication.setApplicationName("Weather App");
        QCoreApplication.setOrganizationName("QtProject");
    
        app = QGuiApplication(sys.argv)
        engine = QQmlApplicationEngine()
        engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))
        if not engine.rootObjects():
            sys.exit(-1)
        sys.exit(app.exec())
    
    • QML主入口:main.qml

    • ApplicationWindow 自带header/footer/background属性,方便设计应用框架

    • Settings组件:可以存储用户的选择,也可以当作全局变量来访问,如:settings.cityId

    • header和footer: 放置NavButton,用于导航、返回:onClicked: stackView.pop()

    • StackView:堆叠页面组件,可以让用户进入子页面,以及返回前一页面:push,pop方法

    • 初始页面为:HomePage

    • HomePage有 signal: lauched,在这里可以监听到

    ApplicationWindow {
        id: window
        visible: true
        width: 450
        height: 800
        title: qsTr("小天气 App")
    
        property string page
    
        Settings {
            id: settings
            property int cityId
        }
    
        property alias settings: settings // 全局变量
    
        background: Image {
            source: "images/background-light.png"
        }
    
        header: NaviButton {
            id: homeButton
            edge: Qt.TopEdge
            enabled: stackView.depth > 1
            imageSource: "images/home.png"
            onClicked: stackView.pop(null)
        }
    
        footer: NaviButton {
            id: backButton
            edge: Qt.BottomEdge
            enabled: stackView.depth > 1
            imageSource: "images/back.png"
            onClicked: stackView.pop()
        }
    
        StackView {
            id: stackView
            focus: true
            anchors.fill: parent
            initialItem: HomePage {  // Qt6必须以function方式响应signal
                onLaunched: (_) => stackView.push("WeatherPage.qml")
            }
        }
    }
    
    
    • 创建主界面UI:HomePage.qml

    • 定义信号:signal launched(int cityId)

    • 使用PathView,动态放置所有可选的model item(城市图标),到一条自定义路径上

    • Path :自定义路径。PathLine是画一条直线,PathArc画圆弧。里面可以用PathAttribute定义每个节点的特殊属性。我画的路径是从中心先往左,然后向上半圆移动到最右边,最后回到中心。

    • Delegate:设置每个子元素的渲染方法。这里用RoundButton 渲染。监听onClicked事件,发送信号,并存储cityId到全局变量中

    • Text组件:显示当前选择城市的名字

    • 可以用鼠标左右滑动,也可以直接点击某个城市图标。然后再点中间,就会进入到子页面(天气详情)

    PathView {
        id: circularView
    
        signal launched(int cityId)
    
        readonly property int cX: width / 2
        readonly property int cY: height / 2
        readonly property int itemSize: size / 3
        readonly property int size: Math.min(width-80, height)
        readonly property int radius: size / 2.4 //画Path圆的半径,越大图标离得越远
    
        snapMode: PathView.SnapToItem
        anchors.fill: parent
        model: ListModel {
            ListElement {
                name: qsTr("南京")
                icon: "images/nanjing.png"
                cityId: 1
            }
    。。。
            ListElement {
                name: "香港"
                icon: "images/hongkong.png"
                cityId: 7
            }
        }
        delegate: RoundButton {
            width: itemSize
            height: itemSize
    
            property string name: model.name //必须要设置prop,不然其它Component不能访问currentItem.xxx
            property string cityId: model.cityId
    
            icon.width: width*.7
            icon.height: width*.7
            icon.source: model.icon
            opacity: PathView.itemOpacity
    
            background: Rectangle {
                radius: width / 2
                border.width: 3
                border.color: parent.PathView.isCurrentItem ? "#41cd52" : "#aaaaaa"
            }
    
            onClicked: {
                console.log(`currIndex=${circularView.currentIndex}, index=${index}, id: ${name}`)
                if (PathView.isCurrentItem){
                    settings.cityId = cityId
                    circularView.launched(cityId) }
                else
                    circularView.currentIndex = index
            }
        }
    
        path: Path {
            startX: cX; startY: cY
            PathAttribute {name: "itemOpacity"; value: 1.0}
            PathLine { x: cX/5; y: cY}
            PathAttribute {name: "itemOpacity"; value: .2}
            PathArc {
                x: cX*1.8
                y: cY
                radiusX: cX/1.2
                radiusY: circularView.radius/1.5
                useLargeArc: true
                direction: PathArc.Clockwise
            }
            PathAttribute {name: "itemOpacity"; value: .2}
            PathLine { x: cX; y: cY }
            PathAttribute {name: "itemOpacity"; value: .1}
          }
    
        Text {
            id: nameText
            property Item currentItem: circularView.currentItem
            visible: currentItem ? currentItem.PathView.itemOpacity === 1.0 : 0
            text: currentItem ? currentItem.name : ""
        }
    }
    
    
    • NavButton.qml 子组件

    • 你的主QML文件,会自动调用当前目录下的所有*.qml文件,作为子组件引入,然后可直接使用它,比如NavButton。当然,也可以用import命令来手动引入子模块、js代码等

    • 添加自定义属性imageSource:父组件可以通过它来对子组件的属性赋值,imageSource: "images/xxx.png",上下图标选择对应的.png文件

    • 添加了动画Behavior on y { NumberAnimation { } :当enabled(父组件设置)时,监听y值变化,然后动态自动改变值从初始值到目标值,达到动画显示和隐藏的效果,默认动画时长250ms

    AbstractButton {
        id: button
    
        property int edge: Qt.TopEdge
        property alias imageSource: image.source
    
        contentItem: Image {
            id: image
            fillMode: Image.Pad
            sourceSize { width: 40; height: 40 } // ### TODO: resize the image
        }
    
        background: Rectangle {
            height: button.height * 4
            width: height
            radius: width / 2
            anchors.horizontalCenter: button.horizontalCenter
            anchors.top: edge === Qt.BottomEdge ? button.top : undefined
            anchors.bottom: edge === Qt.TopEdge ? button.bottom : undefined
    
        }
    
        transform: Translate {
            Behavior on y { NumberAnimation { } }
            y: enabled ? 0 : edge === Qt.TopEdge ? -button.height : button.height
        }
    }
    
    

    下一篇:会讲天气详情页面的设计,包含调用javascript,发送信号给Ptyhon后台,以及本地文件权限管理等。

    相关文章

      网友评论

          本文标题:PyQt/PySide6快速入门 - 4 QML天气小程序(a)

          本文链接:https://www.haomeiwen.com/subject/nvztvrtx.html