上一篇:PyQt/PySide6快速入门 - 3 QML简介与Qt Creator开发环境 - 简书 (jianshu.com)
本期知识点:
QML项目目录框架,PathView动画,StackView页面切换,信号,transform动画
- 参考:Qt官方Example: Qt\Examples\Qt-6.3.0\quickcontrols2\wearable\
- 我的源码(更新中):https://github.com/kevinqqnj/qml_weather 。文中只是代码片段,大家可导入我的完整代码来运行
- 演示视频:Qt QML+PySide天气小程序 (zhihu.com)
天气小应用介绍:
主界面:各个城市之间可划动选择,也可以直接点图标选择。点击某个城市开始查询天气。这类操作适合在可穿戴设备上,比如手表、手环。
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后台,以及本地文件权限管理等。
网友评论