流程有些复杂,QML不支持调用很多常见的js引擎,我们可以利用webview来达到。不过在使用socket.io,发现必须要在安卓4.4版本或更高才行,这个不是安卓的问题,是最新版的Qt没有优化老版本的WebView,唉。
开发流程图:
从发送到回调: QML -> WEBVIEW中的socke.io -> node.js服务器 ->WEBVIEW -> (标注)C++ -> QML
标注:由于socket.io是异步的,QML调用JS允许有回调,但是不支持在WEBVIEW中的异步回调。因此用C++来转发。
#ifndef TQMLHELPER_H
#define TQMLHELPER_H
#include <QObject>
#include<QVariant>
#include<QQuickWindow>
class TQmlHelper : public QObject
{
Q_OBJECT
public:
explicit TQmlHelper(QObject *parent = 0);
Q_INVOKABLE void setroot(QObject* obj);
Q_INVOKABLE void setTXL(QObject* obj);
void do_updataFlist(QByteArray& b);
signals:
public slots:
private:
QObject* root;
QObject* gen;
};
#endif // TQMLHELPER_H
#include "tqmlhelper.h"
#include <QDebug>
#include <QQuickWindow>
TQmlHelper::TQmlHelper(QObject *parent) : QObject(parent)
{
root = NULL;
}
void TQmlHelper::setroot(QObject* obj)
{
qDebug()<<"Get Root *******************"<<obj<<",and RootName:"+obj->objectName();
root = obj;
QObjectList objs = obj->children();
for(auto x : objs)
{
qDebug()<< "auto x : objs :QQuickWindow:"<< x->objectName();
}
}
void TQmlHelper::setTXL(QObject* obj)
{
gen = obj;
qDebug()<< "gen::::::::::::--->>>:"<< obj->objectName();
}
void TQmlHelper::do_updataFlist(QByteArray& b)
{
qDebug()<<"do_updataFlist:"<<b;
QString a = b; ;
QMetaObject::invokeMethod(gen, "刷新好友列表", Qt::ConnectionType::BlockingQueuedConnection, Q_ARG(QVariant, a));
}
#include"myhttp.h"
#include<QtWidgets/QMessageBox>
extern TQmlHelper* tmphelp;
Helloworldcontroller3::Helloworldcontroller3(QObject* parent):HttpRequestHandler(parent)
{
}
#include <qmutex.h>
void Helloworldcontroller3::service(HttpRequest &request, HttpResponse &response) {
response.setHeader("Access-Control-Allow-Origin", "*");
QByteArray path=request.getPath();
qDebug("RequestMapper: path=%s",path.data());
if (path=="/loging") {
QByteArray a = request.getParameter("p1");
qDebug()<<a;
if(a.toInt()==1)
{
qDebug()<<"loging ok";
}else{
qDebug()<<"loging erro";
}
response.setStatus(200,"OK");
}
else if (path=="/Flist") {
QByteArray a = request.getParameter("p1");
tmphelp->do_updataFlist(a);
response.write("ok",true);
response.setStatus(200,"ok");
}
else {
response.setStatus(404,"oo!Not found");
QString s =QString::fromLocal8Bit("请请不要跨域哦");
QByteArray s2(s.toStdString().c_str());
response.write(s2,true);
}
qDebug("RequestMapper: finished request");
}
*****main.qml
TQmlHelper* tmphelp;
void RegFunc(QQmlApplicationEngine& engine)
{
tmphelp = new TQmlHelper(&engine);
engine.rootContext()->setContextProperty("myapp", (QObject*)tmphelp);
}
#include<QLabel>
int main(int argc, char *argv[])
{
QApplication::setApplicationName("Myapp");
QApplication::setOrganizationName("QtProject");
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
QApplication app(argc, argv);
qDebug("********开始*********");
QSettings* listenerSettings= new QSettings("assets:/demo.ini",QSettings::IniFormat,&app);
qDebug("config file loaded");
listenerSettings->beginGroup("listener");
new HttpListener(listenerSettings, new Helloworldcontroller3(&app), &app);
QQuickStyle::setStyle("Material");
QQmlApplicationEngine engine;
RegFunc(engine);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
qDebug("********结束*********");
return app.exec();
}
++,,,++
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.0
import QtQuick.Controls.Styles 1.4
import QtQuick.Controls.Material 2.0
import QtGraphicalEffects 1.0
import QtQuick.Particles 2.0
import "Ndk.js" as Ndk
import "./code"
import QtWebView 1.1
ApplicationWindow {
id:root;
visible: true;
height: 480;
width: 320;
Component.onCompleted: {
myapp.setroot(root);
}
property real pixelDensity: 4.46
property real multiplierH: root.height/480
property real multiplierW: root.width/320
function dpH(numbers) {
return Math.round(numbers*((pixelDensity*25.4)/160)*multiplierH);
}
function dpW(numbers) {
return Math.round(numbers*((pixelDensity*25.4)/160)*multiplierW);
}
function dpi2(px)
{
return Math.round(px*((Screen.pixelDensity*25.4)/160))
}
function px2dip(pxValue) {
var scale = myapp.getdensity();
return (pxValue / scale + 0.5);
}
property color accentcol:"red"
property color backgroundcol:"white"
property color foregroundcol:"#000000"
property color primarycol:"blue"
Material.accent:accentcol
Material.background:backgroundcol
Material.foreground:foregroundcol
Material.primary: primarycol
MessageDialog {
id:dlg;
objectName: "xiaoxikuang";
width: Screen.width;
height: Screen.hight;
visible: false;
title: "进度"
icon: StandardIcon.Question
text: "file.txt already exists. Replace?"
detailedText: "To replace a file means that its existing contents will be lost. " +
"The file that you are copying now will be copied over it instead."
standardButtons: StandardButton.Yes | StandardButton.No
}
function messaggeBox(title,txt)
{
dlg.text = txt;
dlg.title = title;
dlg.visible = true;
}
Item {
visible: true
width:parent.width;
height:dpH(100);
z:100
ListView{
id:dbgtxt;
width:parent.width;
height:dpH(100);
model: ListModel{
id:dbgmodel;
ListElement{txt:"123"}
}
delegate: Label{
font.pixelSize: dpW(18);
font.bold: true;
color: Ndk.green_1
text:txt
}
displaced: Transition {
NumberAnimation { properties: "x,y"; duration: 1000 }
}
move: Transition {
NumberAnimation { properties: "x,y"; duration: 1000 }
}
}
}
function dbg(txt)
{
if(dbgmodel.count>=5)
{
dbgmodel.clear();
}
dbgmodel.append({txt:txt});
}
property alias mwebview: mywebview;
property var txtprogress_var: 0
property var txtmainpage_var: 0
property var txtsubpage1_var: 0
Item {
width: Screen.width;
height: Screen.height
visible: true
z:100
id: bk;
Rectangle{
id:zezao;
opacity: 0.9;
anchors.fill: parent;
color:"#cccccc"
Column{
width: parent.width;
spacing: 2;
Label{
font.pixelSize: dpW(18);
font.bold: true;
color: Ndk.文字色偏白
id:txtprogress
text:"WebKit加载进度:"+txtprogress_var
}
Rectangle{
anchors.margins: dpW(5);
width: parent.width;
height: dpH(20);
color: Ndk.SDColor_success;
radius: dpW(2)
Rectangle{
radius: dpW(2)
property var pass: 0
id:bartxt;
width: parent.width/100*pass;
height: dpH(20);
color: Ndk.SDColor_info;
Behavior on width {NumberAnimation{ easing.type: Easing.InOutBounce;duration: 2000}}
}
}
Label{
id:txtmainpage;
color: Ndk.文字色偏白
font.pixelSize: dpW(18);
font.bold: true;
text:"主页面加载进度:"+txtmainpage_var
}
Rectangle{
anchors.margins: dpW(5);
width: parent.width;
height: dpH(20);
color: Ndk.SDColor_success;
radius: dpW(2)
Rectangle{
radius: dpW(2)
property var pass: 0
id:barmainpage;
width: parent.width/100*pass;
height: dpH(20);
color: Ndk.SDColor_info;
Behavior on width {NumberAnimation{ easing.type: Easing.InOutBounce;duration: 2000}}
}
}
Label{
id:txtsubpage1;
color: Ndk.文字色偏白
font.pixelSize: dpW(18);
font.bold: true;
text:"首页加载进度:"+txtmainpage_var
}
Rectangle{
anchors.margins: dpW(5);
width: parent.width;
height: dpH(20);
color: Ndk.SDColor_success;
radius: dpW(2)
Rectangle{
radius: dpW(2)
property var pass: 0
id:barsubpage1;
width: parent.width/100*pass;
height: dpH(20);
color: Ndk.SDColor_info;
Behavior on width {NumberAnimation{ easing.type: Easing.InOutBounce;duration: 2000}}
}
}
}
Behavior on opacity {
NumberAnimation{ easing.type: Easing.InOutBounce;duration: 2000}
}
onOpacityChanged: {
if(opacity==0)
{
bk.visible = false;
bk.deleteLater();
dbg("onOpacityChanged");
}
}
}
MouseArea{
anchors.fill: parent;
onPressed:{
mouse.accepted = true
}
}
function changgepro(type,pro)
{
if(type == 0)
{
bartxt.pass = pro;
txtprogress_var = pro;
}else if(type == 1)
{
barmainpage.pass = pro;
txtmainpage_var = pro;
}else if(type == 2)
{
barsubpage1.pass = pro;
txtsubpage1_var = pro;
}
if(txtprogress_var == 100 && txtmainpage_var == 100 && txtsubpage1_var ==100)
{
oninitOk();
}
}
function oninitOk()
{
foot.visible = true;
zezao.opacity = 0;
benavShow = true;
messaggeBox("状态","加载完毕");
}
}
WebView{
visible: false;
id:mywebview;
objectName: "webview"
url:"file:///android_asset/index.html";
onLoadProgressChanged: {
bk.changgepro(0,mywebview.loadProgress);
}
onLoadingChanged: {
}
}
property alias roothd: hd
header: ToolBar{
id:hd;
states: [
State {
name: "hide"
PropertyChanges {
target: hd;opacity:0;height:0;width:0;
}
PropertyChanges {
target: lisetview;opacity:0;rotation:360;height:0;
}
}
]
transitions: Transition {
ParallelAnimation {
NumberAnimation { duration: 500; properties: "opacity,x,contentY,height,width" }
ColorAnimation { property: "color"; duration: 888 }
NumberAnimation { duration: 888; properties: "rotation" }
}
}
height:dpH(60);
Text{
text:"mywebview.loadProgress+"
anchors.centerIn: parent
color: "white"
font.pixelSize: dpW(18)
}
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true
color: "#000000";
radius: dpH(15)
id:drop;
horizontalOffset: 0;
verticalOffset: 0;
samples: 16;
smooth: true;
}
}
function getdpistype()
{
console.log("SCALA: "+Screen.pixelDensity*25.4/160)
console.log(Screen.pixelDensity)
var curdpi = Screen.pixelDensity*25.4;
var mydpi = curdpi.toFixed(0);
console.log(mydpi);
console.log("my dpi "+myapp.getdpi())
return "MYDPI+"+mydpi;
if(mydpi>=480)
{
console.log("XXHDPI");
return "XXHDPI";
}else if(mydpi>=320)
{
console.log("XHDPI");
return "XHDPI";
}else if(mydpi>=240)
{
console.log("HDPI");
return "HDPI";
}else if(mydpi>=180)
{
console.log("MDPI");
return "MDPI";
}else
{
console.log("LDPI");
return "LDPI";
}
}
Loader{
id:mainpage;
asynchronous: true
anchors.fill: parent;
sourceComponent: commapnpage;
onProgressChanged: {
bk.changgepro(1,progress*100);
}
}
Component{
id:commapnpage;
SwipeView{
state: "hide1"
currentIndex: tabindex
onCurrentIndexChanged: {
console.log("onCurrentIndexChanged:"+currentIndex);
tabindex = currentIndex;
if(currentIndex==1)
{
tongxinlu.item.获取好友列表();
}
}
Page{
id:gouzhen;
Loader{
id:shouye
asynchronous: true
anchors.fill: parent
sourceComponent: Page_gouzhen{}
onProgressChanged: {
bk.changgepro(2,shouye.progress*100);
}
}
}
Page{
id:tongxun;
Loader{
asynchronous: true
id:tongxinlu;
anchors.fill: parent;
sourceComponent: PageTongxunlv{}
}
}
}
}
property var benavShow :false;
property int tabindex: mainpage.item.currentIndex
footer: Row{
visible: benavShow;
id:foot;
width:benavShow? parent.width:0;
height:benavShow? width/5*0.75:0;
Repeater{
id:rep
delegate: NavNewDelegate{
width: benavShow?parent.width/5:0;
height:benavShow?width*0.75:0;
rotation:benavShow?0:Math.random()*360
id:navitem;
Behavior on width {NumberAnimation {duration: 2000; easing.type: Easing.InOutQuad}}
Behavior on height {NumberAnimation {duration: 2000; easing.type: Easing.InOutQuad}}
Behavior on rotation {NumberAnimation {duration: 2000; easing.type: Easing.InOutQuad}}
onClick: {
dbg("Delegate传递过来的下标:"+index+tabindex);
console.log("Delegate传递过来的下标:"+index+tabindex);
}
}
model:NavNewModel{id:model1}
}
}
Keys.enabled: true;
Keys.onReleased: {
console.log("key"+event.key);
if(event.key==Qt.Key_Back)
{
console.log("back");
}
}
}
****关键代码 QML和C++以及webview中的js库交互的部分
import QtQuick 2.7
import QtQuick.Controls 1.4 as Old
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtQuick.Layouts 1.1
import QtQuick.Window 2.0
import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.4
import QtQuick.Controls.Material 2.0
import QtQuick.Controls.Universal 2.0
import QtGraphicalEffects 1.0
import QtQuick.Particles 2.0
import QtWebSockets 1.0
import "../"
Item {
property alias mview: view
objectName: "FList"
anchors.fill: parent
id:gen
ListView{
id:view
anchors.fill: parent
delegate: ListTongxunlvDelegate{
}
model: ListTongxunlvModel{
id:model
}
Component.onCompleted: {
myapp.setTXL(gen)
console.log("cout:"+model.count)
model.append({name:"代码统计行数:"+model.count,tip:"代码生成的行"})
}
}
//专门被C++调用的函数
function 刷新好友列表(x)
{
console.log("get c++ *****"+x)
root.messaggeBox("刷新好友列表:",x)
}
function 获取好友列表()
{
mywebview.runJavaScript("获取好友列表()",function(result){
for(var x in result)
{
root.dbg(result[x])
}
root.dbg(result)
})
}
}
**********HTML代码 使用裸页面只为加载JS功能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
<title>h</title>
<script src="jquery-1.12.3.js" type="text/javascript" charset="utf-8"></script>
<script src="js/mui.min.js" type="text/javascript" charset="utf-8"></script>
<script src="socket.io.js" type="text/javascript" charset="utf-8"></script>
<script src="index2.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
</body>
</html>
*****js代码
var msocket;
mui.init({})
mui.ready(lod);
function 登录成功(arg1)
{
$.post("http://localhost:8080/loging?p1="+arg1);
}
function 发送到C加加(path,arg1)
{
$.post("http://localhost:8080/"+path+"?p1="+arg1);
}
var isloging = false;
function lod(){
alert("ok");
console.log("模拟器连接");
msocket = io.connect('ws://192.168.0.101:8081', { 'reconnect': true });
msocket.on('connect',function(){
alert("连接成功;");
isloging = true;
msocket.emit("denglu","admin","123",function(callbackdata){
alert("登录结果:"+callbackdata);
})
登录成功(1);
});
msocket.on('connecting',function(){
alert("正在连接");
});
msocket.on('connect_timeout',function(){
console.log("connect_timeout");
});
msocket.on('connect_failed',function(){
alert("连接失败");
});
msocket.on('error',function(data){
alert("错误发生 并且无法被其他事件类型所处理");
});
msocket.on('reconnect_failed',function(){
alert("reconnect_failed");
});
msocket.on('reconnect',function(TheNumber){
alert("reconnectOk"+TheNumber);
});
msocket.on('reconnecting',function(TheNumber){
console.log("reconnecting"+TheNumber);
});
}
function Testfunc()
{
return 123;
}
var mdata;
function 获取好友列表()
{
if(isloging==false)
{
alert("暂未登录,无法拉取好友");
return "";
}
alert("获取好友中");
msocket.emit("获取好友",function(data){
console.log(data)
发送到C加加("Flist",data);
mdata = data;
});
alert("最后层返回");
return mdata;
}