物联网实战--平台篇之(十二)设备管理前端

目录

一、界面演示

二、设备列表

三、抖动单元格

四、设备模型

五、设备编辑


本项目的交流QQ群:701889554

物联网实战--入门篇https://blog.csdn.net/ypp240124016/category_12609773.html

物联网实战--驱动篇https://blog.csdn.net/ypp240124016/category_12631333.html

物联网实战--平台篇https://blog.csdn.net/ypp240124016/category_12653350.html

一、界面演示

设备管理演示

二、设备列表

        参考了几个现有的智能家居相关的APP,他们的设备管理模式基本差不多,都是这种单元格的方式,在QML里,就是GridView组件了,以下是设备列表相关的前端QML代码:

import QtQuick 2.7
import QtQuick.Controls 2.0
import "../base"

Rectangle
{
    property var groupName: ""

    id:id_rootRect
    height: parent.height
    width: parent.width-20
    color: "transparent"
    anchors.horizontalCenter: parent.horizontalCenter
    DevDelDialog//设备删除对话框
    {
        id:id_devDelDialog
        onSiqOkClicked: 
        {
            var dev_list=[]
            var ptr=0
            for(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_devRenameDialog
        
        onSiqOkClicked: 
        {
            theCenterMan.requestRenameDevice(devSn, text)
            funClose()
        }
        
    } 
    
    GridView {
        id:id_gridView
        property int dragItemIndex: -1
        property var dragActive: false
        property int selectCnts: 0
        property var sortFlag: false
        anchors.fill: parent
        clip: true
        cacheBuffer: 10000
        implicitWidth: 150
        implicitHeight: 150
        cellWidth: width*0.5
        cellHeight: cellWidth*0.8
        
        move: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: false
            color: "transparent"
//            border.color: "blue"
//            border.width: 1
            width: id_gridView.cellWidth
            height: id_gridView.cellHeight
            
            Rectangle //拖动矩形
            {
                id:id_dragRect
                radius: 12
                width: id_cellRect.width-15
                height:id_cellRect.height-15
                anchors.centerIn:id_cellRect
                color: id_moveMouseArea.drag.active ? "transparent" : "white"
                
                onParentChanged: 
                {
                    if(parent!=null)
                    {
                        theCenterMan.showSimpleView(dev_sn, id_dragRect)
                    }         
                } 
                
                Rectangle{  //勾选框
                    id:id_selectRect
                    width: 24
                    height: width
                    radius: width/2
                    border.width: 1
                    border.color: "#D0D0D0"
                    visible: id_gridView.dragActive
                    anchors
                    {
                        top:parent.top
                        topMargin:15
                        right:parent.right
                        rightMargin:15
                    }
                    Image {
                        id: id_selectImage
                        anchors.fill: parent
                        visible: select_state
                        mipmap: true
                        source: "qrc:/imagesRC/mainImages/check.png"
                    }
                }
                
                MouseArea
                {
                    id:id_moveMouseArea
                    anchors.fill: parent
//                    drag.target: id_dragRect
                    drag.axis: Drag.YAxis | Drag.XAxis
                    drag.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_state
                            if(select_state)
                                id_gridView.selectCnts++
                            else
                                id_gridView.selectCnts--
//                            console.log("selectCnts=", id_gridView.selectCnts)
                        }
                    }
                    onPressAndHold: 
                    {
                        id_gridView.dragActive=true
                        select_state=true
                        id_gridView.selectCnts=1
                    }
                    onReleased: 
                    {
//                        drag.target=null
//                        id_gridView.dragActive=false
//                        id_dropAnimation.stop()
                    }
                }
                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
                
            }
            DropArea {  //拖拽
            id: dropArea
            anchors.fill: parent
            onDropped:{
                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: dropRectangle
    
                    anchors.centerIn:parent
                    width: id_dragRect.width
                    height: id_dragRect.height
                    color: "transparent"
                    radius: 12
                    states: [
                        State {
                            when: dropArea.containsDrag
                            PropertyChanges {
                                 target: dropRectangle
                                 color: "lightsteelblue"
                                 opacity:0.3
                            }
                        }
                    ]
                }//end Rectangle
    
            }//end drop
            
            PropertyAnimation { //抖动
                id:id_dropAnimation
                target: id_dragRect
                properties: "rotation"
                from:-1.5
                to:1.5
                duration: 300
                easing.type: Easing.InOutExpo
                loops: 300 //循环次数
                onStopped: 
                {
                    target["rotation"] = 0 //显示归位
                }
            }
            Timer{
                interval: Math.random()*500; running: true; repeat: true
                onTriggered: 
                {
                    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=null
                            select_state=false
                            id_gridView.selectCnts=0
                        }
                    }
                }
            }
    
        }//end delegate
        
    }//end GridView
    
    Rectangle  //设置栏
    {
        id:id_setRect
        
        width: parent.parent.width
        height: 60
        anchors
        {
            horizontalCenter:parent.horizontalCenter
            bottom:id_gridView.bottom
        }
        MouseArea{
            anchors.fill: parent //接收鼠标事件,避免选择背后的单元格
        }
        color: "#606060"
//        opacity: 0.5
        visible: id_gridView.dragActive
        
        Row
        {
            height: id_setRect.height
            width: id_setRect.width*0.9
            anchors
            {
                top:parent.top
                topMargin:5
                horizontalCenter:id_setRect.horizontalCenter
            }
            spacing: (width-30*4)/3
            Repeater
            {
                model: ListModel{
                    id:id_setModel
                }
                Rectangle{
                    property var maskFlag: index===0 && id_gridView.selectCnts!==1
                    height: 30
                    width: height
                    radius: width/2
                    color:  maskFlag ? "#808080" : "white"
                    ImageButton01 {
                        anchors.centerIn: parent
                        width: parent.width*0.7
                        height: width
                        mipmap: true 
                        source: img_src
                        onSiqClickedLeft: 
                        {
//                            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_sn
                                                id_devRenameDialog.oldName=theCenterMan.takeWorkDeviceName(id_devRenameDialog.devSn)//旧名称
                                                id_devRenameDialog.funOpen(id_devRenameDialog.oldName)
                                                break
                                            }
                                        }         
                                    }
                                    break
                                case 1: //移动设备
                                    id_devMoveDialog.open()
                                    var dev_list=[]
                                    var ptr=0
                                    for(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=groupName
                                    id_devMoveDialog.moveDevList=dev_list
                                    break
                                case 2: //删除设备
                                    id_devDelDialog.funOpen()
                                    break                                    
                                case 3:  //完成
                                    id_gridView.dragActive=false;
                                    if(id_gridView.sortFlag==true)//有排序动作
                                    {
                                        id_gridView.sortFlag=false
                                        dev_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: 25
                        anchors
                        {
                            horizontalCenter:parent.horizontalCenter
                            top:parent.bottom
                            topMargin:3
                        }
                        text: name
                        font.pointSize: 10
                        font.family: "宋体"
                        color:  maskFlag ? "#808080" : "white"
                    }
                }

            }
        }
        

        
    }
    
    
    Connections
    {
        target: theCenterMan
        onSiqAddDevice2Group:
        {
            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=0
                for(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_dropAnimation
                target: id_dragRect
                properties: "rotation"
                from:-1.5
                to:1.5
                duration: 300
                easing.type: Easing.InOutExpo
                loops: 300 //循环次数
                onStopped: 
                {
                    target["rotation"] = 0 //显示归位
                }
            }
            Timer{
                interval: Math.random()*500; running: true; repeat: true
                onTriggered: 
                {
                    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=null
                            select_state=false
                            id_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_setRect
        
        width: parent.parent.width
        height: 60
        anchors
        {
            horizontalCenter:parent.horizontalCenter
            bottom:id_gridView.bottom
        }
        MouseArea{
            anchors.fill: parent //接收鼠标事件,避免选择背后的单元格
        }
        color: "#606060"
//        opacity: 0.5
        visible: id_gridView.dragActive
        
        Row
        {
            height: id_setRect.height
            width: id_setRect.width*0.9
            anchors
            {
                top:parent.top
                topMargin:5
                horizontalCenter:id_setRect.horizontalCenter
            }
            spacing: (width-30*4)/3
            Repeater
            {
                model: ListModel{
                    id:id_setModel
                }
                Rectangle{
                    property var maskFlag: index===0 && id_gridView.selectCnts!==1
                    height: 30
                    width: height
                    radius: width/2
                    color:  maskFlag ? "#808080" : "white"
                    ImageButton01 {
                        anchors.centerIn: parent
                        width: parent.width*0.7
                        height: width
                        mipmap: true 
                        source: img_src
                        onSiqClickedLeft: 
                        {
//                            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_sn
                                                id_devRenameDialog.oldName=theCenterMan.takeWorkDeviceName(id_devRenameDialog.devSn)//旧名称
                                                id_devRenameDialog.funOpen(id_devRenameDialog.oldName)
                                                break
                                            }
                                        }         
                                    }
                                    break
                                case 1: //移动设备
                                    id_devMoveDialog.open()
                                    var dev_list=[]
                                    var ptr=0
                                    for(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=groupName
                                    id_devMoveDialog.moveDevList=dev_list
                                    break
                                case 2: //删除设备
                                    id_devDelDialog.funOpen()
                                    break                                    
                                case 3:  //完成
                                    id_gridView.dragActive=false;
                                    if(id_gridView.sortFlag==true)//有排序动作
                                    {
                                        id_gridView.sortFlag=false
                                        dev_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: 25
                        anchors
                        {
                            horizontalCenter:parent.horizontalCenter
                            top:parent.bottom
                            topMargin:3
                        }
                        text: name
                        font.pointSize: 10
                        font.family: "宋体"
                        color:  maskFlag ? "#808080" : "white"
                    }
                }

            }
        }
        

        
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瓦力农场

本文有偿阅读,自愿打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值