目录
本项目的交流QQ群:701889554
物联网实战--入门篇https://blog.csdn.net/ypp240124016/category_12609773.html
物联网实战--驱动篇https://blog.csdn.net/ypp240124016/category_12631333.html
本项目资源文件 https://download.csdn.net/download/ypp240124016/89317308
一、界面设计
上图是米家APP关于分组管理的界面,主页面是选项卡+视图的模式,分组管理可以删除和排序,重命名它需要单机某个分组再进入一层界面进行操作,我们做些省略,下图是模仿的界面。
二、项目工程概览
以下是针对这部分界面的项目代码结构图,主要分为两部分,一是分组切换页面——GroupTabView,二是分组管理页面——GroupEditView,其它都是对话框类型的,下面对这些内容做具体讲解。
三、分组切换
分组就是下图红框里的内容,每个名称都是一个选项卡,下面对应的界面内容也会跟着切换,原本想着用QT自带的TabView来实现,后面发现达不到想要的效果,实际上米家APP里的选项卡和页面内容是分开的,是通过当前页面值关联起来的。所以后面就自己实现了这个过程,相对比较啰嗦点。
import QtQuick 2.7
import QtQuick.Controls 2.12
import "../base"
Rectangle
{
color: "transparent"
ListView//滑动tab bar
{
id:id_tabBarView
clip:true
orientation: ListView.Horizontal
height: 40
anchors
{
left:parent.left
right:id_listButton.left
}
model: ListModel{
id:id_tabBarModel
}
delegate: Rectangle{
height: 40
width: id_titleText.contentWidth+15
color: "transparent"
Text{
id:id_titleText
height: parent.height
width: 20
anchors.centerIn: parent
font.family: Qt.platform.os === "windows" ? "宋体" : "黑体"
font.pointSize: 18
font.bold: id_tabBarView.currentIndex===id
text: group_name
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
MouseArea
{
anchors.fill: parent
onClicked:
{
id_tabBarView.currentIndex=id
}
}
}
onCurrentIndexChanged:
{
id_mainView.currentIndex=currentIndex
}
}
GroupNewDialog
{
id:id_groupNewDialog
}
GroupManDialog
{
id:id_groupManDialog
onSiqNewGroup: //新建分组
{
id_groupNewDialog.funOpen("新分组")
}
onSiqManGroup: //分组管理
{
theAccountMan.setHomeCurrView("home-group")
}
onSiqOpenGroup:
{
id_tabBarView.currentIndex=id
id_mainView.currentIndex=id
}
}
Rectangle{ //列表按钮
id:id_listButton
height: id_tabBarView.height*0.8
width: height*1.5
anchors
{
right:parent.right
rightMargin:15
verticalCenter:id_tabBarView.verticalCenter
}
radius: height/2
color: "#BFC5D6"
ImageButton01{
anchors.centerIn: parent
height: parent.height*0.85
width: height
source: "qrc:/mainImgRC/images/home/list.png"
onSiqClickedLeft:
{
id_groupManDialog.open()
}
}
}
SwipeView { //主页面切换
id: id_mainView
width: parent.width
anchors
{
top:id_tabBarView.bottom
bottom:parent.bottom
}
Repeater {
model: ListModel{
id:id_mainModel
}
Rectangle {
color: "transparent"
Text{
anchors.centerIn: parent
font.pointSize: 18
text: id
}
}
}
onCurrentIndexChanged: {
id_tabBarView.currentIndex=currentIndex
}
}
Component.onCompleted:
{
for(var i=0; i<1; i++)
{
id_tabBarModel.append({"id":i, "group_name":"全部"})
id_mainModel.append({"id":i, "group_name":"全部"})
}
}
Connections
{
target: theCenterMan
onSiqAddGroup:
{
if(index===0)
{
id_tabBarModel.clear()
id_mainModel.clear()
id_tabBarModel.append({"id":0, "group_name":"全部"})
id_mainModel.append({"id":0, "group_name":"全部"})
}
index=id_tabBarModel.count
id_tabBarModel.append({"id":index, "group_name":group_name})
id_mainModel.append({"id":index, "group_name":group_name})
}
onSiqRenameGroup:
{
for(var i=1; i<id_tabBarModel.count; i++)
{
var group_name=id_tabBarModel.get(i).group_name
if(group_name===old_name)
{
id_tabBarModel.setProperty(i, "group_name", new_name)
id_mainModel.setProperty(i, "group_name", new_name)
break
}
}
}
onSiqDelGroup:
{
for(var i=1; i<id_tabBarModel.count; i++)
{
if(group_name===id_tabBarModel.get(i).group_name)
{
id_tabBarModel.remove(i)
id_mainModel.remove(i)
break
}
}
}
}
}
分组切换具体代码如上所示,主要分为Tab和View两部分,Tab用一个ListView,这样就可以实现滑动而不切换的效果了,如果是QT的TabView,点一下就会切换过去,没法用。
ListView核心就两个内容,模型和单元格,即model和delegate两个属性,模型一般采用ListModel,通过操作ListModel实例就可以改变单元格的内容以及增减单元格等操作了。delegate就是单元格具体内容了,在这里主要就是文本,另外背景的矩形要改成透明的,这样APP的背景色才能透出来,还有一个是鼠标,点击后会触发切换,tab切换后又会联动下方的主视图切换,这样两者就关联起来了。
主页面是一个SwipeView ,这个是之前常用的模块了,后面这个页面是要承载设备显示用的,暂时没什么用处,就显示个页码。
四、分组管理
在tab的右边有一个按钮,是触发分组管理用的,下面是分组管理的具体代码,是一个对话框,显示内容有三个——新建分组、编辑分组和各分组的显示。其中分组显示采用一个ListView列表视图,显示组名和组内设备数量。
import QtQuick 2.7
import QtQuick.Controls 2.14
import "../base"
//分组管理弹框
Popup {
signal siqNewGroup()
signal siqManGroup()
signal siqOpenGroup(var id, var group_name)
property var rowHeight: 40
id:id_popup
visible: false
implicitWidth: parent.width*0.6
implicitHeight: rowHeight*(id_listModel.count+2)+id_popup.topPadding+id_popup.bottomPadding
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnReleaseOutside
x:parent.width*0.3
y:10
background: Rectangle
{
radius:15
}
Rectangle//分割横线
{
id:id_lineRect
width: parent.width*0.8
height: 1
color:"#F0F0F0"
anchors
{
horizontalCenter:parent.horizontalCenter
bottom:parent.bottom
bottomMargin:rowHeight*2
}
}
TextButton01//新建按钮
{
id:id_newButton
height: rowHeight
width: parent.width
anchors
{
left:parent.left
leftMargin:10
top:id_lineRect.bottom
}
textValue: "新建"
onSiqClickedLeft:
{
id_popup.close()
siqNewGroup()
}
Image//新建图标
{
width: height
height: parent.height*0.6
mipmap: true
anchors
{
verticalCenter:parent.verticalCenter
right:parent.right
rightMargin:15
}
source: "qrc:/mainImgRC/images/home/new.png"
}
}
TextButton01//分组管理
{
id:id_manButton
height: id_newButton.height
width: parent.width
anchors
{
left:parent.left
leftMargin:10
top:id_newButton.bottom
}
textValue: "管理"
onSiqClickedLeft:
{
id_popup.close()
siqManGroup()
}
Image//管理图标
{
width: height
height: parent.height*0.5
mipmap: true
anchors
{
verticalCenter:parent.verticalCenter
right:parent.right
rightMargin:17
}
source: "qrc:/mainImgRC/images/home/man.png"
}
}
ListView
{
clip: true
width: parent.width
anchors
{
top:parent.top
bottom:id_lineRect.top
}
model: ListModel{
id:id_listModel
}
delegate: Rectangle{
height: rowHeight
width: parent.width
TextButton01//分组按钮
{
id:id_groupButton
height: rowHeight
width: parent.width-20
anchors
{
left:parent.left
leftMargin:10
verticalCenter:parent.verticalCenter
}
textValue: group_name
onSiqClickedLeft: //打开某个分组
{
id_popup.close()
siqOpenGroup(id, group_name)
}
Text{
id:id_totalNumText
height: parent.height
width: 30
anchors{
right: parent.right
verticalCenter: parent.verticalCenter
}
color: "#808080"
font.family: "宋体"
font.pointSize: 18
text: total_num
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
}
}
Component.onCompleted:
{
for(var i=0; i<1; i++)
{
id_listModel.append({"id":i, "group_name":"全部", "total_num":0})
}
}
Connections
{
target: theCenterMan
onSiqAddGroup:
{
if(index===0)
{
id_listModel.clear()
id_listModel.append({"id":0, "group_name":"全部", "total_num":total_num})
}
index=id_listModel.count
id_listModel.append({"id":index, "group_name":group_name, "total_num":total_num})
}
onSiqRenameGroup:
{
for(var i=1; i<id_listModel.count; i++)
{
var group_name=id_listModel.get(i).group_name
if(group_name===old_name)
{
id_listModel.setProperty(i, "group_name", new_name)
break
}
}
}
onSiqDelGroup:
{
for(var i=1; i<id_listModel.count; i++)
{
if(group_name===id_listModel.get(i).group_name)
{
id_listModel.remove(i)
break
}
}
}
}
}
五、新建分组
点击 新建按钮后,会弹出新建对话框,可以输入组名进行新建操作。
六、编辑分组
编辑分组比较复杂,包含了删除、重命名和排序,其中重点是排序功能,具体代码如下。这里面主要难点是一个分组排序的拖拽功能,核心思想是—— 单元格内分为两部分,一个是拖拽有效区id_dragRect,一个是拖拽显示区dropRectangle,当鼠标捕捉到拖拽信号后,就把拖拽区的父组件从单元格修改为根矩形组件,然后取消布局,这样拖拽区就可以被拉动了;接着是放掉后检测当前坐标,看下在哪个单元格上就跟其做交换即可,最后配合ListView的move和moveDisplaced动画,产生交换效果就完成了,具体实现看代码。
import QtQuick 2.7
import QtQuick.Controls 2.0
import "../base"
//分组编辑界面
Rectangle {
id:id_rootRect
focus: true
MsgDialog01
{
id:id_msgDialog
}
Keys.onPressed:
{
if(event.key === Qt.Key_Back)
{
console.log("phone Key_Back!")
event.accepted = true;
theAccountMan.setHomeCurrView("home-logined")
}
}
ImageButton01//返回按钮
{
id:id_backButton
source: "qrc:/mainImgRC/images/login/back.png"
anchors
{
left:parent.left
leftMargin:20
top:parent.top
topMargin:20
}
onSiqClickedLeft:
{
theAccountMan.setHomeCurrView("home-logined")
}
}
ImageButton01{
id:id_okButton
source: "qrc:/mainImgRC/images/home/ok.png"
height: 35
anchors
{
right:parent.right
rightMargin:20
verticalCenter:id_backButton.verticalCenter
}
onSiqClickedLeft: //完成
{
var str_list=[]
for(var i=1; i<id_listModel.count; i++)
{
str_list[i]=id_listModel.get(i).group_name
}
theCenterMan.requestOrderGroup(str_list)
}
}
GroupRenameDialog//重命名
{
id:id_renameDialog
onSiqOkClicked:
{
if(text)
{
if(text==="全部")
{
id_msgDialog.funOpen("不能使用\"全部\"作为组名!")
return
}
theCenterMan.requestRenameGroup(oldName, text)
}
funClose()
}
}
GroupDelDialog //删除分组对话框
{
id:id_delGroupDialog
onSiqOkClicked:
{
theCenterMan.requestDelGroup(groupName)
funClose()
}
}
ListView{
property int dragItemIndex: -1
id:id_editListView
clip: true
width: parent.width
anchors
{
top:id_backButton.bottom
bottom:parent.bottom
}
model: ListModel{
id:id_listModel
}
move:Transition {
NumberAnimation { properties: "x,y"; duration: 200 }
}
moveDisplaced:Transition {
NumberAnimation { properties: "x,y"; duration: 200 }
}
delegate: Rectangle{
id:id_unionRect
height: 60
width: parent.width
Rectangle
{
id:id_dragRect
height: id_unionRect.height
width: id_unionRect.width
color: id_moveMouseArea.drag.active ? "transparent" : "white"
MouseArea
{
id:id_moveMouseArea
enabled: id>0
anchors.fill: parent
drag.target: id_dragRect
drag.axis: Drag.YAxis
drag.onActiveChanged: {
if (id_moveMouseArea.drag.active) {
id_editListView.dragItemIndex = index;
// console.log("index=", index)
}
id_dragRect.Drag.drop();
}
}
states: [
State {
when: id_dragRect.Drag.active
ParentChange {
target: id_dragRect
parent: id_rootRect
}
AnchorChanges {
target: id_dragRect
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
}
]
Drag.active: id_moveMouseArea.drag.active
Drag.hotSpot.x: id_dragRect.width / 2
Drag.hotSpot.y: id_dragRect.height / 2
ImageButton01{
id:id_delButton
source: "qrc:/mainImgRC/images/home/del.png"
height: 25
width: height
visible: id>0
anchors
{
verticalCenter:parent.verticalCenter
left:parent.left
leftMargin:20
}
onSiqClickedLeft: //删除
{
id_delGroupDialog.groupName=group_name
id_delGroupDialog.funOpen()
}
}
Text{ //组名文本
id:id_groupText
height: 40
anchors{
left: id_delButton.right
leftMargin: 10
right: id_totalNumText.left
verticalCenter: parent.verticalCenter
}
color: "black"
font.family: Qt.platform.os === "windows" ? "宋体" : "黑体"
font.pointSize: 18
font.bold: id_moveMouseArea.pressed
text: group_name
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight
}
Text{ //数量文本
id:id_totalNumText
height: 30
width: 30
anchors{
right: id_renameButton.left
rightMargin: 5
verticalCenter: parent.verticalCenter
}
color: "#808080"
font.family: "宋体"
font.pointSize: 18
text: total_num
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight
}
ImageButton01{
id:id_renameButton
source: "qrc:/mainImgRC/images/home/rename.png"
height: 25
width: height
visible: id>0
anchors
{
verticalCenter:parent.verticalCenter
right:parent.right
rightMargin:20
}
onSiqClickedLeft: //重命名
{
id_renameDialog.oldName=group_name
id_renameDialog.funOpen(group_name)
}
}
}
DropArea {
id: dropArea
anchors.fill: parent
onDropped:{
// console.log("onDropped")
var other_index = id_editListView.indexAt(id_moveMouseArea.mouseX + id_unionRect.x, id_moveMouseArea.mouseY + id_unionRect.y);
// console.log("index:",index,"other_index:",other_index,"listView.dragItemIndex:",id_editListView.dragItemIndex);
if(other_index>0)
{
id_listModel.move(id_editListView.dragItemIndex,other_index, 1);
}
}
Rectangle {
id: dropRectangle
anchors.fill: parent
color: "transparent"
states: [
State {
when: dropArea.containsDrag
PropertyChanges {
target: dropRectangle
color: "lightsteelblue"
opacity:0.3
}
}
]
}//end Rectangle
}//end drop
}
}
Component.onCompleted:
{
for(var i=0; i<1; i++)
{
id_listModel.append({"id":i, "group_name":"全部", "total_num":0})
}
}
Connections
{
target: theCenterMan
onSiqAddGroup:
{
if(index===0)
{
id_listModel.clear()
id_listModel.append({"id":0, "group_name":"全部", "total_num":total_num})
}
index=id_listModel.count
id_listModel.append({"id":index, "group_name":group_name, "total_num":total_num})
}
onSiqRenameGroup:
{
for(var i=1; i<id_listModel.count; i++)
{
var group_name=id_listModel.get(i).group_name
if(group_name===old_name)
{
id_listModel.setProperty(i, "group_name", new_name)
break
}
}
}
onSiqDelGroup:
{
for(var i=1; i<id_listModel.count; i++)
{
if(group_name===id_listModel.get(i).group_name)
{
id_listModel.remove(i)
break
}
}
}
}
}
删除和重命名就是相关对话框内容了,没有很复杂的内容,具体看代码。
import QtQuick 2.7
import QtQuick.Controls 2.14
Popup {
property var titleText: "标题"
property var cancelText: "取消"
property var okText: "确定"
property var cancelColor: "#303030"
property var okColor: "#303030"
signal siqCancelClicked()
signal siqOkClicked()
id:id_popup
visible: false
implicitWidth: parent.width
implicitHeight: 200
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnReleaseOutside
anchors.centerIn: Overlay.overlay
background: Rectangle
{
radius:10
}
Text {//标题文字
id:id_titleText
height: 40
width: parent.width*0.8
anchors
{
top:parent.top
topMargin:10
horizontalCenter:parent.horizontalCenter
}
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pointSize: height*0.4
font.family: "宋体"
text: titleText
color: "black"
}
BaseButton02//取消按钮
{
id:id_cancelButton
height: 50
width: parent.width*0.4
releaseColor: "#F0F0F0"
pressedColor: "#F0F0F0"
anchors
{
left:parent.left
leftMargin:20
bottom:parent.bottom
bottomMargin:20
}
buttonText: cancelText
buttonColor:cancelColor
onSiqClickedLeft:
{
siqCancelClicked()
}
}
BaseButton02//确定按钮
{
id:id_okButton
height: id_cancelButton.height
width: id_cancelButton.width
releaseColor: "#F0F0F0"
pressedColor: "#F0F0F0"
anchors
{
verticalCenter:id_cancelButton.verticalCenter
right:parent.right
rightMargin:20
}
buttonText: okText
buttonColor:okColor
onSiqClickedLeft:
{
siqOkClicked()
}
}
function funOpen()
{
id_popup.open()
}
function funClose()
{
id_popup.close()
}
}
import QtQuick 2.0
import "../base"
//删除警示对话框
BaseWarnDialog {
titleText: "确认删除分组吗?"
property var groupName: ""
okText: "删除"
okColor: "#F00000"
onSiqCancelClicked:
{
funClose()
}
}
import QtQuick 2.0
import "../base"
//重命名分组对话框
BaseEditDialog {
titleText: "重命名分组"
property var oldName: ""
onSiqCancelClicked:
{
funClose()
}
}