Repository: GDPURJYFS/WellChat Branch: master Commit: a861ddfd961b Files: 97 Total size: 248.8 KB Directory structure: gitextract_f1fzdbi7/ ├── .gitignore ├── LICENSE ├── README.md ├── Sparrow/ │ ├── Sparrow.pri │ ├── keyboard.cpp │ ├── keyboard.h │ ├── qmlnetworkaccessmanagerfactory.cpp │ ├── qmlnetworkaccessmanagerfactory.h │ ├── qtbridgingandroid.cpp │ ├── qtbridgingandroid.h │ └── sparrow_global.h ├── WellChat.pro ├── android/ │ ├── AndroidManifest.xml │ ├── assets/ │ │ └── font/ │ │ └── NotoSansHans-DemiLight.otf │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── res/ │ │ └── values/ │ │ └── libs.xml │ └── src/ │ └── org/ │ └── gdpurjyfs/ │ ├── sparrow/ │ │ └── QtBridgingAndroid.java │ └── wellchat/ │ └── WellChatActivity.java ├── deployment.pri ├── desktop.qrc ├── doc/ │ └── weixin-ui-analyse.md ├── main.cpp ├── qml/ │ └── WellChat/ │ ├── BussinessPage/ │ │ ├── Chat/ │ │ │ ├── ChatPage.qml │ │ │ ├── Heartbeat.qml │ │ │ ├── Lazy.qml │ │ │ ├── Tuling123.js │ │ │ └── heart.js │ │ ├── ChatsView.qml │ │ ├── Contacts/ │ │ │ └── ContactsListView.qml │ │ ├── ContactsView.qml │ │ ├── Discover/ │ │ │ └── MomentsPage/ │ │ │ └── MomentsPage.qml │ │ ├── DiscoverPage.qml │ │ ├── Personal/ │ │ │ ├── FavoritesPage.qml │ │ │ ├── MyPostsPage.qml │ │ │ ├── Settings/ │ │ │ │ ├── AboutPage.qml │ │ │ │ ├── ChatSettingsPage.qml │ │ │ │ ├── DoNotDisturbSettingsPage.qml │ │ │ │ ├── GeneralSettingsPage.qml │ │ │ │ ├── MyAccountSettingsPage.qml │ │ │ │ ├── NotificationsSettingsPage.qml │ │ │ │ ├── PrivacySettingsPage.qml │ │ │ │ └── SettingsGroup.qml │ │ │ └── SettingsPage.qml │ │ ├── PersonalPage.qml │ │ ├── ProfilePage.qml │ │ ├── R.qml │ │ └── qmldir │ ├── Component/ │ │ ├── +android/ │ │ │ └── UI.js │ │ ├── Constant.qml │ │ ├── Icon.qml │ │ ├── IconButton.qml │ │ ├── IconLabel.qml │ │ ├── MainListView.qml │ │ ├── SampleTextArea.qml │ │ ├── ScrollBar.qml │ │ ├── Separator.qml │ │ └── UI.js │ ├── MainView.qml │ ├── Sparrow/ │ │ ├── +android/ │ │ │ ├── UI.js │ │ │ └── WebPage.qml │ │ ├── BottomBar.qml │ │ ├── ClickedShaderEffect.qml │ │ ├── GeneralSettings.qml │ │ ├── Page.qml │ │ ├── PageStackWindow.qml │ │ ├── PopupLayer/ │ │ │ ├── Delegate/ │ │ │ │ ├── PopupLayerBottomMenuDelegate.qml │ │ │ │ ├── PopupLayerDialogDelegate.qml │ │ │ │ ├── PopupLayerSideMenuDelegate.qml │ │ │ │ └── qmldir │ │ │ ├── PopupLayer.qml │ │ │ ├── PopupLayerDelegate.qml │ │ │ ├── PopupLayerTransition.qml │ │ │ ├── qmldir │ │ │ └── readme.md │ │ ├── QObject.qml │ │ ├── SampleButton.qml │ │ ├── SampleIcon.qml │ │ ├── SampleLabel.qml │ │ ├── SampleTextField.qml │ │ ├── TopBar.qml │ │ ├── Tracker.qml │ │ ├── UI.js │ │ ├── WebPage.qml │ │ ├── qmldir │ │ └── resources/ │ │ ├── NotoSansHans-DemiLight.otf │ │ └── readme.md │ ├── WellChat.qmlproject │ ├── main.qml │ └── resource/ │ ├── R.qml │ └── qmldir ├── qml.qrc └── src/ └── wellchat/ ├── collectionsmodel.cpp └── collectionsmodel.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ WellChat.pro.user *.user ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 GDPURJYFS Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # WellChat WellChat is a Application that is a WeChat-like APP by qml. > best tool vesion: > Qt version >= 5.5.0 > Android SDK APi >= 19 > Android 5.0 or new 使用 qml 来仿制安卓微信的 Qt 程序,可以运行在安卓上。 ## 界面展示 ### 480 * 800 微信界面 ![](Screenshot/480x800/WeChat.png) 仿制界面 ![](Screenshot/480x800/WellChat-01.png) ![](Screenshot/480x800/WellChat-02.png) ![](Screenshot/480x800/WellChat-03.png) ![](Screenshot/480x800/WellChat-04.png) ### 1080P 微信界面 ![](Screenshot/1080x1920/WellChat01.jpg) ![](Screenshot/1080x1920/WellChat02.jpg) ![](Screenshot/1080x1920/WellChat03.jpg) ![](Screenshot/1080x1920/WellChat04.jpg) ![](Screenshot/1080x1920/WellChat05.jpg) ![](Screenshot/1080x1920/WellChat06.jpg) ![](Screenshot/1080x1920/WellChat07.jpg) ![](Screenshot/1080x1920/WellChat08.jpg) 植入图灵聊天机器人的聊天界面 ![](Screenshot/1080x1920/WellChat09.jpg) ## QML开发安卓应用的局限 `QtQuick.Control` 这个模块是专门为桌面平台准备的,对移动平台支持并不完善。不能很好地动态切换不同的 `ToolBar` 和 `StatusBar` 来适配不同的页面。 如何设计一个简单的页面栈呢?查看[简单易用的页面栈框架](https://github.com/GDPURJYFS/Sparrow) ## 剖析界面 微信界面的主要操作交互逻辑有首界面的四个可以切换的分页,以及一个页面栈。查看[微信界面剖析](doc/weixin-ui-analyse.md) --- ***images and protocol Copyright (C) by [Tencent] (http://weixin.qq.com/)*** ***图片、协议版权归[腾讯] (http://weixin.qq.com/) 所有!*** ================================================ FILE: Sparrow/Sparrow.pri ================================================ QT += core network sql qml android { QT += androidextras } HEADERS += \ #$$PWD/qmlnetworkaccessmanagerfactory.h \ #$$PWD/notificationclient.h \ $$PWD/keyboard.h \ $$PWD/sparrow_global.h \ #$$PWD/qtnativeforandroid.h \ $$PWD/qtbridgingandroid.h SOURCES += \ #$$PWD/qmlnetworkaccessmanagerfactory.cpp \ #$$PWD/notificationclient.cpp \ $$PWD/keyboard.cpp \ #$$PWD/qtnativeforandroid.cpp \ $$PWD/qtbridgingandroid.cpp OTHER_FILES += $$PWD/../android/src/org/gdpurjyfs/sparrow/QtBridgingAndroid.java # $$PWD/../android/src/org/gdpurjyfs/sparrow/QtNativeForAndroid.java \ DISTFILES += ================================================ FILE: Sparrow/keyboard.cpp ================================================ #include "keyboard.h" #include #include #include "sparrow_global.h" Keyboard::Keyboard(QObject *parent) : QObject(parent), m_inputMethod (QGuiApplication::inputMethod()) { connect(m_inputMethod, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged())); #ifndef Q_OS_ANDROID connect(this, SIGNAL(visibleChanged()), this, SLOT(onVisibleChangedChanged())); #endif } bool Keyboard::visible() const { return m_inputMethod->isVisible(); } void Keyboard::setVisible(bool v) { if(v) { m_inputMethod->show(); } else { m_inputMethod->hide(); } } QRectF Keyboard::keyboardRectangle() const { return this->m_keyboardRectangle; } Keyboard *Keyboard::singleton() { static Keyboard* keyboard = new Keyboard(QCoreApplication::instance()); return keyboard; } void Keyboard::setKeyboardRectangle(const QRectF &keyboardRectangle) { if(this->keyboardRectangle() != keyboardRectangle) { this->m_keyboardRectangle = keyboardRectangle; emit keyboardRectangleChanged(this->m_keyboardRectangle); } } void Keyboard::onVisibleChangedChanged() { #ifndef Q_OS_ANDROID this->setKeyboardRectangle(m_inputMethod->keyboardRectangle()); #endif } ================================================ FILE: Sparrow/keyboard.h ================================================ #ifndef VIRTUALKEYBOARD_H #define VIRTUALKEYBOARD_H #include #include class QtBridgingAndroid; class QtNativeForAndroid; class QInputMethod; class Keyboard : public QObject { Q_OBJECT Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged) Q_PROPERTY(QRectF keyboardRectangle READ keyboardRectangle NOTIFY keyboardRectangleChanged) public: explicit Keyboard(QObject *parent = 0); bool visible()const; void setVisible(bool v); QRectF keyboardRectangle()const; static Keyboard *singleton(); signals: void visibleChanged(); void keyboardRectangleChanged(const QRectF& keyboardRectangle); protected slots: void setKeyboardRectangle(const QRectF& keyboardRectangle); private slots: void onVisibleChangedChanged(); private: QInputMethod* m_inputMethod; QRectF m_keyboardRectangle; friend class QtNativeForAndroid; friend class QtBridgingAndroid; }; #endif // VIRTUALKEYBOARD_H ================================================ FILE: Sparrow/qmlnetworkaccessmanagerfactory.cpp ================================================ #include "qmlnetworkaccessmanagerfactory.h" #include #include #include #include #include QmlNetworkAccessManagerFactory::QmlNetworkAccessManagerFactory() { #ifdef QT_DEBUG qDebug() << QStandardPaths::writableLocation(QStandardPaths::CacheLocation); #endif } QmlNetworkAccessManagerFactory::~QmlNetworkAccessManagerFactory() { #ifdef QT_DEBUG qDebug() << "~QmlNetworkAccessManagerFactory"; #endif } QNetworkAccessManager *QmlNetworkAccessManagerFactory::create(QObject *parent) { QNetworkAccessManager* manager = new QNetworkAccessManager(parent); QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); if(QDir().mkpath(cachePath)) { QNetworkDiskCache* diskCache = new QNetworkDiskCache(manager); diskCache->setCacheDirectory(cachePath); diskCache->setMaximumCacheSize(10*1024*1024); manager->setCache(diskCache); } return manager; } ================================================ FILE: Sparrow/qmlnetworkaccessmanagerfactory.h ================================================ #ifndef QMLNETWORKACCESSMANAGERFACTORY_H #define QMLNETWORKACCESSMANAGERFACTORY_H #include class QmlNetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory { public: QmlNetworkAccessManagerFactory(); ~QmlNetworkAccessManagerFactory(); QNetworkAccessManager *create(QObject *parent); }; #endif // QMLNETWORKACCESSMANAGERFACTORY_H ================================================ FILE: Sparrow/qtbridgingandroid.cpp ================================================ #include "qtbridgingandroid.h" #include "keyboard.h" #include #include #include QtBridgingAndroid::QtBridgingAndroid(QObject *parent) : QObject(parent) { } void QtBridgingAndroid::sendNotification(const QString ¬ifyString) { #ifdef Q_OS_ANDROID #ifdef QT_DEBUG qDebug() << "sending... "; #endif QAndroidJniObject javaNotification = QAndroidJniObject::fromString(notifyString); // org/gdpurjyfs/wellchat/NotificationClient // org/gdpurjyfs/wellchat/QtBridgingAndroid // org/gdpurjyfs/sparrow/QtBridgingAndroid QAndroidJniObject::callStaticMethod("org/gdpurjyfs/sparrow/QtBridgingAndroid", "notify", "(Ljava/lang/String;)V", javaNotification.object() ); Q_SAFE_CALL_JAVA #endif #ifndef Q_OS_ANDROID Q_UNUSED(notifyString) qDebug() << "not allow to use the QtAndroidExtras"; #endif } void QtBridgingAndroid::setStatusBarColor(const QColor &color) { #ifdef Q_OS_ANDROID QString colorString = color.name(QColor::HexRgb); QAndroidJniObject javaColorString = QAndroidJniObject::fromString(colorString); #ifdef QT_DEBUG qDebug() << "colorString" << colorString; #endif // org/gdpurjyfs/wellchat/QtBridgingAndroid // org/gdpurjyfs/sparrow/QtBridgingAndroid QAndroidJniObject::callStaticMethod("org/gdpurjyfs/sparrow/QtBridgingAndroid", "setStatusBarColor", "(Ljava/lang/String;)V", javaColorString.object() ); Q_SAFE_CALL_JAVA #else Q_UNUSED(color) #endif #ifndef Q_OS_ANDROID qDebug() << "not allow to use the QtAndroidExtras"; #endif } #ifdef Q_OS_ANDROID // 在 Java 中被调用 void QtBridgingAndroid::notifiedKeyboardRectangle(JNIEnv *env, jobject thiz, jint x, jint y, jint width, jint height) { Q_UNUSED(env) Q_UNUSED(thiz) if(QGuiApplication::applicationState() != Qt::ApplicationHidden) { #ifdef QT_DEBUG qDebug() << "invoke method notifiedKeyboardRectangle: " << #endif QMetaObject::invokeMethod(Keyboard::singleton(), "setKeyboardRectangle", Qt::AutoConnection, Q_ARG(QRectF, QRect(x, y, width, height))) ; } } bool QtBridgingAndroid::registerNativeMethodForJava() { JNINativeMethod methods[] = { { "notifiedKeyboardRectangle", "(IIII)V", (void*)&(QtBridgingAndroid::notifiedKeyboardRectangle) } }; // org/gdpurjyfs/wellchat/QtBridgingAndroid // "org/gdpurjyfs/sparrow/qtnativeforandroid" // 包名 // const char * classname = "org/gdpurjyfs/sparrow/QtNativeForAndroid"; const char * classname = "org/gdpurjyfs/sparrow/QtBridgingAndroid"; jclass clazz; QAndroidJniEnvironment env; QAndroidJniObject javaClass(classname); clazz = env->GetObjectClass(javaClass.object()); Q_SAFE_CALL_JAVA bool result = false; if(clazz) { jint ret = env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])); //! [bug] env->DeleteGlobalRef(clazz); result = (ret >= 0); } else { #ifdef QT_DEBUG qDebug() << "can't find java class" << classname; #endif } Q_SAFE_CALL_JAVA return result; } void QtBridgingAndroid::installListener() { #ifdef Q_OS_ANDROID // org/gdpurjyfs/wellchat/NotificationClient // org/gdpurjyfs/wellchat/QtBridgingAndroid // org/gdpurjyfs/sparrow/QtBridgingAndroid QAndroidJniObject::callStaticMethod("org/gdpurjyfs/sparrow/QtBridgingAndroid", "listenKeyboardHeight"); Q_SAFE_CALL_JAVA #endif } #endif ================================================ FILE: Sparrow/qtbridgingandroid.h ================================================ #ifndef QTBRIDGINGANDROID_H #define QTBRIDGINGANDROID_H #include #include #include "sparrow_global.h" class QtBridgingAndroid : public QObject { Q_OBJECT public: explicit QtBridgingAndroid(QObject *parent = 0); // Java Method Q_INVOKABLE void sendNotification(const QString& notifyString); // Java Method Q_INVOKABLE void setStatusBarColor(const QColor& color); public: #ifdef Q_OS_ANDROID static void notifiedKeyboardRectangle(JNIEnv * env, jobject thiz, jint x, jint y, jint width, jint height); static bool registerNativeMethodForJava(); static void installListener(); #endif }; #endif // QTBRIDGINGANDROID_H ================================================ FILE: Sparrow/sparrow_global.h ================================================ #ifndef GLOBAL #define GLOBAL #include #ifdef Q_OS_ANDROID #include #include #define Q_SAFE_CALL_JAVA { \ QAndroidJniEnvironment env; \ if(env->ExceptionCheck()) { \ qDebug() << "have a java exception"; \ env->ExceptionClear(); \ } \ } #endif #endif // GLOBAL ================================================ FILE: WellChat.pro ================================================ TEMPLATE = app QT += qml quick widgets SOURCES += main.cpp \ src/wellchat/collectionsmodel.cpp RESOURCES += \ qml.qrc # Additional import path used to resolve QML modules in Qt Creator's code model QML_IMPORT_PATH = # Default rules for deployment. include(deployment.pri) include(Sparrow/Sparrow.pri) DISTFILES += \ android/AndroidManifest.xml \ android/gradle/wrapper/gradle-wrapper.jar \ android/gradlew \ android/res/values/libs.xml \ android/build.gradle \ android/gradle/wrapper/gradle-wrapper.properties \ android/gradlew.bat ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android OTHER_FILES += android/src/org/gdpurjyfs/wellchat/WellChatActivity.java # android/src/org/gdpurjyfs/wellchat/QtBridgingAndroid.java \ HEADERS += \ src/wellchat/collectionsmodel.h ================================================ FILE: android/AndroidManifest.xml ================================================ ================================================ FILE: android/build.gradle ================================================ buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.1.0' } } allprojects { repositories { jcenter() } } apply plugin: 'com.android.application' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } android { /******************************************************* * The following variables: * - androidBuildToolsVersion, * - androidCompileSdkVersion * - qt5AndroidDir - holds the path to qt android files * needed to build any Qt application * on Android. * * are defined in gradle.properties file. This file is * updated by QtCreator and androiddeployqt tools. * Changing them manually might break the compilation! *******************************************************/ compileSdkVersion androidCompileSdkVersion.toInteger() buildToolsVersion androidBuildToolsVersion sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] res.srcDirs = [qt5AndroidDir + '/res', 'res'] resources.srcDirs = ['src'] renderscript.srcDirs = ['src'] assets.srcDirs = ['assets'] jniLibs.srcDirs = ['libs'] } } lintOptions { abortOnError false } } ================================================ FILE: android/gradle/wrapper/gradle-wrapper.properties ================================================ #Wed Apr 10 15:27:10 PDT 2013 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip ================================================ FILE: android/gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # For Cygwin, ensure paths are in UNIX format before anything is touched. if $cygwin ; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` fi # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >&- APP_HOME="`pwd -P`" cd "$SAVED" >&- CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: android/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: android/res/values/libs.xml ================================================ https://download.qt-project.org/ministro/android/qt5/qt-5.4 ================================================ FILE: android/src/org/gdpurjyfs/sparrow/QtBridgingAndroid.java ================================================ /* * Copyright (c) <2015> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /*! * Activity 先于 Qt 加载 * 1. 在 Activity OnCreate 中调用 QtBridgingAndroid::Init,然后进入Qt::main * 2. 在 Qt::main 中注册 Java 的 native 函数 QtBridgingAndroid::notifiedKeyboardRectangle * 3. 在 Qt::main 通过调用 Java::QtBridgingAndroid::listenKeyboardHeight 注入监听键盘事件 * 4. 在 Qt::main 加载 QML。 */ package org.gdpurjyfs.sparrow; import android.app.Notification; import android.app.NotificationManager; import android.content.Context; import android.app.Activity; import android.os.Bundle; import android.graphics.Color; import android.app.Activity; import android.view.Window; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.RelativeLayout; import android.view.ViewTreeObserver; import android.graphics.Rect; import android.view.ViewGroup; import java.lang.Thread; //! http://blog.csdn.net/foruok/article/details/46323129 class SetStatusBarColorRunnable implements Runnable { private Activity m_activity; private int m_color; public SetStatusBarColorRunnable(Activity activity, int color) { m_activity = activity; m_color = color; } // this method is called on Android Ui Thread @Override public void run() { m_activity.getWindow().setStatusBarColor(m_color); } } public class QtBridgingAndroid { public static Activity instanceActivity; private static NotificationManager notificationManager; private static Notification.Builder builder; private static Rect keyboardRectangle; private static boolean hasListenVirtualKeyboard = false; // allow Qt call this static function // api 19 // @TargetApi(Build.VERSION_CODES.LOLLIPOP) // android.view.ViewRootImpl$CalledFromWrongThreadException //! http://daydayup1989.iteye.com/blog/784831 public static void setStatusBarColor(String colorString) { if(instanceActivity != null) { try { System.out.println("colorString: " + colorString); int color = Color.parseColor(colorString); System.out.println("color: " + color); instanceActivity.runOnUiThread(new SetStatusBarColorRunnable(instanceActivity, color)); } catch(IllegalArgumentException e) { e.printStackTrace(); } catch(Exception e1) { e1.printStackTrace(); } } } // allow Qt call this static function public static void notify(String notifyText) { if (notificationManager == null) { notificationManager = (NotificationManager)instanceActivity.getSystemService(Context.NOTIFICATION_SERVICE); builder = new Notification.Builder(instanceActivity); builder.setSmallIcon(org.gdpurjyfs.wellchat.R.drawable.icon); builder.setContentTitle("WellChat"); } System.out.println("set setContentText"); builder.setContentText(notifyText); notificationManager.notify(1, builder.build()); } private static View getRootView(Activity context) { return ((ViewGroup)context.findViewById(android.R.id.content)).getChildAt(0); } // 确保Qt调用时,只注入一次键盘监听事件 public static void listenKeyboardHeight() { if(!hasListenVirtualKeyboard) { final View myRootView = getRootView(instanceActivity); myRootView.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { Rect outRect = new Rect(); instanceActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(outRect); keyboardRectangle = new Rect(); myRootView.getWindowVisibleDisplayFrame(keyboardRectangle); int screenHeight = myRootView.getRootView().getHeight(); // 小于100 就不行了 // 这里还要减去状态栏的高度 // 魔幻数字 // android:windowSoftInputMode="adjustPan" magic = 0 // 其他情况为 5 int magic = 0; int virtualKeyboardHeight = screenHeight - (keyboardRectangle.bottom - keyboardRectangle.top) - outRect.top - magic; if( virtualKeyboardHeight < 100 ) { virtualKeyboardHeight = 0; } // java 通知 Qt 键盘改变了 System.out.println("try to call native method"); notifiedKeyboardRectangle( keyboardRectangle.centerX(), keyboardRectangle.centerY(), keyboardRectangle.width(), virtualKeyboardHeight); } }); hasListenVirtualKeyboard = true; } } // allow java call this native method public static native void notifiedKeyboardRectangle(int x, int y, int width, int height); // Java call this method and init this static Bridge Class public static void Init(Activity instanceActivity) { QtBridgingAndroid.instanceActivity = instanceActivity; System.out.println("QtBridgingAndroid::Init"); } } ================================================ FILE: android/src/org/gdpurjyfs/wellchat/WellChatActivity.java ================================================ /**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtAndroidExtras module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ package org.gdpurjyfs.wellchat; import android.app.Notification; import android.app.NotificationManager; import android.content.Context; import android.app.Activity; import android.view.Window; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.RelativeLayout; import android.view.ViewTreeObserver; import android.graphics.Rect; import android.view.ViewGroup; import android.os.Bundle; //import org.gdpurjyfs.sparrow; public class WellChatActivity extends org.qtproject.qt5.android.bindings.QtActivity { // @Override // public void onCreate (Bundle savedInstanceState){ // System.out.println("这里竟然不能有其他复杂的函数操作,会闪退的。"); // super.onCreate(savedInstanceState); // org.gdpurjyfs.sparrow.QtBridgingAndroid.Init(this); // } public WellChatActivity() { org.gdpurjyfs.sparrow.QtBridgingAndroid.Init(this); } } ================================================ FILE: deployment.pri ================================================ android-no-sdk { target.path = /data/user/qt export(target.path) INSTALLS += target } else:android { QT += androidextras x86 { target.path = /libs/x86 } else: armeabi-v7a { target.path = /libs/armeabi-v7a } else { target.path = /libs/armeabi } export(target.path) INSTALLS += target } else:unix { isEmpty(target.path) { qnx { target.path = /tmp/$${TARGET}/bin } else { target.path = /opt/$${TARGET}/bin } export(target.path) } INSTALLS += target } win32 { RESOURCES += \ desktop.qrc } export(INSTALLS) ================================================ FILE: desktop.qrc ================================================ qml/WellChat/Sparrow/resources/NotoSansHans-DemiLight.otf ================================================ FILE: doc/weixin-ui-analyse.md ================================================ # 微信界面剖析 微信界面剖析分为:首界面,单个界面,字体大小,素材等。 ## 首界面 可以先查看[简单易用的页面栈框架](readme.md)。 先看看微信的首界面。有四个分页,`BottomBar` 可以显示当前页面的以及进行切换。`TopBar` 显示微信的应用名称和两个按钮。所以首页面的实现可以使用一个横向的 `ListView` 来实现,内部有四个不同的页面,使用 `VisualItemModel` 进行加载。 ![weixin-ui-analyse-01](images/weixin-ui-analyse-01.png) 现在取其中的聊天界面为例子。 ![weixin-ui-analyse-02](images/weixin-ui-analyse-02.png) 在点开一个联系人进行聊天时,会将一个聊天页面压入页面栈。 ![weixin-ui-analyse-03](images/weixin-ui-analyse-03.png) 先不管聊天界面是如何实现的。就讲讲页面压栈要注意的问题。 先回到首界面的 `Chats`。里面有若干个联系人,在点击某一个联系人之后,触发一个函数,将聊天页面压入栈。一般是由 `MouseArea` 触发 `clicked` 信号,然后触发页面入栈的函数。 问题来了,由于页面入栈的实现问题,`StackView` 将一个页面推入栈顶是需要时间的,所以 `StackView` 会产生一个过渡,来提高用户体验,如果你的手速够快的话,双击某个联系人,会不会触发两次点击事件呢?答案是,会的。所以在触发页面入栈的函数中应该添加一个处理语句,在当前页面 A 要将页面 B 入栈时,在处理函数中添加一句 `A.enable = false;`,在 B 页面弹出后,设置 `A.enable = true;`。就不会因为点击过快,触发两次函数,向页面栈压入两个页面了。 > ps: 后来在微信上,拼手速,以极快速度点击**设置**,确实会压入两个或两个以上的**设置**页面。不同安卓机,有不同响应,版本为 **6.2.4**。看来是通病啊。在当前页面压入另一个页面时,是需要时间的,在这个处理过程中,当前页面不做屏蔽处理的话,是十分麻烦的。当然,这种误操作只会有程序猿才能发现。 > 这个 `enable` 属性为真时,允许处理键盘和鼠标事件。详细查看 `Item::enable`。 ## 字体大小 字体大小,以及安卓各个硬件上差异,进行适配就有些问题了。如何进行字体大小适配? 直接查看 [Sparrow](https://github.com/GDPURJYFS/Sparrow) 中 `UI.js` 的字体设置大小。使用了 `pointSize` 这个计量单位可以很好的工作在不同屏幕下。可以按照自己需求改动 `UI.js` 中的字体大小值。 ## 单个界面 要注意微信在各个屏幕分辨率下,每个可选项的高度,其实是根据字体大小来决定的。例如上下的留白是根据字体的高度。 ## 素材 素材获取来源互联网,所有权归腾讯公司所有。 ## Model/View 随着研究的深入,我发现,QtQuick 本身提供的 ListModel 不能很好的适应类似于微信这样的界面业务。必须要从 `C++` 中重写对应的逻辑模型。 **TODO** --- > 查看 [Sparrow 框架](https://github.com/GDPURJYFS/Sparrow) > 查看[一周 app 计划](https://github.com/GDPURJYFS/A-week-to-develop-android-app-plan)可以了解到更多的安卓开发的问题。 ================================================ FILE: main.cpp ================================================ /*! * Activity 先于 Qt 加载 * 1. 在 Activity OnCreate 中调用 QtBridgingAndroid::Init,然后进入Qt::main * 2. 在 Qt::main 中注册 Java 的 native 函数 QtBridgingAndroid::notifiedKeyboardRectangle * 3. 在 Qt::main 通过调用 Java::QtBridgingAndroid::listenKeyboardHeight 注入监听键盘事件 * 4. 在 Qt::main 加载 QML。 */ #include #include #include #include #include "Sparrow/qtbridgingandroid.h" #include "Sparrow/keyboard.h" #include "src/wellchat/collectionsmodel.h" int main(int argc, char *argv[]) { //! [java register native function] #ifdef Q_OS_ANDROID qDebug() << "QtNative::registerNativeMethod : " << QtBridgingAndroid::registerNativeMethodForJava(); #endif //! [java register native function] QApplication app(argc, argv); //! [0] app.setApplicationName("WellChat"); app.setOrganizationDomain("github.com/GDPURJYFS"); app.setOrganizationName("GDPURJYFS"); app.setApplicationVersion("0.0.1"); //! [0] QQmlApplicationEngine engine; //! [2] register qml type qmlRegisterType("WellChat", 1, 0, "CollectionsModel"); //! [2] //! [3] //! import path or imoprt plugin engine.addImportPath("qrc:/qml/WellChat"); //! [3] //! [4] //! load qml file engine.load(QUrl(QStringLiteral("qrc:/qml/WellChat/main.qml"))); //! [4] //! [5] QtBridgingAndroid *BridgingAndroid = new QtBridgingAndroid(&engine); QQmlContext *context = engine.rootContext(); context->setContextProperty("BridgingAndroid", BridgingAndroid); #ifdef Q_OS_ANDROID //! [1] 向java安装事件监听,需要在 QApplication 示例化之后 QtBridgingAndroid::installListener(); // QQmlEngine: Illegal attempt to connect to Keyboard(0xe20036a0) // that is in a different thread than the QML engine // QQmlApplicationEngine(0xe0fa1924. //! [1] #endif context->setContextProperty("Keyboard", Keyboard::singleton()); //! [5] return app.exec(); } ================================================ FILE: qml/WellChat/BussinessPage/Chat/ChatPage.qml ================================================ import Resource 1.0 as R import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Window 2.0 import QtQuick.Layouts 1.1 import QtQuick.Dialogs 1.2 import Sparrow 1.0 import Sparrow.PopupLayer 1.0 import "./Tuling123.js" as Tuling123 import "../../Component" Page { id: chatPage property string username focus: true Keys.onBackPressed: { event.accepted = true; stackView.pop(); Qt.inputMethod.hide(); } topBar: TopBar { id: topBar RowLayout { anchors.fill: parent spacing: 10 Item { width: topBar.height - 2; height: width } SampleIcon { iconSize: Qt.size( topBar.height - 2, topBar.height - 2) anchors.verticalCenter: parent.verticalCenter iconSource: R.R.activeIconBack // iconSource: constant.backActiveIcon onClicked: { Qt.inputMethod.hide(); stackView.pop(); } Separator { color: "black" anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter } } } // such as menuBar Row { parent: topBar anchors.left: parent.left anchors.leftMargin: (topBar.height - 2) * 1.5 anchors.fill: parent SampleLabel { text: username // Layout.alignment: Qt.AlignRight color: "white" anchors.verticalCenter: parent.verticalCenter } SampleIcon { iconSource: R.R.labelIconSettings iconSize: Qt.size( topBar.height - 2, topBar.height - 2) anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right onClicked: { if(Qt.inputMethod.visible === false) { inputYourNicoName.changeYourNicoName() } else { Qt.inputMethod.visible = false; inputYourNicoName.changeYourNicoName() } } } } } /////////////////////////////////////////////////////////////////////////////////// property string chatContentBuffer: "" Lazy { id: lazy } property int readKeyboardHeight: Keyboard.keyboardRectangle.height onReadKeyboardHeightChanged: { // console.log("onReadKeyboardHeightChanged, readKeyboardHeight:", // readKeyboardHeight); if(readKeyboardHeight != 0) { keyboardHeight = Keyboard.keyboardRectangle.height; // console.log("onReadKeyboardHeightChanged, keyboardHeight:", // keyboardHeight) } } property int keyboardHeight: 0 onKeyboardHeightChanged: { if(Qt.platform.os === "android") { if(keyboardHeight != 0) { upAnimation.to = keyboardHeight; upAnimation.start(); } } } NumberAnimation { id: upAnimation target: chatPage.bottomBarArea duration: 50 from: 0 properties: "anchors.bottomMargin" } function inputMethodShowHelper() { // 如果是安卓 if(Qt.platform.os === "android") { // 第一次打开 if(keyboardHeight == 0) { Qt.inputMethod.visibleChanged.connect(function() { if(Qt.inputMethod.visible) { Qt.inputMethod.visibleChanged.disconnect(arguments.callee); lazy.startCallback(50, function() { // 动态生成TextArea loader_input.sourceComponent = component_input; loader_input.item.focus = true; }); } }); Qt.inputMethod.show(); } else { upAnimation.to = keyboardHeight; upAnimation.start(); lazy.startCallback(50, function() { // 动态生成TextArea loader_input.sourceComponent = component_input; Qt.inputMethod.show(); }); } } else { loader_input.sourceComponent = component_input; loader_input.item.focus = true; } } signal keyboardOpen() onKeyboardOpen: { if(Qt.platform.os === "android") { try { if(!Keyboard.visible) { // 关闭键盘 loader_input.sourceComponent = undefined; upAnimation.to = 0; upAnimation.start(); } } catch(e) { console.log(e) } } } Component.onCompleted: { Qt.inputMethod.visibleChanged.connect(keyboardOpen); } bottomBar: BottomBar { id: bottombar focus: true RowLayout { focus: true anchors.fill: parent spacing: 5 IconButton { width: topBar.height - 2 height: topBar.height - 2 activeIconSource: R.R.activeIconSound inactiveIconSource: R.R.inactiveIconSound } Item { focus: true Layout.fillWidth: true implicitHeight: topBar.height IconButton { width: topBar.height * 0.9 height: topBar.height * 0.9 activeIconSource: R.R.activeIconEmoticon inactiveIconSource: R.R.inactiveIconEmoticon anchors.bottom: parent.bottom anchors.right: parent.right anchors.rightMargin: 10 } Rectangle { FontMetrics { id: fontMetrics font.family: GeneralSettings.generalfontFamily font.pointSize: GeneralSettings.generalFontPointSize font.weight: Font.Thin font.bold: false } width: parent.width - 10 anchors.horizontalCenter: parent.horizontalCenter height: 1 color: parent.focus? "#71d01d" : "#ccc" anchors.bottom: parent.bottom anchors.bottomMargin: fontMetrics.height * 0.2 } Component { id: component_input // Sample TextArea { id: input //readOnly: true focus: true font.family: GeneralSettings.generalfontFamily font.pointSize: GeneralSettings.generalFontPointSize wrapMode: TextEdit.Wrap backgroundVisible: false Component.onDestruction: { chatContentBuffer = input.text; } Component.onCompleted: { input.text = chatContentBuffer; input.cursorPosition = input.length; } // textFormat: TextEdit.RichText width: parent.width implicitHeight: { if(lineCount >= 2) { (topBar.height - 2) * 2 } else { (topBar.height- 2) * lineCount } } // Tracker { } } } Loader { id: loader_input focus: true anchors.fill: parent onLoaded: { loader_input.item.focus = true; loader_input.item.forceActiveFocus(); } MouseArea { anchors.fill: parent visible: { if(loader_input.item) { return loader_input.item.readOnly } else { return true; } } onClicked: inputMethodShowHelper(); // 当关闭输入框的时候 // 用来显示上次输入的文本 SampleLabel { width: parent.width elide: Text.ElideRight text: chatContentBuffer verticalAlignment: Text.AlignVCenter anchors.verticalCenter: parent.verticalCenter } } } } SampleButton { id: sendButton width: topBar.height * 0.9 height: topBar.height * 0.9 Layout.alignment: Qt.AlignRight text: qsTr("Send") onClicked: { __sendHelp(); } } Item { width: 5; height: 5 } } } /* states: [ // State { // name: "FixTopBar" // PropertyChanges { // target: chatPage.topBarArea // anchors.topMargin: try { // return Keyboard.keyboardRectangle.height; // } catch(e) { // return 0; // } // } // } State { name: "FixBottomBar" PropertyChanges { target: chatPage.bottomBarArea anchors.bottomMargin: keyboardHeight } } ] transitions: [ // Transition { // from: "FixTopBar" // to: "" // NumberAnimation { // property: "anchors.topMargin" // duration: 350 // } // }, // Transition { // from: "" // to: "FixTopBar" // NumberAnimation { // property: "anchors.topMargin" // duration: 350 // } // } Transition { // 回落,键盘收回 from: "FixBottomBar" to: "" SequentialAnimation { ScriptAction { script: { loader_input.sourceComponent = undefined; console.log("from FixBottomBar to '', readOnly is true"); } } NumberAnimation { property: "anchors.bottomMargin" duration: 150 } } }, Transition { from: "" to: "FixBottomBar" SequentialAnimation { NumberAnimation { property: "anchors.bottomMargin" duration: 150 } ScriptAction { script: { loader_input.sourceComponent = component_input; console.log("from '' to FixBottomBar, readOnly is false"); } } } } ] */ //////////////////////////////////////////////////////////////////////// property string userId: "垃圾君" ListView { id: view anchors.fill: parent model: chatModels delegate: Rectangle { width: view.width height: chatText.contentHeight * 1.5 color: "transparent" border.color: "black" border.width: 1 SampleLabel { id: chatText width: parent.width * 0.8 >= chatText.contentWidth ? chatText.contentWidth : parent.width * 0.8 anchors.right: ChatId != "JiJiZhaZha" ? parent.right : undefined text: ChatText verticalAlignment: Text.AlignVCenter anchors.verticalCenter: parent.verticalCenter wrapMode: Text.WrapAtWordBoundaryOrAnywhere } } ListModel { id: chatModels // ListElement { // ChatText: "这是文本" // ChatId: "JiJiZhaZha" // } // ListElement { // ChatText: "我是垃圾君" // ChatId: "垃圾君" // } } } property bool __dontFixBottomBar: false PopupLayer { id: inputYourNicoName parent: chatPage popupItem.width: chatPage.width * 0.8 popupItem.height: popupItem.width * 0.5 onStateChanged: { if(inputYourNicoName.state == "Hide") { __dontFixBottomBar = false; } else { __dontFixBottomBar = true; } } RowLayout { anchors.fill: parent anchors.margins: parent.width * 0.05 SampleTextField { id: getNicoName Layout.fillWidth: true } SampleButton { text: qsTr("OK") onClicked: { chatPage.userId = getNicoName.text; inputYourNicoName.close(); Qt.inputMethod.hide(); } } } function changeYourNicoName () { getNicoName.text = chatPage.userId; inputYourNicoName.open(); } } function tryToNotify(notifiString) { try { console.log("will send", notifiString); BridgingAndroid.sendNotification(notifiString); }catch(e) { console.log(e) } } onApplicationStateChanged: { if(applicationState == Qt.ApplicationInactive || applicationState == Qt.ApplicationSuspended || applicationState == Qt.ApplicationHidden) { Qt.inputMethod.hide(); } } function __sendHelp() { if(loader_input.item.text !== "" ) { chatModels.append({ "ChatText": loader_input.item.text, "ChatId": userId }); view.positionViewAtIndex(view.count-1, ListView.End ); Tuling123.sendTextToTuling123(loader_input.item.text, userId, function(responseText){ chatModels .append( { "ChatText": responseText, "ChatId": "JiJiZhaZha" }); view.positionViewAtIndex(view.count-1, ListView.End ); }, Tuling123.errorCodeHandle ); loader_input.item.text = ""; } } } ================================================ FILE: qml/WellChat/BussinessPage/Chat/Heartbeat.qml ================================================ import QtQuick 2.0 QtObject { id: heartbeat property alias source: worker.source property alias interval: timer.interval property alias running: timer.running signal message(var msg) signal beat() property Timer timer: Timer { id: timer repeat: true interval: 1500 triggeredOnStart: true running: false } property WorkerScript worker: WorkerScript { id: worker } function sendMessage(msg) { worker.sendMessage(msg); } function start() { timer.start(); } function stop() { timer.stop(); } Component.onCompleted: { worker.message.connect(message); timer.triggered.connect(beat); } } ================================================ FILE: qml/WellChat/BussinessPage/Chat/Lazy.qml ================================================ import Resource 1.0 as R import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Window 2.0 import QtQuick.Layouts 1.1 import QtQuick.Dialogs 1.2 import Sparrow 1.0 import Sparrow.PopupLayer 1.0 import "../../Component" Timer { id: lazy interval: 100 running: false repeat: false property var callback onTriggered: { try { callback(); } catch(e) { } } function startCallback(time, callback) { time = time || 500; callback = callback || function() { }; interval = time; lazy.callback = callback; lazy.start(); } } ================================================ FILE: qml/WellChat/BussinessPage/Chat/Tuling123.js ================================================ .pragma library // callback(responseText) // err(errorCode) function sendTextToTuling123(text, chatUserId, callback, err) { // http://www.tuling123.com/openapi/api?key=b77b735b2797bc613e7623f4406fe342&info=你好 var host = "http://www.tuling123.com/openapi/api?"; // 这里填写你在图灵机器人网站申请的apiKey // 现在图灵机器人网站是免费申请的哦。 var apiKey = "b77b735b2797bc613e7623f4406fe342"; var info = text; var dataTemplate = { "code": 10000, // 返回的错误码 "text": "回复的内容", "url": "", // 返回单条链接 "list": [] // 返回多条信息 } var xhr = new XMLHttpRequest; xhr.onreadystatechange = function() { if(xhr.readyState == xhr.DONE) { console.log("xhr.responseText: ", xhr.responseText); try { var dataObject = JSON.parse(xhr.responseText); if(!isErrorCode(dataObject.code)) { callback(dataObject.text); } } catch(e) { console.log(e); } } } xhr.open("GET", host+ "key=" + apiKey +"&userid=" + chatUserId +"&info="+info ); xhr.send() } function isErrorCode(code) { var errorCodes = [ { "code":40001, "description":"参数key长度错误(应该是32位)" }, { "code":40002, "description":"请求内容info为空" }, { "code":40003, "description":"key错误或帐号未激活" }, { "code":40004, "description":"当天请求次数已使用完" }, { "code":40005, "description":"暂不支持所请求的功能" }, { "code":40006, "description":"图灵机器人服务器正在升级" }, { "code":40007, "description":"数据格式异常" }, ]; for(var iter in errorCodes) { if(errorCodes[iter].code === code) { return true; } } return false; } function errorCodeHandle(code) { var errorCodes = [ { "code":40001, "description":"参数key长度错误(应该是32位)" }, { "code":40002, "description":"请求内容info为空" }, { "code":40003, "description":"key错误或帐号未激活" }, { "code":40004, "description":"当天请求次数已使用完" }, { "code":40005, "description":"暂不支持所请求的功能" }, { "code":40006, "description":"图灵机器人服务器正在升级" }, { "code":40007, "description":"数据格式异常" }, ]; for(var iter in errorCodes) { if(errorCodes[iter].code === code) { console.log(errorCodes[iter].description); } } } ================================================ FILE: qml/WellChat/BussinessPage/Chat/heart.js ================================================ // .pragma library // 心跳包 // to include static singleton mess_id Qt.include("./storage.js") WorkerScript.onMessage = function(message) { var model = message.listModel; var doc = new XMLHttpRequest; doc.open("POST", "http://cnzxzc.tunnel.mobi/qyvlik/sendMess"); doc.onreadystatechange = function () { if (doc.readyState === XMLHttpRequest.DONE) { console.log("on message, model:", model); console.log("XMLHttpRequest DONE"); console.log("====轮询结果=======,mess_id:", mess_id); console.log(doc.responseText); console.log("====轮询结果======="); try { var content = JSON.parse(doc.responseText); if(content.hasOwnProperty("mess_id")) { mess_id = content.mess_id; timestamp = content.timestamp; var item = { "chatContext": content.content } model.append(item); model.sync(); } } catch(e) { console.log(e); } } } var lunxun = { "timestamp": timestamp, "room_id":"1", "mess_id":mess_id } doc.send(JSON.stringify(lunxun)); } ================================================ FILE: qml/WellChat/BussinessPage/ChatsView.qml ================================================ import BussinessPage 1.0 as BR import QtQuick 2.0 import QtQuick.Controls 1.4 import QtQuick.Window 2.0 import QtQuick.Layouts 1.1 import "../Component" import Sparrow 1.0 Page { id: chatsView title: "FriendList" color: "white" property int headPrtraitSize: 90 signal openNewChat(int userid, string username) onOpenNewChat: { if(__find(username) !== null) { // go to the chat page __LoadChatPage(userid, name); } else { chatItemsModel.append( { "chatTime": (new Date).toDateString(), "chatName":username, "chatisBool": false, "chatContext":userid }); // go to the chat page __LoadChatPage(userid, username); } } ListView { id: listView width: chatsView.width height: chatsView.height model: chatItemsModel highlightMoveDuration: 1000 highlightRangeMode: ListView.ApplyRange // 控制滚动速度 maximumFlickVelocity: 5000 ///////////////////////////////////////////////////////////////////////////////////////// /* highlightRangeMode: ListView.StrictlyEnforceRange readonly property alias topSideBarIsOpen: listView.__topSideBarIsOpen property bool __topSideBarIsOpen: false onAtYBeginningChanged: { try { if(listView.atYBeginning) { console.log(-listView.contentY, listView.headerItem.height) if( -contentY > listView.headerItem.height) { listView.__topSideBarIsOpen = true; } } else { listView.__topSideBarIsOpen = false; } } catch(e) { } } onTopSideBarIsOpenChanged: { if(listView.topSideBarIsOpen) { listView.highlightRangeMode = ListView.ApplyRange // 焦点Item允许停止的位置 } else { listView.highlightRangeMode = ListView.StrictlyEnforceRange } } header: Rectangle { id: headerItem width: listView.width; height: listView.height color: "black" } //*/ ///////////////////////////////////////////////////////////////////////////////////////// add: Transition { NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 400 } NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 400 } } move: Transition { NumberAnimation { properties: "x,y"; duration: 800; easing.type: Easing.OutBack } } displaced: Transition { NumberAnimation { properties: "x,y"; duration: 400; easing.type: Easing.OutBounce } } states: [ State { name: "ShowBar" when: listView.movingVertically PropertyChanges { target: verticalScrollBar; opacity: 1 } }, State { name: "HideBar" when: !listView.movingVertically PropertyChanges { target: verticalScrollBar; opacity: 0 } } ] transitions: [ Transition { from: "ShowBar" to: "HideBar" NumberAnimation { properties: "opacity"; duration: 400 } }, Transition { from: "HideBar" to: "ShowBar" NumberAnimation { properties: "opacity"; duration: 400 } } ] ScrollBar { id: verticalScrollBar width: 10 * Screen.devicePixelRatio height: listView.height - width anchors.right: listView.right orientation: Qt.Vertical position: listView.visibleArea.yPosition pageSize: listView.visibleArea.heightRatio } delegate: Rectangle { id: chatItem // chatItem property int chatItemHeight: chatItemRowLayout.height width: chatsView.width height: chatItem.chatItemHeight color: "transparent" border.width: 1 border.color: "#ccc" state: "UnSelected" states: [ State { name: "Selected" PropertyChanges { target:chatItem; color: "#ccc" } }, State { name: "UnSelected" PropertyChanges { target:chatItem; color: "transparent" } } ] transitions: [ Transition { from: "Selected" to: "UnSelected" NumberAnimation { properties: "color"; duration: 400 } }, Transition { from: "UnSelected" to: "Selected" NumberAnimation { properties: "color"; duration: 400 } } ] RowLayout { id: chatItemRowLayout width: parent.width height: (chatItemName.contentHeight+chatItemContext.contentHeight) * 1.5 anchors.margins: spacing Image { width: chatItem.chatItemHeight height: chatItem.chatItemHeight anchors.verticalCenter: parent.verticalCenter sourceSize.width: chatItem.chatItemHeight - 2 sourceSize.height: chatItem.chatItemHeight - 2 source: "../Resource/tests/tests001.jpg" fillMode: Image.PreserveAspectFit } ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true RowLayout { Layout.fillHeight: true Layout.fillWidth: true SampleLabel { id: chatItemName; Layout.fillWidth: true; text: name } SampleLabel { id: chatItemTime; Layout.fillWidth: true; text: chatTime } } RowLayout { Layout.fillHeight: true Layout.fillWidth: true SampleLabel { id: chatItemContext; Layout.fillWidth: true; text: chatContext } SampleLabel { id: chatItemisBool;Layout.fillWidth: true; text: chatisBool } } } } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onPressAndHold: { chatItem.state = "Selected"; chatItemMenu.aboutToHide.connect(function(){ chatItemMenu.aboutToHide.disconnect(arguments.callee); chatItem.state = "UnSelected"; }); chatItemMenu.aboutToShow.connect(function(){ chatItemMenu.aboutToShow.disconnect(arguments.callee); chatItem.state = "Selected"; }); chatItemMenu.chatItemIndex = index; chatItemMenu.popup(); } onClicked: { __LoadChatPage(1, name); } } } Menu { id: chatItemMenu property int chatItemIndex: 0 MenuItem { text: qsTr("Delete conversation") onTriggered: { listView.model.remove(chatItemMenu.chatItemIndex); } } MenuItem { text: qsTr("Sticky on top") onTriggered: { listView.model.move(chatItemMenu.chatItemIndex, 0, 1); } } MenuItem { text: qsTr("Clear") onTriggered: { listView.model.clear(); } } } ListModel { id: chatItemsModel // ListElement { // username: "Apple" // chatContext: "2333" // chatTime: 2.45 // chatisBool: true // } /* 联系人名字 聊天记录 时间 是否 */ Component.onCompleted: { for(var i=0; i<1; i++) { chatItemsModel .append( { "chatTime": i, "name":"忍野忍", "chatisBool": false, "chatContext":"233" + i }); } } } } function __find(name) { if(chatItemsModel.count == 0) return null; var chatItemsCount = chatItemsModel.count; for(var i=0; i [A, B, D] - "replace" transition between C and D function __PushPage(url, properties, replace){ var component = Qt.createComponent(url); properties = properties || { }; replace = replace || false; try { if(component.status === Component.Ready) { // 防止点击过快,开启过多画面 page.enabled = false; properties.focus = true; properties.width = Qt.binding(function(){ return stackView.width }); properties.height = Qt.binding(function(){ return stackView.height }); properties.stackView = page.stackView; var loadPage = component.createObject(page.stackView, properties); loadPage.exited.connect(function() { loadPage.exited.disconnect(arguments.callee); page.enabled = true; // 防止焦点丢失 page.focus = true; }); //! push(D, replace) => [A, B, D] - "replace" transition between C and D stackView.push({item: loadPage , destroyOnPop:true // 不能用。 界面会僵死 // , replace: replace }); return loadPage; } else { console.log("component errorString: ",component.errorString()); page.enabled = true; page.focus = true; return null; } } catch(e) { console.log("Error: ",e); console.log("component errorString: ",component.errorString()); page.enabled = true; page.focus = true; return null; } } Component.onCompleted: { entered(); } Component.onDestruction: exited(); } ================================================ FILE: qml/WellChat/Sparrow/PageStackWindow.qml ================================================ import QtQuick 2.0 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.3 ApplicationWindow { id: pageStackWindow width: 360 height: 640 visible: true color: "#ebebeb" style: ApplicationWindowStyle { background: Rectangle { width: pageStackWindow.width height: pageStackWindow.height color: pageStackWindow.color } } property alias stackView: __stackView property alias initialPage: __stackView.initialItem property alias focus: __stackView.focus readonly property alias currentItem: __stackView.currentItem readonly property alias depth: __stackView.depth readonly property alias busy: __stackView.busy StackView { id: __stackView anchors.fill: parent focus: visible } onInitialPageChanged: { if(initialPage.stackView === null) { initialPage.stackView = pageStackWindow.stackView } } function clear() { __stackView.clear(); } function push(item) { return __stackView.push(item); } function pop(item) { return __stackView.pop(item); } } ================================================ FILE: qml/WellChat/Sparrow/PopupLayer/Delegate/PopupLayerBottomMenuDelegate.qml ================================================ //~ PopupLayerDialogDelegate // internal import // don't use the module import import "../" import QtQuick 2.0 PopupLayerDelegate { showTransition: PopupLayerTransition { SequentialAnimation { NumberAnimation { easing.overshoot: 5 easing.type: Easing.OutBack properties: "opacity" duration: 50 } AnchorAnimation { duration: 250 } } } hideTransition: PopupLayerTransition { SequentialAnimation { AnchorAnimation { duration: 250 } NumberAnimation { easing.overshoot: 5 easing.type: Easing.OutBack properties: "rotation,opacity" duration: 50 } } } hideChanges: [ AnchorChanges { target: popupItem anchors.top: maskItem.bottom anchors.horizontalCenter: maskItem.horizontalCenter }, PropertyChanges { target: maskItem opacity: 0 } ] showChanges: [ AnchorChanges { target: popupItem anchors.bottom: maskItem.bottom anchors.horizontalCenter: maskItem.horizontalCenter }, PropertyChanges { target: maskItem opacity: 1 } ] } ================================================ FILE: qml/WellChat/Sparrow/PopupLayer/Delegate/PopupLayerDialogDelegate.qml ================================================ //~ PopupLayerDialogDelegate // internal import // don't use the module import import "../" import QtQuick 2.0 PopupLayerDelegate { showTransition: PopupLayerTransition { SequentialAnimation { NumberAnimation { easing.overshoot: 5 easing.type: Easing.OutBack properties: "opacity" duration: 50 } AnchorAnimation { duration: 250 } PauseAnimation { duration: 50 } NumberAnimation { easing.overshoot: 5 easing.type: Easing.OutBack properties: "rotation,anchors.bottomMargin" duration: 150 } } } hideTransition: PopupLayerTransition { SequentialAnimation { NumberAnimation { easing.overshoot: 5 easing.type: Easing.OutBack properties: "anchors.bottomMargin" duration: 150 } AnchorAnimation { duration: 250 } NumberAnimation { easing.overshoot: 5 easing.type: Easing.OutBack properties: "rotation,opacity" duration: 50 } } } hideChanges: [ AnchorChanges { target: popupItem anchors.bottom: maskItem.top anchors.horizontalCenter: maskItem.horizontalCenter }, PropertyChanges { target: popupItem anchors.bottomMargin: Math.sin(Math.PI*2/360*10) * (popupItem.width/2) rotation: 10 }, PropertyChanges { target: maskItem opacity: 0 } ] showChanges: [ AnchorChanges { target: popupItem anchors.verticalCenter: maskItem.verticalCenter anchors.horizontalCenter: maskItem.horizontalCenter }, PropertyChanges { target: popupItem anchors.bottomMargin: Math.sin(Math.PI*2/360*10) * (popupItem.width/2) rotation: 0 }, PropertyChanges { target: maskItem opacity: 1 } ] } ================================================ FILE: qml/WellChat/Sparrow/PopupLayer/Delegate/PopupLayerSideMenuDelegate.qml ================================================ //~ PopupLayerSideMenuDelegate // internal import // don't use the module import import "../" import QtQuick 2.0 PopupLayerDelegate { showTransition: PopupLayerTransition { SequentialAnimation { NumberAnimation { easing.overshoot: 5 easing.type: Easing.OutBack properties: "opacity" duration: 150 } NumberAnimation { easing.overshoot: 5 properties: "x" duration: 150 } } } hideTransition: PopupLayerTransition { SequentialAnimation { NumberAnimation { easing.overshoot: 5 properties: "x" duration: 150 } NumberAnimation { easing.overshoot: 5 easing.type: Easing.OutBack properties: "opacity" duration: 100 } } } hideChanges: [ PropertyChanges { target: popupItem x: -popupItem.width }, PropertyChanges { target: maskItem opacity: 0 } ] showChanges: [ PropertyChanges { target: popupItem x: 0 }, PropertyChanges { target: maskItem opacity: 1 } ] } ================================================ FILE: qml/WellChat/Sparrow/PopupLayer/Delegate/qmldir ================================================ module PopupLayer.Delegate PopupLayerDialogDelegate 1.0 ./PopupLayerDialogDelegate.qml PopupLayerSideMenuDelegate 1.0 ./PopupLayerSideMenuDelegate.qml PopupLayerBottomMenuDelegate 1.0 ./PopupLayerBottomMenuDelegate.qml ================================================ FILE: qml/WellChat/Sparrow/PopupLayer/PopupLayer.qml ================================================ //! Qt 5.0 or new // internal import // don't use the module import import "./Delegate" as Delegate import QtQuick 2.0 Rectangle { // Mask id: layer anchors.fill: parent visible: opacity != 0 color: "#aa000000" property alias maskColor: layer.color readonly property alias popupItem: internalPopupItem default property alias data: internalPopupItem.data MouseArea { anchors.fill: parent onClicked: { layer.close() // _switch(); } } Rectangle { id: internalPopupItem width: parent.width * 0.7 height: internalPopupItem.width * 0.75 MouseArea { anchors.fill: parent; onClicked: { } } } state: "Hide" states: [ State { name: "Hide" changes: delegate.hideChanges }, State { name: "Show" changes: delegate.showChanges } ] property PopupLayerDelegate delegate: Delegate.PopupLayerDialogDelegate { popupItem: internalPopupItem maskItem: layer } transitions: [ Transition { id: hide_to_show from: "Hide" to: "Show" animations: delegate.showTransition.animaitons }, Transition { id: show_to_hide from: "Show" to: "Hide" animations: delegate.hideTransition.animaitons } ] function open() { if(layer.state == "Hide") { layer.state = "Show"; } } function close() { layer.state = "Hide"; } function switchState() { if(layer.state == "Hide") { layer.state = "Show"; return layer.state; } else { layer.state = "Hide"; return layer.state; } } function _switch() { if(layer.state == "Hide") { layer.state = "Show"; } else { layer.state = "Hide"; } } } ================================================ FILE: qml/WellChat/Sparrow/PopupLayer/PopupLayerDelegate.qml ================================================ //~ PopupLayerDelegate import QtQuick 2.0 QtObject { id: popupLayerDelegate property PopupLayerTransition showTransition: null property PopupLayerTransition hideTransition: null property Item popupItem: null property Item maskItem: null property list showChanges property list hideChanges } ================================================ FILE: qml/WellChat/Sparrow/PopupLayer/PopupLayerTransition.qml ================================================ import QtQuick 2.0 QtObject { id: popupLayerTransition default property alias animaitons: popupLayerTransition.__animaions // Animation is an abstract class // property list __animaions property list __animaions } ================================================ FILE: qml/WellChat/Sparrow/PopupLayer/qmldir ================================================ module PopupLayer PopupLayer 1.0 ./PopupLayer.qml PopupLayerDelegate 1.0 ./PopupLayerDelegate.qml PopupLayerTransition 1.0 ./PopupLayerTransition.qml ================================================ FILE: qml/WellChat/Sparrow/PopupLayer/readme.md ================================================ # PopupLayer 使用 QML 实现的弹出层。 使用注意事项。 1. `PopupLayer` 内部有个 `popupItem`,是作为这个控件内部孩子的默认父指针。 2. `PopupLayer` 默认覆盖他的 `parent`(可以显示指定),但是在内部没有直接指定其 `z` 序。在使用的时候,需要注意其实例化的位置。(QML 文档实例化可视对象时,从上往下实例化,位于最下面的可视化对象会在顶层显示,可以通过设定 `z` 序来处理)。 3. `PopupLayer` 的 `delegate` 属性是仿制 `StackViewDelegate` 的机制。提供了一个 `PopupLayerDelegate`。 4. `PopupLayerDelegate` 声明如下: ``` //~ PopupLayerDelegate import QtQuick 2.0 QtObject { id: popupLayerDelegate property PopupLayerTransition showTransition: null property PopupLayerTransition hideTransition: null property Item popupItem: null property Item maskItem: null property list showChanges property list hideChanges } ``` 声明了需要捕获的 `popupItem` 和 `maskItem`。 声明了两个改变列表。 声明了两个 `PopupLayerTransition` 接口,在这个 `PopupLayerTransition` 接口中实现必要的动画。 `PopupLayerTransition` 声明如下 ``` import QtQuick 2.0 QtObject { id: popupLayerTransition default property alias animaitons: popupLayerTransition.__animaions // Animation is an abstract class // property list __animaions property list __animaions } ``` 5. 具体实现案例 ``` //~ PopupLayerSideMenuDelegate // internal import // don't use the module import import "../" import QtQuick 2.0 PopupLayerDelegate { showTransition: PopupLayerTransition { SequentialAnimation { NumberAnimation { easing.overshoot: 5 easing.type: Easing.OutBack properties: "opacity" duration: 150 } NumberAnimation { easing.overshoot: 5 properties: "x" duration: 150 } } } hideTransition: PopupLayerTransition { SequentialAnimation { NumberAnimation { easing.overshoot: 5 properties: "x" duration: 150 } NumberAnimation { easing.overshoot: 5 easing.type: Easing.OutBack properties: "opacity" duration: 100 } } } hideChanges: [ PropertyChanges { target: popupItem x: -popupItem.width }, PropertyChanges { target: maskItem opacity: 0 } ] showChanges: [ PropertyChanges { target: popupItem x: 0 }, PropertyChanges { target: maskItem opacity: 1 } ] } ``` 6. 如何使用 ``` PopupLayer{ id: optionsMenu popupItem.width: page.width popupItem.height: page.width < page.height ? page.width * 0.5 : page.height * 0.3 delegate: PopupLayerBottomMenuDelegate { // 必须指定 popupItem 和 maskItem popupItem: optionsMenu.popupItem maskItem: optionsMenu } Item { anchors.fill: parent Button { anchors.centenIn: parent text: "close" onClicked: { optionsMenu.close(); } } } } ``` 7. 在 QML 实现动画的总结 1. 确定要对谁实施动画。(target)。 2. 写状态,当 target 处于某状态时,其属性的值。 3. 写各个状态的过度。 4. 完善过度中的动画。 ================================================ FILE: qml/WellChat/Sparrow/QObject.qml ================================================ import QtQuick 2.0 QtObject { id: qObject default property alias data: qObject.__data property list __data } ================================================ FILE: qml/WellChat/Sparrow/SampleButton.qml ================================================ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.0 import Sparrow 1.0 Button { id: button property color buttonColor: "#45c01a" readonly property alias pressed: mouseArea.pressed readonly property alias hovered: mouseArea.hovered style: ButtonStyle { background: Rectangle{ anchors.fill: parent radius: button.height * 0.1 color: buttonColor } label:Rectangle { color: "transparent" implicitWidth: buttonText.implicitWidth * 1.5 implicitHeight: buttonText.implicitHeight * 2.0 baselineOffset: buttonText.y + buttonText.y + buttonText.baselineOffset SampleLabel { id:buttonText anchors.centerIn: parent text: button.text horizontalAlignment: Text.AlignHCenter color:"white"; } } } MouseArea { id: mouseArea property bool pressed: false property bool hovered: false hoverEnabled: true anchors.fill: parent onEntered: hovered = true; onExited: hovered = false; onReleased: pressed = false; onPressed: { pressed = true; shaderEffect.touchStart(mouse.x, mouse.y); } onClicked: lazyClicked.start(); } Timer { id: lazyClicked interval: 100 onTriggered: { button.clicked(); } } ClickedShaderEffect { id: shaderEffect anchors.fill: parent } } ================================================ FILE: qml/WellChat/Sparrow/SampleIcon.qml ================================================ import QtQuick 2.5 import QtQuick.Layouts 1.1 import Sparrow 1.0 Item { id: icon property alias backgroundColor: clickedShaderEffect.backgroundColor property alias spreadColor: clickedShaderEffect.spreadColor signal clicked() height: iconSize.height width: iconSize.width property size iconSize: Qt.size(33, 33) property alias iconSource: iconImage.source clip: true Image { id: iconImage sourceSize: iconSize anchors.centerIn: parent fillMode: Image.PreserveAspectFit horizontalAlignment: Image.AlignHCenter verticalAlignment: Image.AlignHCenter } MouseArea { anchors.fill: parent onClicked: icon.clicked() onPressed: clickedShaderEffect.touchStart(mouse.x, mouse.y) } ClickedShaderEffect { id: clickedShaderEffect anchors.fill: parent backgroundColor: "#10ffffff" spreadColor: "#50ffffff" } } ================================================ FILE: qml/WellChat/Sparrow/SampleLabel.qml ================================================ import QtQuick 2.0 import QtQuick.Controls 1.2 import Sparrow 1.0 Label { font.family: GeneralSettings.generalfontFamily font.pointSize: GeneralSettings.generalFontPointSize } ================================================ FILE: qml/WellChat/Sparrow/SampleTextField.qml ================================================ import Sparrow 1.0 import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 TextField { id: textField font.family: GeneralSettings.generalfontFamily font.pointSize: GeneralSettings.generalFontPointSize // By a hexadecimal triplet or quad in the form "#RRGGBB" and "#AARRGGBB" respectively. // For example, the color red corresponds to a triplet of "#FF0000" // and a slightly transparent blue to a quad of "#800000FF". property color backgroundColor: "#80ffffff" style: TextFieldStyle { id: style textColor: "black" renderType: Text.NativeRendering background: Rectangle { radius: 2 // a r g b color: backgroundColor border.color: "#333" border.width: style.control.activeFocus ? 3 : 2 } } implicitHeight: fontMetrics.height * 2.0 FontMetrics { id: fontMetrics font: textField.font } } ================================================ FILE: qml/WellChat/Sparrow/TopBar.qml ================================================ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.1 ToolBar { id: toolBar // #FAFAFA property color backgroundColor: "#22292c" property var inline: ToolBar { id: i visible: false } style: ToolBarStyle { padding { left: 0 right: 0 top: 0 bottom: 0 } background: Rectangle { color: backgroundColor implicitWidth: if(isDesktop()) { i.width * 1.2 } else { i.width } implicitHeight: if(isDesktop()) { i.height * 1.2 } else { i.height } } } // [Fix Bug] when a RowLayout anchors.fill TopBar // file:///C:/Qt/Qt5.5.1/5.5/mingw492_32/qml/QtQuick/Controls/ToolBar.qml:142:9: QML Item: Binding loop detected for property "layoutWidth" // file:///C:/Qt/Qt5.5.1/5.5/mingw492_32/qml/QtQuick/Controls/ToolBar.qml:142:9: QML Item: Binding loop detected for property "layoutHeight" Item { anchors.fill: parent } function isDesktop () { var os = Qt.platform.os; if(os === "windows" || os === "osx" || os === "unix" || os === "wince" || os === "winrt" ) { return true; } else { return false; } } } ================================================ FILE: qml/WellChat/Sparrow/Tracker.qml ================================================ import QtQuick 2.0 Rectangle { anchors.fill: parent color: "green" opacity: 0.5 // color: "transparent" // border.width: 1 // border.color: "green" } ================================================ FILE: qml/WellChat/Sparrow/UI.js ================================================ .pragma library // console.log("Window UI Config"); var defaultFontFamily = "Microsoft YaHei UI"; // 微软雅黑 //var defaultNormalFontPointSize = 9; var SmallFontPointSize = 12; var StandardFontPointSize = 13; var BigFontPointSize = 15; var LargeFontPointSize = 16; var HugeFontPointSize = 18; var gridViewBufferBlock = 50; // icon 72 x 72 // 80 x 80 ================================================ FILE: qml/WellChat/Sparrow/WebPage.qml ================================================ import QtQuick 2.0 import QtQuick.Controls 1.2 import QtWebKit 3.0 import Sparrow 1.0 /* canGoBack : bool canGoForward : bool loadProgress : int loading : bool title : string url : url don't use the signal void goBack() void goForward() void loadHtml(string html, url baseUrl, url unreachableUrl) void reload() void stop() void runJavaScript(script, callback) */ Page { id: webPage readonly property alias canGoBack: webView.canGoBack readonly property alias canGoForward: webView.canGoForward readonly property alias loadProgress: webView.loadProgress readonly property alias loading: webView.loading title: webView.title property alias url: webView.url //@disable-check M324 WebView { id: webView anchors.fill: parent } function goBack() { webView.goBack(); } function goForward() { webView.goForward(); } function loadHtml(html, baseUrl, unreachableUrl){ webView.loadHtml(html, baseUrl, unreachableUrl); } function runJavaScript(script, callback) { throw "QtWebView 1.0 not install"; } function reload() { webView.reload(); } function stop() { webView.stop(); } } ================================================ FILE: qml/WellChat/Sparrow/qmldir ================================================ # version 1.0 module Sparrow UI 1.0 ./UI.js singleton GeneralSettings 1.0 ./GeneralSettings.qml QObject 1.0 ./QObject.qml PageStackWindow 1.0 ./PageStackWindow.qml Page 1.0 ./Page.qml BottomBar 1.0 ./BottomBar.qml TopBar 1.0 ./TopBar.qml SampleIcon 1.0 ./SampleIcon.qml SampleLabel 1.0 ./SampleLabel.qml SampleButton 1.0 ./SampleButton.qml SampleTextField 1.0 ./SampleTextField.qml WebPage 1.0 ./WebPage.qml Tracker 1.0 ./Tracker.qml ClickedShaderEffect 1.0 ./ClickedShaderEffect.qml # internal QObject ./QObject.qml ================================================ FILE: qml/WellChat/Sparrow/resources/readme.md ================================================ 在安卓中,推荐字体需要大家来进行测试了。。。 [有哪些值得推荐的中文字体?](https://www.zhihu.com/question/20727176) ================================================ FILE: qml/WellChat/WellChat.qmlproject ================================================ import QmlProject 1.0 Project { /* Include .qml, .js, and image files from current directory and subdirectories */ mainFile: "main.qml" QmlFiles { directory: "." } JavaScriptFiles { directory: "." } ImageFiles { directory: "." } Files { directory: "." filter: "*.md" } Files { directory: "." filter: "*.otf" } Files { directory: "." filter: "*.ttf" } Files { directory: "." filter: "*.html" } Files { directory: "." filter:"qmldir" } /* List of plugin directories passed to QML runtime */ importPaths: [ "." ] } ================================================ FILE: qml/WellChat/main.qml ================================================ import QtQuick 2.0 import QtQuick.Controls 1.4 import Sparrow 1.0 PageStackWindow { id: mainWindow title: qsTr("WellChat") initialPage: MainView { id: mainView focus: mainWindow.focus stackView: mainWindow.stackView pageStackWindow: mainWindow width: stackView.width height: stackView.height Keys.onBackPressed: { event.accepted = true; // console.log("back") Qt.quit(); } } property int applicationState : Qt.application.state onApplicationStateChanged: { if(applicationState == Qt.ApplicationActive) { try { var topBarColor = mainView.topBar.backgroundColor; console.log("get topBarColor:", topBarColor); console.log("try to set status bar color:"); BridgingAndroid .setStatusBarColor(topBarColor); console.log("success!"); } catch(e) {console.log(e);} } } } ================================================ FILE: qml/WellChat/resource/R.qml ================================================ pragma Singleton import QtQuick 2.0 // R // Applicaton icon html ... resource QtObject { id: resource objectName: "ApplicatonResources" readonly property url testPic: Qt.resolvedUrl( "./tests/tests001.jpg") // bar icons // ./icons/bar-icons/active readonly property url activeIconBack: //icons.activeIcon("back") Qt.resolvedUrl("./icons/bar-icons/active/back.png") readonly property url inactiveIconBack: Qt.resolvedUrl("./icons/bar-icons/inactive/back.png") readonly property url activeIconChat: Qt.resolvedUrl("./icons/bar-icons/active/chat.png") readonly property url inactiveIconChat: Qt.resolvedUrl("./icons/bar-icons/inactive/chat.png") readonly property url activeIconContacts: Qt.resolvedUrl("./icons/bar-icons/active/contacts.png") readonly property url inactiveIconContacts: Qt.resolvedUrl("./icons/bar-icons/inactive/contacts.png") readonly property url activeIconDicover: Qt.resolvedUrl("./icons/bar-icons/active/dicover.png") readonly property url inactiveIconDicover: Qt.resolvedUrl("./icons/bar-icons/inactive/dicover.png") readonly property url activeIconEmoticon: Qt.resolvedUrl("./icons/bar-icons/active/emoticon.png") readonly property url inactiveIconEmoticon: Qt.resolvedUrl("./icons/bar-icons/inactive/emoticon.png") readonly property url activeIconMagnifier: Qt.resolvedUrl("./icons/bar-icons/active/magnifier.png") readonly property url inactiveIconMagnifier: Qt.resolvedUrl("./icons/bar-icons/inactive/magnifier.png") readonly property url activeIconMe: Qt.resolvedUrl("./icons/bar-icons/active/me.png") readonly property url inactiveIconMe: Qt.resolvedUrl("./icons/bar-icons/inactive/me.png") readonly property url activeIconPlus: Qt.resolvedUrl("./icons/bar-icons/active/plus.png") readonly property url inactiveIconPlus: Qt.resolvedUrl("./icons/bar-icons/inactive/plus.png") readonly property url activeIconSound: Qt.resolvedUrl("./icons/bar-icons/active/sound.png") readonly property url inactiveIconSound: Qt.resolvedUrl("./icons/bar-icons/inactive/sound.png") // label icons // ./icons/label-icons/ readonly property url labelIconDriftBottle: Qt.resolvedUrl("./icons/label-icons/drift-bottle.png") readonly property url labelIconFace: Qt.resolvedUrl("./icons/label-icons/face.png") readonly property url labelIconGames: Qt.resolvedUrl("./icons/label-icons/games.png") readonly property url labelIconMoments: Qt.resolvedUrl("./icons/label-icons/moments.png") readonly property url labelIconMyPosts: Qt.resolvedUrl("./icons/label-icons/my-posts.png") readonly property url labelIconPeopleNearby: Qt.resolvedUrl("./icons/label-icons/people-nearby.png") readonly property url labelIconScanQRCode: Qt.resolvedUrl("./icons/label-icons/scan-qr-code.png") readonly property url labelIconSettings: Qt.resolvedUrl("./icons/label-icons/settings.png") readonly property url labelIconShake: Qt.resolvedUrl("./icons/label-icons/shake.png") readonly property url labelIconShareExcel: Qt.resolvedUrl("./icons/label-icons/share-excel.png") readonly property url labelIconShareFile: Qt.resolvedUrl("./icons/label-icons/share-file.png") readonly property url labelIconShareMusic: Qt.resolvedUrl("./icons/label-icons/share-music.png") readonly property url labelIconSharePdf: Qt.resolvedUrl("./icons/label-icons/share-pdf.png") readonly property url labelIconShakePicture: Qt.resolvedUrl("./icons/label-icons/share-picture.png") readonly property url labelIconSharePosition: Qt.resolvedUrl("./icons/label-icons/share-position.png") readonly property url labelIconShareSound: Qt.resolvedUrl("./icons/label-icons/share-sound.png") readonly property url labelIconShareText: Qt.resolvedUrl("./icons/label-icons/share-text.png") readonly property url labelIconShareUrl: Qt.resolvedUrl("./icons/label-icons/share-url.png") /// readonly property url labelIconShareVideo: Qt.resolvedUrl("./icons/label-icons/share-video.png") readonly property url labelIconShareWord: Qt.resolvedUrl("./icons/label-icons/share-word.png") readonly property url labelIconShareZip: Qt.resolvedUrl("./icons/label-icons/share-zip.png") readonly property url labelIconWallet: Qt.resolvedUrl("./icons/label-icons/wallet.png") } ================================================ FILE: qml/WellChat/resource/qmldir ================================================ module Resource singleton R 1.0 ./R.qml ================================================ FILE: qml.qrc ================================================ qml/WellChat/BussinessPage/Chat/ChatPage.qml qml/WellChat/BussinessPage/Chat/heart.js qml/WellChat/BussinessPage/Chat/Heartbeat.qml qml/WellChat/BussinessPage/Contacts/ContactsListView.qml qml/WellChat/BussinessPage/Discover/MomentsPage/MomentsPage.qml qml/WellChat/BussinessPage/Personal/Settings/AboutPage.qml qml/WellChat/BussinessPage/Personal/Settings/ChatSettingsPage.qml qml/WellChat/BussinessPage/Personal/Settings/DoNotDisturbSettingsPage.qml qml/WellChat/BussinessPage/Personal/Settings/GeneralSettingsPage.qml qml/WellChat/BussinessPage/Personal/Settings/MyAccountSettingsPage.qml qml/WellChat/BussinessPage/Personal/Settings/NotificationsSettingsPage.qml qml/WellChat/BussinessPage/Personal/Settings/PrivacySettingsPage.qml qml/WellChat/BussinessPage/Personal/Settings/SettingsGroup.qml qml/WellChat/BussinessPage/Personal/FavoritesPage.qml qml/WellChat/BussinessPage/Personal/MyPostsPage.qml qml/WellChat/BussinessPage/Personal/SettingsPage.qml qml/WellChat/BussinessPage/ChatsView.qml qml/WellChat/BussinessPage/ContactsView.qml qml/WellChat/BussinessPage/DiscoverPage.qml qml/WellChat/BussinessPage/PersonalPage.qml qml/WellChat/BussinessPage/ProfilePage.qml qml/WellChat/BussinessPage/qmldir qml/WellChat/BussinessPage/R.qml qml/WellChat/Component/+android/UI.js qml/WellChat/Component/Constant.qml qml/WellChat/Component/Icon.qml qml/WellChat/Component/IconButton.qml qml/WellChat/Component/IconLabel.qml qml/WellChat/Component/MainListView.qml qml/WellChat/Component/SampleTextArea.qml qml/WellChat/Component/ScrollBar.qml qml/WellChat/Component/Separator.qml qml/WellChat/Component/UI.js qml/WellChat/Resource/background/active/chat-bubble.png qml/WellChat/Resource/background/inactive/chat-bubble.png qml/WellChat/Resource/icons/bar-icons/active/back.png qml/WellChat/Resource/icons/bar-icons/active/chat.png qml/WellChat/Resource/icons/bar-icons/active/contacts.png qml/WellChat/Resource/icons/bar-icons/active/discover.png qml/WellChat/Resource/icons/bar-icons/active/emoticon.png qml/WellChat/Resource/icons/bar-icons/active/magnifier.png qml/WellChat/Resource/icons/bar-icons/active/me.png qml/WellChat/Resource/icons/bar-icons/active/plus.png qml/WellChat/Resource/icons/bar-icons/active/sound.png qml/WellChat/Resource/icons/bar-icons/inactive/back.png qml/WellChat/Resource/icons/bar-icons/inactive/chat.png qml/WellChat/Resource/icons/bar-icons/inactive/contacts.png qml/WellChat/Resource/icons/bar-icons/inactive/discover.png qml/WellChat/Resource/icons/bar-icons/inactive/emoticon.png qml/WellChat/Resource/icons/bar-icons/inactive/magnifier.png qml/WellChat/Resource/icons/bar-icons/inactive/me.png qml/WellChat/Resource/icons/bar-icons/inactive/plus.png qml/WellChat/Resource/icons/bar-icons/inactive/sound.png qml/WellChat/Resource/icons/icons/a9p.png qml/WellChat/Resource/icons/icons/a9q.png qml/WellChat/Resource/icons/icons/a_0.png qml/WellChat/Resource/icons/icons/a_7.png qml/WellChat/Resource/icons/icons/a_f.png qml/WellChat/Resource/icons/icons/a_g.png qml/WellChat/Resource/icons/icons/a_h.png qml/WellChat/Resource/icons/icons/a_i.png qml/WellChat/Resource/icons/icons/a_j.png qml/WellChat/Resource/icons/icons/a_k.png qml/WellChat/Resource/icons/icons/a_l.png qml/WellChat/Resource/icons/icons/a_m.png qml/WellChat/Resource/icons/icons/a_o.png qml/WellChat/Resource/icons/icons/a_p.png qml/WellChat/Resource/icons/icons/a_q.png qml/WellChat/Resource/icons/icons/a_t.png qml/WellChat/Resource/icons/icons/a_z.png qml/WellChat/Resource/icons/icons/aeo.png qml/WellChat/Resource/icons/icons/aep.png qml/WellChat/Resource/icons/icons/aeq.png qml/WellChat/Resource/icons/icons/aes.png qml/WellChat/Resource/icons/icons/aet.png qml/WellChat/Resource/icons/icons/aeu.png qml/WellChat/Resource/icons/icons/aez.png qml/WellChat/Resource/icons/icons/af0.png qml/WellChat/Resource/icons/icons/af1.png qml/WellChat/Resource/icons/icons/af2.png qml/WellChat/Resource/icons/icons/af3.png qml/WellChat/Resource/icons/icons/af4.png qml/WellChat/Resource/icons/icons/af5.png qml/WellChat/Resource/icons/icons/af6.png qml/WellChat/Resource/icons/icons/af7.png qml/WellChat/Resource/icons/icons/af8.png qml/WellChat/Resource/icons/icons/ag2.png qml/WellChat/Resource/icons/icons/ak3.png qml/WellChat/Resource/icons/icons/ak4.png qml/WellChat/Resource/icons/icons/ak5.png qml/WellChat/Resource/icons/icons/alarm.png qml/WellChat/Resource/icons/icons/au8.png qml/WellChat/Resource/icons/icons/au9.png qml/WellChat/Resource/icons/icons/female.png qml/WellChat/Resource/icons/icons/male.png qml/WellChat/Resource/icons/icons/un-alarm.png qml/WellChat/Resource/icons/label-icons/drift-bottle.png qml/WellChat/Resource/icons/label-icons/face.png qml/WellChat/Resource/icons/label-icons/favorites.png qml/WellChat/Resource/icons/label-icons/games.png qml/WellChat/Resource/icons/label-icons/moments.png qml/WellChat/Resource/icons/label-icons/my-posts.png qml/WellChat/Resource/icons/label-icons/people-nearby.png qml/WellChat/Resource/icons/label-icons/scan-qr-code.png qml/WellChat/Resource/icons/label-icons/settings.png qml/WellChat/Resource/icons/label-icons/shake.png qml/WellChat/Resource/icons/label-icons/share-excel.png qml/WellChat/Resource/icons/label-icons/share-file.png qml/WellChat/Resource/icons/label-icons/share-music.png qml/WellChat/Resource/icons/label-icons/share-pdf.png qml/WellChat/Resource/icons/label-icons/share-picture.png qml/WellChat/Resource/icons/label-icons/share-position.png qml/WellChat/Resource/icons/label-icons/share-sound.png qml/WellChat/Resource/icons/label-icons/share-text.png qml/WellChat/Resource/icons/label-icons/share-url.png qml/WellChat/Resource/icons/label-icons/share-video.png qml/WellChat/Resource/icons/label-icons/share-word.png qml/WellChat/Resource/icons/label-icons/share-zip.png qml/WellChat/Resource/icons/label-icons/wallet.png qml/WellChat/Resource/tests/tests001.jpg qml/WellChat/Resource/qmldir qml/WellChat/Resource/R.qml qml/WellChat/Sparrow/+android/UI.js qml/WellChat/Sparrow/+android/WebPage.qml qml/WellChat/Sparrow/PopupLayer/Delegate/PopupLayerDialogDelegate.qml qml/WellChat/Sparrow/PopupLayer/Delegate/PopupLayerSideMenuDelegate.qml qml/WellChat/Sparrow/PopupLayer/Delegate/qmldir qml/WellChat/Sparrow/PopupLayer/PopupLayer.qml qml/WellChat/Sparrow/PopupLayer/PopupLayerDelegate.qml qml/WellChat/Sparrow/PopupLayer/PopupLayerTransition.qml qml/WellChat/Sparrow/PopupLayer/qmldir qml/WellChat/Sparrow/PopupLayer/readme.md qml/WellChat/Sparrow/resources/readme.md qml/WellChat/Sparrow/BottomBar.qml qml/WellChat/Sparrow/ClickedShaderEffect.qml qml/WellChat/Sparrow/GeneralSettings.qml qml/WellChat/Sparrow/Page.qml qml/WellChat/Sparrow/PageStackWindow.qml qml/WellChat/Sparrow/qmldir qml/WellChat/Sparrow/QObject.qml qml/WellChat/Sparrow/SampleButton.qml qml/WellChat/Sparrow/SampleIcon.qml qml/WellChat/Sparrow/SampleLabel.qml qml/WellChat/Sparrow/SampleTextField.qml qml/WellChat/Sparrow/TopBar.qml qml/WellChat/Sparrow/Tracker.qml qml/WellChat/Sparrow/UI.js qml/WellChat/Sparrow/WebPage.qml qml/WellChat/main.qml qml/WellChat/MainView.qml qml/WellChat/WellChat.qmlproject qml/WellChat/BussinessPage/Chat/Lazy.qml qml/WellChat/BussinessPage/Chat/Tuling123.js ================================================ FILE: src/wellchat/collectionsmodel.cpp ================================================ #include "collectionsmodel.h" #include #include #include #include #include #include #include CollectionsModel::CollectionsModel(QObject *parent): QAbstractListModel(parent) { database = QSqlDatabase::addDatabase("QSQLITE"); //! [android AppDataLocation] : //! /data/data/org.gdpurjyfs.wellchat/files/WellChat.db if( QDir().mkpath(QStandardPaths::writableLocation( QStandardPaths::AppDataLocation))) { QString dbPath = QStandardPaths::writableLocation( QStandardPaths::AppDataLocation) + QDir::separator() + QCoreApplication::applicationName() + ".db"; #ifdef QT_DEBUG qDebug() << "dbPath" << QDir::toNativeSeparators(dbPath) ; #endif database.setDatabaseName(dbPath); if(!database.open()) { qDebug()< roleNamesHash = this->roleNames(); if(roleNamesHash.contains(role)) return QString(roleNamesHash[role]); else { return QString(""); } } QHash CollectionsModel::roleNames() const { QHash roleNamesHash; roleNamesHash.insert(Author, "Author"); roleNamesHash.insert(CreateTime, "CreateTime"); roleNamesHash.insert(Title, "Title"); roleNamesHash.insert(Source, "Source"); roleNamesHash.insert(SourceName, "SourceName"); roleNamesHash.insert(Summary, "Summary"); roleNamesHash.insert(CollectionType, "CollectionType"); return roleNamesHash; } int CollectionsModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); QString table_recode_count_sql = "select count(*) as TotalNum from collections"; int table_recode_count = 0; if(database.isOpen()) { QSqlQuery query(table_recode_count_sql, database); if(query.exec() && query.next()) { bool ok = false; int totalNum = query.value("TotalNum").toInt(&ok); if(ok) { table_recode_count = totalNum; #ifdef QT_DEBUG qDebug() << "table_recode_count: " << table_recode_count ; #endif } } else { qDebug() << "CollectionsModel::rowCount " << query.lastError() ; } } return table_recode_count; } QVariant CollectionsModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0) return QVariant(); if (index.row() >= this->rowCount(index)) { qWarning() << "CollectionsModels: Index out of bound"; return QVariant(); } // %1 is index %2 is index+1 QString select_recode_limit_2_sql = "select * from collections limit "; if(database.isOpen()) { QString::number(index.row()); select_recode_limit_2_sql = select_recode_limit_2_sql + QString::number(index.row()) + " ," +QString::number(index.row()+1); qDebug() << "select_recode_limit_2_sql : " << select_recode_limit_2_sql << endl; QSqlQuery query(select_recode_limit_2_sql, database); if(query.exec() && query.next()) { switch (role) { case Author: return query.value("Author").toString(); case CreateTime: return query.value("CreateTime").toString(); case Title: return query.value("Title").toString(); case Source: return query.value("Source").toString(); case SourceName: return query.value("SourceName").toString(); case Summary: return query.value("Summary").toString(); case CollectionType: return query.value("CollectionType").toString(); default: break; } } else { qDebug() << "CollectionsModel::data " << query.lastError(); } } return QVariant(QString("")); } QObject *CollectionsModel::singleton(QQmlEngine *qmlEngine, QJSEngine *jsEngine) { Q_UNUSED(jsEngine) static CollectionsModel* collectionsModel = new CollectionsModel(qmlEngine); return collectionsModel; } ================================================ FILE: src/wellchat/collectionsmodel.h ================================================ #ifndef COLLECTIONSMODEL_H #define COLLECTIONSMODEL_H #include #include class QQmlEngine; class QJSEngine; class CollectionsModel : public QAbstractListModel { Q_OBJECT enum CollectionRole { Author = Qt::UserRole + 1, // 作者 CreateTime, // 创建时间 Title, // 标题 Source, // 来源地址:http://baidu.com SourceName, // 来源名字:baidu Summary, // 摘要 CollectionType // 收藏的类型,例如链接,音乐,图片等 }; public: CollectionsModel(QObject* parent=0); QString roleName(int role); QHash roleNames() const; int rowCount(const QModelIndex &parent) const; QVariant data(const QModelIndex &index, int role) const; static QObject* singleton(QQmlEngine* qmlEngine, QJSEngine* jsEngine); private: QSqlDatabase database; }; #endif // COLLECTIONSMODEL_H