目录
一、界面演示
二、设备列表
三、抖动单元格
四、设备模型
五、设备编辑
本项目的交流QQ群:701889554
物联网实战--入门篇https://blog.csdn.net/ypp240124016/category_12609773.html
物联网实战--驱动篇https://blog.csdn.net/ypp240124016/category_12631333.html
一、界面演示
设备管理演示
二、设备列表
参考了几个现有的智能家居相关的APP,他们的设备管理模式基本差不多,都是这种单元格的方式,在QML里,就是GridView组件了,以下是设备列表相关的前端QML代码:
import QtQuick 2.7
import QtQuick.Controls 2.0
import "../base"Rectangle
{property var groupName: ""id:id_rootRectheight: parent.heightwidth: parent.width-20color: "transparent"anchors.horizontalCenter: parent.horizontalCenterDevDelDialog//设备删除对话框{id:id_devDelDialogonSiqOkClicked: {var dev_list=[]var ptr=0for(var i=0; i<id_cellModel.count; i++){if(id_cellModel.get(i).select_state)//选中{dev_list[ptr++]=id_cellModel.get(i).dev_sn}}theCenterMan.requestDelDevice(dev_list)funClose()}}DevRenameDialog//设备重命名对话框{id:id_devRenameDialogonSiqOkClicked: {theCenterMan.requestRenameDevice(devSn, text)funClose()}} GridView {id:id_gridViewproperty int dragItemIndex: -1property var dragActive: falseproperty int selectCnts: 0property var sortFlag: falseanchors.fill: parentclip: truecacheBuffer: 10000implicitWidth: 150implicitHeight: 150cellWidth: width*0.5cellHeight: cellWidth*0.8move:Transition {NumberAnimation { properties: "x,y"; duration: 200 }}moveDisplaced:Transition {NumberAnimation { properties: "x,y"; duration: 200 }}model: ListModel{id:id_cellModel}delegate: Rectangle{//单元格矩形id:id_cellRect
// property var selectState: falsecolor: "transparent"
// border.color: "blue"
// border.width: 1width: id_gridView.cellWidthheight: id_gridView.cellHeightRectangle //拖动矩形{id:id_dragRectradius: 12width: id_cellRect.width-15height:id_cellRect.height-15anchors.centerIn:id_cellRectcolor: id_moveMouseArea.drag.active ? "transparent" : "white"onParentChanged: {if(parent!=null){theCenterMan.showSimpleView(dev_sn, id_dragRect)} } Rectangle{ //勾选框id:id_selectRectwidth: 24height: widthradius: width/2border.width: 1border.color: "#D0D0D0"visible: id_gridView.dragActiveanchors{top:parent.toptopMargin:15right:parent.rightrightMargin:15}Image {id: id_selectImageanchors.fill: parentvisible: select_statemipmap: truesource: "qrc:/imagesRC/mainImages/check.png"}}MouseArea{id:id_moveMouseAreaanchors.fill: parent
// drag.target: id_dragRectdrag.axis: Drag.YAxis | Drag.XAxisdrag.onActiveChanged: {if (id_moveMouseArea.drag.active) {id_gridView.dragItemIndex = index;console.log("drag index=", index)}id_dragRect.Drag.drop();}onClicked: {if(id_gridView.dragActive){select_state=!select_stateif(select_state)id_gridView.selectCnts++elseid_gridView.selectCnts--
// console.log("selectCnts=", id_gridView.selectCnts)}}onPressAndHold: {id_gridView.dragActive=trueselect_state=trueid_gridView.selectCnts=1}onReleased: {
// drag.target=null
// id_gridView.dragActive=false
// id_dropAnimation.stop()}}states: [State {when: id_dragRect.Drag.activeParentChange {target: id_dragRectparent: id_rootRect}AnchorChanges {target: id_dragRectanchors.horizontalCenter: undefinedanchors.verticalCenter: undefined}}]Drag.active: id_moveMouseArea.drag.activeDrag.hotSpot.x: id_dragRect.width / 2Drag.hotSpot.y: id_dragRect.height / 2}DropArea { //拖拽id: dropAreaanchors.fill: parentonDropped:{console.log("onDropped")var other_index = id_gridView.indexAt(id_moveMouseArea.mouseX + id_cellRect.x, id_moveMouseArea.mouseY + id_cellRect.y);console.log("other_index:",other_index,"id_gridView.dragItemIndex:",id_gridView.dragItemIndex);if(other_index!==id_gridView.dragItemIndex && other_index>=0){id_cellModel.move(id_gridView.dragItemIndex,other_index, 1);id_gridView.sortFlag=true//有排序动作}}Rectangle {id: dropRectangleanchors.centerIn:parentwidth: id_dragRect.widthheight: id_dragRect.heightcolor: "transparent"radius: 12states: [State {when: dropArea.containsDragPropertyChanges {target: dropRectanglecolor: "lightsteelblue"opacity:0.3}}]}//end Rectangle}//end dropPropertyAnimation { //抖动id:id_dropAnimationtarget: id_dragRectproperties: "rotation"from:-1.5to:1.5duration: 300easing.type: Easing.InOutExpoloops: 300 //循环次数onStopped: {target["rotation"] = 0 //显示归位}}Timer{interval: Math.random()*500; running: true; repeat: trueonTriggered: {if(id_gridView.dragActive){if(id_dropAnimation.stopped){id_dropAnimation.start()id_moveMouseArea.drag.target=id_dragRect }}else{if(id_dropAnimation.started){id_dropAnimation.stop()id_moveMouseArea.drag.target=nullselect_state=falseid_gridView.selectCnts=0}}}}}//end delegate}//end GridViewRectangle //设置栏{id:id_setRectwidth: parent.parent.widthheight: 60anchors{horizontalCenter:parent.horizontalCenterbottom:id_gridView.bottom}MouseArea{anchors.fill: parent //接收鼠标事件,避免选择背后的单元格}color: "#606060"
// opacity: 0.5visible: id_gridView.dragActiveRow{height: id_setRect.heightwidth: id_setRect.width*0.9anchors{top:parent.toptopMargin:5horizontalCenter:id_setRect.horizontalCenter}spacing: (width-30*4)/3Repeater{model: ListModel{id:id_setModel}Rectangle{property var maskFlag: index===0 && id_gridView.selectCnts!==1height: 30width: heightradius: width/2color: maskFlag ? "#808080" : "white"ImageButton01 {anchors.centerIn: parentwidth: parent.width*0.7height: widthmipmap: true source: img_srconSiqClickedLeft: {
// console.log("clicked index=", index)switch(index){case 0: //修改名称if(id_gridView.selectCnts===1){for(var i=0; i<id_cellModel.count; i++){if(id_cellModel.get(i).select_state)//选中{id_devRenameDialog.devSn=id_cellModel.get(i).dev_snid_devRenameDialog.oldName=theCenterMan.takeWorkDeviceName(id_devRenameDialog.devSn)//旧名称id_devRenameDialog.funOpen(id_devRenameDialog.oldName)break}} }breakcase 1: //移动设备id_devMoveDialog.open()var dev_list=[]var ptr=0for(i=0; i<id_cellModel.count; i++){if(id_cellModel.get(i).select_state)//选中{dev_list[ptr++]=id_cellModel.get(i).dev_sn}}id_devMoveDialog.srcGroupName=groupNameid_devMoveDialog.moveDevList=dev_listbreakcase 2: //删除设备id_devDelDialog.funOpen()break case 3: //完成id_gridView.dragActive=false;if(id_gridView.sortFlag==true)//有排序动作{id_gridView.sortFlag=falsedev_list=[]for(i=0; i<id_cellModel.count; i++){dev_list[i]=id_cellModel.get(i).dev_sn}theCenterMan.requestSortDevice(groupName, dev_list)//排序}break}}}Text{height: 25anchors{horizontalCenter:parent.horizontalCentertop:parent.bottomtopMargin:3}text: namefont.pointSize: 10font.family: "宋体"color: maskFlag ? "#808080" : "white"}}}}}Connections{target: theCenterManonSiqAddDevice2Group:{if(group_name===groupName){id_cellModel.append({"dev_sn":dev_sn, "select_state":false})}}onSiqDelDevice:{for(var i=0; i<id_cellModel.count; i++){if(dev_sn===id_cellModel.get(i).dev_sn){id_cellModel.remove(i)break}}if(flag>0) //删除完成{var dev_list=[]var ptr=0for(i=0; i<id_cellModel.count; i++){dev_list[ptr++]=id_cellModel.get(i).dev_sn}theCenterMan.requestSortDevice(groupName, dev_list)//重新排序}}onSiqClearDevice:{if(group_name===groupName){id_cellModel.clear()}}}Component.onCompleted: {var img_src="qrc:/imagesRC/mainImages/home/rename.png"var name="修改名称"id_setModel.append({"img_src":img_src, "name":name})img_src="qrc:/imagesRC/mainImages/home/move.png"name="移动设备"id_setModel.append({"img_src":img_src, "name":name}) img_src="qrc:/imagesRC/mainImages/home/del02.png"name="删除设备"id_setModel.append({"img_src":img_src, "name":name}) img_src="qrc:/imagesRC/mainImages/home/finish.png"name="完成"id_setModel.append({"img_src":img_src, "name":name}) }}
在GridView里的重点其实就是拖拽排序功能了,跟之前的分组排序类似的,只不过网格拖拽复杂点,首先方向是X和Y两个方向;然后是抖动效果,在长按某个设备单元格后整体就会抖动起来,让用户直观地感受到是在编辑状态;最后是选中效果,用户选中后就会增加一个打勾按钮,本质就是图片显示条件设置了。
三、抖动单元格
抖动使用的是动画组件,其中需要改变的对象是拖拽矩形,改变的属性是角度,范围从-1.5~1.5角度,如果停止后角度重置为0。在这里我们加个定时器用来管理所有单元格的抖动状态,在有设备被长按激活拖拽后,网格里所有的设备都要抖动起来,在定时器里启动;如果退出编辑去激活后,同样是在定时器内检测停止。
PropertyAnimation { //抖动id:id_dropAnimationtarget: id_dragRectproperties: "rotation"from:-1.5to:1.5duration: 300easing.type: Easing.InOutExpoloops: 300 //循环次数onStopped: {target["rotation"] = 0 //显示归位}}Timer{interval: Math.random()*500; running: true; repeat: trueonTriggered: {if(id_gridView.dragActive){if(id_dropAnimation.stopped){id_dropAnimation.start()id_moveMouseArea.drag.target=id_dragRect }}else{if(id_dropAnimation.started){id_dropAnimation.stop()id_moveMouseArea.drag.target=nullselect_state=falseid_gridView.selectCnts=0}}}}
四、设备模型
设备模型是我们后续开发新硬件产品所需要对应增加的内容,在其它平台上,很多称之为物模型,一般用json语句来描述设备的属性、状态和功能,这种模式的特点是通用性比较强,缺点是性能损耗大,深度定制使用体验不佳,所以只能定义一些较为简单的设备模型,复杂的调试起来很麻烦。那么,我们这边的思想还是以代码驱动为核心,物模型后端逻辑直接用C/C++实现,前端界面用QML实现,配套使用,下面举例说明。
如上图所示,modelCpp存放的是物模型后端代码,modelQml存放的是物模型前端代码,由于各个物模型有一定相似性,所以都会定义一个基本的模型,具体设备模型再继承于这个基础模型,在这里我们以后面要实现的净化器为例,定义型号为AP01,那么他的ModelAp01,继承于BaseModel,具体的后面完善净化器项目的时候再说明。
同样的,前端代码也是这种模式,SimpleAp01.qml继承于BaseSimpleView,带Simple说明是简易模型,就是每个单元格内显示的内容,对于每个物模型有两个界面,一个是这里所说的简易界面,另一个是详情界面,就是之前净化器项目那个可以具体控制操作的界面。
对于模型的显示也是一个需要技巧的地方,首先要知道,每个界面都需要有一个父组件/窗口,这样才能显示,对于设备模型前端来讲,它的父窗口其实就是那个可以被拖拽的单元格了,所以我们要显示模型的时候,就是将这个单元格地址传到模型里去,然后模型内部自己主动显示在单元格上,下面通过代码了解这一流程。
首先在网格单元格内,当单元格创建完成后,会触发onParentChanged信号,在这里就可以根据设备sn将单元格矩形最为父窗口,把指针传入模型后端。
然后我们来看看控制中心的showSimpleView函数的内容,如下所示,就是根据dev_sn找到这个模型实例对象,然后调用模型的showSimple进行显示,注意,这里继续将父窗口指针继续传递。
接下来是重头戏,如下图所示,是不是感觉似曾相识,跟主程序显示是一样的,每个模型内部都有一个QML显示引擎,通过这里的配置,就可以实现模型内容的前后端交互了,在这里有两个接口,theModelAp01和theCenterMan,其中通过theCenterMan可以调用CenterMan类内的相关功能。函数末尾就是加载SimpleAp01这个模型文件。
到此为止,物模型并不能显示,因为上图中的父窗口指针只是保存在C++后端了,并没有跟前端的QML有什么关联,这就引出了最后一步,就是在具体的QML模型文件内调用takeModelParent(),将刚才保存在后端的父窗口指针赋值到当前模型的parent属性,这样就形成了闭环,完成了模型显示的功能。
五、设备编辑
当用户长按某个单元格后,除了单元格会抖动起来以外,底部还会出现一个设置栏,目前定义的功能是修改名称、移动设备和删除设备,这里比较复杂的还是上一篇所写的后台管理,特别是移动设备功能;那么对于前端代码主要做个展示,具体内容自己阅读理解应该问题不大。
Rectangle //设置栏{id:id_setRectwidth: parent.parent.widthheight: 60anchors{horizontalCenter:parent.horizontalCenterbottom:id_gridView.bottom}MouseArea{anchors.fill: parent //接收鼠标事件,避免选择背后的单元格}color: "#606060"
// opacity: 0.5visible: id_gridView.dragActiveRow{height: id_setRect.heightwidth: id_setRect.width*0.9anchors{top:parent.toptopMargin:5horizontalCenter:id_setRect.horizontalCenter}spacing: (width-30*4)/3Repeater{model: ListModel{id:id_setModel}Rectangle{property var maskFlag: index===0 && id_gridView.selectCnts!==1height: 30width: heightradius: width/2color: maskFlag ? "#808080" : "white"ImageButton01 {anchors.centerIn: parentwidth: parent.width*0.7height: widthmipmap: true source: img_srconSiqClickedLeft: {
// console.log("clicked index=", index)switch(index){case 0: //修改名称if(id_gridView.selectCnts===1){for(var i=0; i<id_cellModel.count; i++){if(id_cellModel.get(i).select_state)//选中{id_devRenameDialog.devSn=id_cellModel.get(i).dev_snid_devRenameDialog.oldName=theCenterMan.takeWorkDeviceName(id_devRenameDialog.devSn)//旧名称id_devRenameDialog.funOpen(id_devRenameDialog.oldName)break}} }breakcase 1: //移动设备id_devMoveDialog.open()var dev_list=[]var ptr=0for(i=0; i<id_cellModel.count; i++){if(id_cellModel.get(i).select_state)//选中{dev_list[ptr++]=id_cellModel.get(i).dev_sn}}id_devMoveDialog.srcGroupName=groupNameid_devMoveDialog.moveDevList=dev_listbreakcase 2: //删除设备id_devDelDialog.funOpen()break case 3: //完成id_gridView.dragActive=false;if(id_gridView.sortFlag==true)//有排序动作{id_gridView.sortFlag=falsedev_list=[]for(i=0; i<id_cellModel.count; i++){dev_list[i]=id_cellModel.get(i).dev_sn}theCenterMan.requestSortDevice(groupName, dev_list)//排序}break}}}Text{height: 25anchors{horizontalCenter:parent.horizontalCentertop:parent.bottomtopMargin:3}text: namefont.pointSize: 10font.family: "宋体"color: maskFlag ? "#808080" : "white"}}}}}