物联网实战--入门篇之(十一)安卓QT--前端开发

目录

一、设计思路

二、QML文件结构

三、顶部框

四、中心圆圈

五、泡泡

六、开关栏

七、调速栏

八、安卓编译


一、设计思路

        还是再贴一下米家APP的截图,再根据我们之前第九篇的分析,大概可以得出设计思路了。首先一个根页面当底版,然后在跟页面上进行布局,布局内容分为三块,数据显示界面、开关界面和调速界面,从上到下依次排列即可。自动、睡眠模式那一栏省略,对我们当前没太大用处。

        同时,这三个模块界面要考虑开机和关机两个不同的状态,关机时候,数据界面和开关界面变黑,调速界面变灰色,并且滑块禁止滑动;开机时候,数据界面根据空气等级变化颜色,同时有很多小气泡随机生成往中心运动,开关界面按钮颜色变化,调速界面也变成彩色,并且滑块可以滑动了。

        界面整体设计方案基本这样了,下面通过代码带大家参与设计过程,同时说明如何实现数据交互、显示等问题。

   

二、QML文件结构

       题外话, QML文件语法跟js很相似,会C语言也能轻松上手,本质上就是每个QML对象的背后都有一个QT帮我们封装好的C++类,QT把这个类的属性接口暴露给我们设置,这样既降低了编程难度,又达成了前后端分离的目的。不过目前似乎很多老QT程序员不太接受QML,就个人而言,是个好东西,值得入手。

        回到我们的项目,下图中的0、1、2就是靠近底层的程度,0对应的main.qml就是我们C++中QML引擎加载的文件,它是前端的根文件,内容也很简单,就是一个Window窗口,并设置一些长宽的等属性,里面就包含了一个我们自定义的MainView页面,因为Window对象没有布局相关的属性,但又是必须的,所以才会再自定义一个MainView,便于自身扩展、布局。

        打开MainView其实也很简单,就一个Item,内部也就包含了一个设备详情页面AirDevPage对象,这里的Item是一个很基础的对象,很多对象都是继承于它的,比如矩形框就是在它的基础之上添加了边框颜色、宽度、内部背景颜色等属性。只要是图形对象,一般都要用anchros这个属性,称为锚布局,很灵活、很好用。

        AirDevPage就是我们这个项目的核心界面了,那么很多人会觉得MainView这个文件是不是多余的,直接把AirDevPage放在main.qml里不就好了?这对当前的项目效果是一样的,但是既然是项目教程,就要想的远一点,我们当前只有一个净化器相关的界面是可以这样,但是哪天又想在这个项目里添加其它设备呢,比如温湿度、烟感等,那么我们就可以直接再创建跟AirDevPage同级的页面,比如ThDevPage,SmokeDevPage,再用切换画面把他们整合在一起就行了,整体的文件结构都不需要改变,增加就行。

        接着打开AirDevPage.qml,内容就比较多了,也是主角净化器相关的界面,下面按结构讲解。

三、顶部框

        这部分在米家APP上是没有的,我们为了结合自己的项目,把头顶区域拿来做状态栏,主要有两个功能,一个是设备的在线状态,一个是序列号。

        在线状态的C++代码如下图所示,在定时函数内,每隔1秒检测,如果保活时间超过5秒认为设备断线了,当然这个时间自己定义,不要太久就行了。

        对于前端,首先定义一个状态变量,在图片对象的属性里,显示图片根据这个状态值自动切换,而状态值的改变发生在槽函数里。

        序列号也是一样的道理,后端组织好后直接发送给前端显示。

        顶部矩形框作为一个小模块,整体的QML代码如下。这里顺便再提一下,图片的路径"qrc:/imagesRC/images/led_green.png",前缀有qrc:字样,说明是从资源文件里获取的,如果要显示本地图片也行,例如"images/led_green.png",这样你就需要在可执行程序的目录下添加images文件夹,并放入led_green.png图片才行,但是这样明显比较麻烦的,也不利于统一管理,所以还是采用资源文件比较好。还有个细节是,每添加或创建一个qml文件,你都需要右键工程,点击执行qmake,这样才能正确编译。

    Rectangle//顶部矩形框
    {
        color:"transparent"
        id:id_headRect
        width: parent.width
        height: 60
        anchors
        {
            top:parent.top
        }
        Label{ //显示净化器设备ID号
            id:id_devSnLabel
            height: 30
            width: parent.width
            anchors.centerIn: parent
            
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
            font.pointSize: height*0.5
            font.family:"黑体"
            color: "black"
            text:"ID: 11223344"
        }
        Image {
            id: id_stateImage
            width: 30
            height: width
            anchors
            {
                verticalCenter:parent.verticalCenter
                left:parent.left
                leftMargin:width*2
            }
            source: onlineState>0 ? "qrc:/imagesRC/images/led_green.png" : "qrc:/imagesRC/images/led_gray.png"
        }
    }

四、中心圆圈

        这一部分是比较麻烦的,以下是大概的布局,对着米家APP截图模仿就行了,这里有定义一个switchState开关状态变量,为了方便页面切换用的。

        接下来先具体看下关机状态下的圆圈代码,可以发现,它其实是个矩形,把圆角半径设置为宽度的一般后就变成圆形了。对于颜色,可以上图标网站上选好后复制下来就行了,具体网址iconfont-阿里巴巴矢量图标库,简单的图标也可以在上面找的。

        彩色圆圈跟黑色圆圈是重叠的,看他们的visible属性会发现,互为相反的,true就是显示,false就是隐藏。

        至于彩色圆圈的内容稍微麻烦点,需要渐变色,起始颜色根据污染等级确定,边缘颜色设置为透明transparent,最后根据位置和半径画圆填充就行了。这里有个槽函数onAlarmLevelChanged,当污染等级改变的时候要请求重绘,这样颜色才能更新。

彩色圆圈具体的代码如下。

import QtQuick.Controls 2.5
import QtGraphicalEffects 1.0
import QtQuick.Particles 2.12
import QtQuick 2.14


Rectangle
{
    property var alarmLevel: 0
    
    id:id_onCircular
    Canvas {
        id:id_canvas
        anchors.fill: parent
        onPaint: {
            var ctx = getContext("2d");
            ctx.reset();

            var centreX = width / 2;
            var centreY = height / 2;
            var radius=width/2;
            
            // 画径向渐变色
            var gradient=ctx.createRadialGradient(centreX,centreY,0,centreX,centreY,radius);
            gradient.addColorStop(0, funTakeCenterColor());
            gradient.addColorStop(1, 'transparent');
            ctx.fillStyle=gradient;
            ctx.arc(centreX, centreY, width / 2, 0, Math.PI * 2, false);
            ctx.fill();
        }
    }

    function funTakeCenterColor()
    {
        var color="#0ee7cb"
        switch(alarmLevel)
        {
            case 0:color="#0ee7cb";break;
            case 1:color="#dd9220";break;
            case 2:color="#f65345";break;
               
        }
        return color
    }
    onAlarmLevelChanged: 
    {
        id_canvas.requestPaint()
    }
    
}

        至于其他的温湿度、PM2.5等文本按部就班就行了,没什么特殊的,主要就看下anchors锚布局的用法以及数值是如何更新的就行了。

五、泡泡

        这部分本来是属于中心圆圈的内容的,但是比较特殊就单独出来了,具体代码如下,关键注释都有。首先Repeater的作用就是重复某个对象,对象的数量和一些参数可以通过model属性确定和传递。在这里,重复对象就是小圆圈了,它们显示与否跟开关状态关联,颜色跟污染等级关联;小圆圈的内部设置了一个定时器,这样才能实时改变位置模拟出运动的效果。实际运行时,首先需要随机配置出发点和大小,然后朝着中心运动,到达一定范围内就消失,重新出发。

        出发的时候,我们获取了三个随机数(0~1),分别用于设置气泡直径、位置x和位置y;而后根据与中心点的距离和位置判断该如何运动,最后一步步靠近中心、消失,周而复始。

        气泡的数量通过初始化ListModel获得,我这里是30个。

    Repeater  //泡泡
    {
        model: ListModel{
            id:id_listModel
        }

        Rectangle{
            id:id_bubbles
            visible: switchState
            width: 8
            height: width
            radius: width/2
            color: id_colorView.funTakeCenterColor()
            x:0
            y:0
            Timer{
                interval: 50; running: switchState; repeat: true
                onTriggered: 
                {
                    var c_x=id_rootRect.width/2
                    var c_y=id_rootRect.height/2//矩形框的中心点
                    var det_x=c_x-id_bubbles.x
                    var det_y=c_y-id_bubbles.y
                    var det_val=Math.sqrt(det_x*det_x+det_y*det_y)//与中心点的距离
                    if(x==0 || y==0 || det_val<20)//消失,重新出发
                    {
                        var rand1=Math.random()
                        var rand2=Math.random()
                        var rand3=Math.random()//获取3个随机数
                        id_bubbles.width=rand3*6+4  //泡泡直径
                        id_bubbles.x=rand1*id_rootRect.width
                        id_bubbles.y=rand2*id_rootRect.height//出发位置
                    }
                    else
                    {
                        //向中心移动
                        if(id_bubbles.x<c_x-5)id_bubbles.x+=1
                        else if(id_bubbles.x>c_x+5)id_bubbles.x-=1
                        
                        if(id_bubbles.y<c_y-5)id_bubbles.y+=1
                        else if(id_bubbles.y>c_y+5)id_bubbles.y-=1
                    }
                }
            }
        }
    }

六、开关栏

        这部分比较简单了,就是一个图片按钮,根据开关状态改变颜色,具体代码如下。代码里的ImageButton01是自定义的图片按钮,是图片跟鼠标的结合,这样在按下的时候会放大一下图片,有个动态感。可以看到,按下之后在槽函数里会直接调用后端函数theMainInterface.setSwitchState(!switchState)  进行开关设置。

import QtQuick 2.7

Rectangle {
    
    property var switchState: 0
    radius: 10
    width: parent.width
    height: 90
    
    ImageButton01
    {
        id: id_switchImage
        imgSrc: "qrc:/imagesRC/images/off.png"
        height: parent.height*0.5
        width: height
        anchors
        {
            verticalCenter:parent.verticalCenter
            left:parent.left
            leftMargin:height*0.2
        }
        onSiqClickedLeft: 
        {
//            switchState=!switchState

            theMainInterface.setSwitchState(!switchState)
        }
    }
    
    Connections
    {
        target: theMainInterface
        onSiqUpdateSwitchState:
        {
            switchState=state
            if(switchState>0)
            {
                id_switchImage.imgSrc="qrc:/imagesRC/images/on.png"
            }
            else
            {
                id_switchImage.imgSrc="qrc:/imagesRC/images/off.png"
            }
        }
    }
    
}

七、调速栏

        这里的难点是在于如何自定义滑块的样式,因为默认的样式跟米家APP不一样,那就要自己定义了,自定义的过程主要是位置的细节要自己好好琢磨计算下,不然会出现奇怪的效果。这里就只贴这部分的代码了。

    Slider {
        id:id_speedSlider
        width: parent.width*0.9
        height: parent.height*0.45
        anchors
        {
            horizontalCenter:parent.horizontalCenter
            bottom:parent.bottom
            bottomMargin:10
        }
        enabled: switchState
        from: 0  // 滑动条的起始值
       value: 50 // 滑动条的当前值
       to: 100   // 滑动条的结束值
       stepSize: 1 // 滑动条的步长
       background: Rectangle{  //背景
           id:id_backRect
            height: id_speedSlider.height
            width: id_speedSlider.width//+height
            
            radius: height/2
            color: "#F0F0F0"
            anchors.centerIn: id_speedSlider
            Rectangle {//左边填充
                height: id_backRect.height
                width: id_speedSlider.visualPosition * (parent.width-parent.height)+height
                anchors
                {
                    left:parent.left
                    verticalCenter:parent.verticalCenter
                }
                color: funTakeSliderColor()
                radius: height/2
            }
       }
       
       handle: Rectangle//滑块
       {
            height:id_backRect.height*0.85
            width:height
            radius:height/2
            x:id_speedSlider.height/2+id_speedSlider.visualPosition * (parent.width-parent.height)-height/2
            anchors.verticalCenter:parent.verticalCenter
       }
       
       onMoved: 
       {
            theMainInterface.setFanSpeed(id_speedSlider.visualPosition)
       }
       
    }

        自定义内容主要就是背景background和滑块handle两个属性,这里先说背景,就是APP上灰色部分,实际上这个背景直径铺满了整个Slider对象,只不过又加上了左边动态填充的部分,填充部分的宽度计算是个重点,其实就是滑动距离再加上圆弧直径就是填充部分的长度了。这里填充颜色我们也是根据污染等级进行关联的,米家APP上好像没有。

        剩下就是滑块白色圆圈的位置计算了,首先滑到0的时候,也就是最左边,它应该跟填充部分同心,这时候绿色是一个圆环,因为白色滑块的直径比较小,根据这点,我们可以先确定起始偏移是背景高度的一半,即id_speedSlider.height/2;然后如果滑到1的位置,也就是最右端,白圈应该与右侧圆弧同心,可以计算两端圆心的距离是id_speedSlider.width-id_speedSlider.height,这时候是白圈矩形左顶点的x坐标,我们需要的是白圈中心,所以还要减去自身的半径height/2,所以整体的白圈的x坐标如下图所示。

        对于后端调用部分,只要滑动滑块就会产生moved信号,这时候可以直接调用后端速度设置函数,id_speedSlider.visualPosition是0~1的范围,代表滑动百分比,其余由后端程序处理即可。

        最后是适用面积的问题,在滑动的时候会变化,具体小米是怎么计算的没有深入研究,自己大概自定义了一个。

八、安卓编译

        安卓编译的前提是环境要搭好,这在第二篇的时候有说明了,安装好后选择安卓套件,再点击锤子或者三角形都行,点击三角形时候要打开安卓手机的USB调试再接到电脑,这样APP编译好后就可以直接安装在手机里了。

     

          到此,整个前端乃至整个项目基本讲解完成了,如有疑问或者错误,请留言,欢迎讨论。

  本项目的交流QQ群:701889554

   写于2024-4-3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/523308.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot属性配置的多种方式

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉🍎个人主页:Leo的博客💞当前专栏: 循序渐进学SpringBoot ✨特色专栏: MySQL学习 🥭本文内容:SpringBoot属性配置的多种方式 📚个人知识库: Leo知识库,欢迎大家访问 目录 …

突破编程_前端_SVG(概述)

1 什么是 SVG SVG&#xff0c;全称可缩放矢量图形&#xff08;Scalable Vector Graphics&#xff09;&#xff0c;是一种基于 XML&#xff08;可扩展标记语言&#xff09;的矢量图像格式。这种图像格式的主要特点是它描述的是矢量图形&#xff0c;而不是基于像素的位图图像。因…

接口和抽象类的综合案例

题目要求&#xff1a; 代码框架&#xff1a; 代码实现&#xff1a; person类&#xff1a; package www.jsu.com;public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name name;this.age age;}public …

【第十二篇】使用BurpSuite实现CSRF(实战案例)

CSRF存在前提:简单的身份验证只能保证请求是发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的 业务场景:新增、删除、收藏、编辑、保存使用Burp发现CSRF漏洞的过程如下。 1、如图,存在修改邮箱的功能点如下: 2、修改邮箱的流量包,此时邮箱已被修改: 思路:是…

Labview如何0基础自学快速入门?(纯干货帖)

大家好&#xff0c;首先声明&#xff1a;本文纯干货&#xff0c;单纯为了帮助大家快速入门。有用的话大家点赞评论加关注即可。谢谢大家 题主是从一个毫无编程基础的Labview小白到现在能独立承担软件开发项目的工程师&#xff0c;作为瑞文的老玩家&#xff0c;题主觉得&#xf…

Git场景运用

git 脚本在开发中应用场景-CSDN博客 Git基础 Git基本运作流程 ​​​​​​​ (1) workspace->index->Repository ​ 本地写代码在workspace&#xff0c;add暂存到index&#xff0c;commit提交到本地Repository。多项目成员&#xff0c;每员对应本地仓库&#xff0c;各自…

uniapp-设置UrlSchemes从外部浏览器H5打开app

需求&#xff1a;外部浏览器H5页面&#xff0c;跳转到uniapp开发的原生app内部。 1、uniapp内部的配置&#xff1a; &#xff08;1&#xff09;打开manifest->App常用其他设置&#xff0c;如下&#xff0c;按照提示输入您要设置的urlSchemes&#xff1a; &#xff08;2&am…

pom.xml文件中的标签认识

周末不卷&#xff0c;研究下pom.xml里的内容。 一般一个pom.xml文件外面一个project包着以下的标签&#xff1a; groupId artifactId repositories properties dependencies build plugins 下面分别来说说这几个标签的含义&#xff1a; 1、groupId&#xff1a;表示项目组的id…

MSOLSpray:一款针对微软在线账号(AzureO365)的密码喷射与安全测试工具

关于MSOLSpray MSOLSpray是一款针对微软在线账号&#xff08;Azure/O365&#xff09;的密码喷射与安全测试工具&#xff0c;在该工具的帮助下&#xff0c;广大研究人员可以直接对目标账户执行安全检测。支持检测的内容包括目标账号凭证是否有效、账号是否启用了MFA、租户账号是…

vivado 系统内逻辑设计调试流程

系统内逻辑设计调试流程 Vivado 工具提供了诸多功能 &#xff0c; 用于在真实硬件器件中调试系统内设计。系统内调试流程包含 3 个不同阶段 &#xff1a; 1. 探测阶段 &#xff1a; 确定设计中要探测的信号和探测的方法。 2. 实现阶段 &#xff1a; 完成设计实现 &…

Java学习笔记24(面向对象编程(高级))

1.面向对象编程(高级) 1.1 类变量和类方法 1.类变量 ​ *类变量也叫静态变量/静态属性&#xff0c;是该类的所有对象共享的变量&#xff0c;任何一个该类的对象去访问它时&#xff0c;取到的都是相同的值&#xff0c;同样任何一个该类的对象去修改它时&#xff0c;修改的也是…

31.2k star, 免费开源的白板绘图工具 tldraw

31.2k star, 免费开源的白板绘图工具 tldraw 分类 开源分享 项目名: tldraw -- 无限画布白板 Github 开源地址&#xff1a; https://github.com/tldraw/tldraw 在线测试地址&#xff1a; tldraw 文档地址&#xff1a; tldraw SDK tldraw 是一款开源免费的无限画布白板&…

网络规划(homework 静态路由 and Rip路由表更新)

1、写出下图路由器1和路由器3中的路由表&#xff08;按直接交付、特定主机交付、特定网络交付、 默认交付的顺序放置路由项&#xff09; 2、写出Ri更新后的路由表&#xff08;rip路由协议&#xff09; 1、将Rj广播的路由消息全部1 2、直接对照着更新Ri中的路由表

基于java实现的二手车交易网站

开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea…

【可能是全网最丝滑的LangChain教程】六、快速入门Agent

系列文章地址 【可能是全网最丝滑的LangChain教程】一、LangChain介绍-CSDN博客 【可能是全网最丝滑的LangChain教程】二、LangChain安装-CSDN博客 【可能是全网最丝滑的LangChain教程】三、快速入门LLM Chain-CSDN博客 【可能是全网最丝滑的LangChain教程】四、快速入门Re…

[深度学习] 无人车环境准备

1. 安装过程基本遵循以下步骤 电脑端环境配置 - OriginBot智能机器人开源套件 需要注意以下两点&#xff1a; 1> 由于深度学习需要的包和镜像体积都比较大&#xff0c;所以虚拟机硬盘大小建议120GB 2> 虚拟机的网络适配器应该设置为桥接模式&#xff0c;如果使用NAT模…

Windows系统C盘空间优化进阶:磁盘清理与Docker日志管理

Windows系统C盘空间优化进阶&#xff1a;磁盘清理与Docker日志管理 文章目录 Windows系统C盘空间优化进阶&#xff1a;磁盘清理与Docker日志管理磁盘清理工具 使用“运行”命令访问磁盘清理利用存储感知自动管理空间清理WinSxS文件夹结合手动清理策略 小结删除临时文件总结&…

git入门教程

Git 1. Git历史 同生活中的许多伟大事件一样&#xff0c;Git 诞生于一个极富纷争大举创新的年代。Linux 内核开源项目有着为数众广的参与者。绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上&#xff08;1991&#xff0d;2002年间&#xff09;。到 2002…

vitepress系列-06-部署篇

部署篇 上传代码库 选择腾讯旗下的CONDING 有手就行 构建 采用CONDING自带的CICD: 注意&#xff1a;这边持续集成中的构建计划已经满足不了vitepress了&#xff0c;会把默认流水线拉挂了&#xff0c;但是如果你是vuepress依旧可以 采用云原生进行构建&#xff1a; 步骤一&a…

本地生活抖音同城商家流量推广运营解决方案

【干货资料持续更新&#xff0c;以防走丢】 本地生活抖音同城商家流量推广运营解决方案 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 抖音运营资料合集&#xff08;完整资料包含以下内容&#xff09; 目录 抖音本地生活运营方案&#xff0c;帮助本地生活服务…