Browse Source

添加项目sybotan-android-database.sybotan-android-graphy。

庞利祥 5 years ago
parent
commit
52a2fcee26
27 changed files with 3391 additions and 0 deletions
  1. 169 0
      sybotan-android-database/build.gradle
  2. 21 0
      sybotan-android-database/proguard-rules.pro
  3. 25 0
      sybotan-android-database/src/main/AndroidManifest.xml
  4. 453 0
      sybotan-android-database/src/main/java/com/sybotan/android/database/SAndroidDao.kt
  5. 76 0
      sybotan-android-database/src/main/java/com/sybotan/android/database/SDatabase.kt
  6. 89 0
      sybotan-android-database/src/main/java/com/sybotan/android/database/SRowParser.kt
  7. 50 0
      sybotan-android-database/src/main/java/com/sybotan/android/database/extensions/SSQLiteDatabaseExtension.kt
  8. 26 0
      sybotan-android-database/src/main/res/values/strings.xml
  9. 161 0
      sybotan-android-graphy/build.gradle
  10. 21 0
      sybotan-android-graphy/proguard-rules.pro
  11. 24 0
      sybotan-android-graphy/src/main/AndroidManifest.xml
  12. 583 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/SGraphyItem.kt
  13. 336 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/SGraphyScene.kt
  14. 492 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/SGraphyView.kt
  15. 52 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/data/SLineF.kt
  16. 38 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/enums/SGraphyItemFlag.kt
  17. 38 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/enums/SGraphyViewTouchState.kt
  18. 40 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/events/SGraphyViewMoveEvent.kt
  19. 38 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/events/SGraphyViewZoomEvent.kt
  20. 250 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphyClockItem.kt
  21. 71 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphyImageItem.kt
  22. 86 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphyLineItem.kt
  23. 43 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphyPolygonItem.kt
  24. 101 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphyRectItem.kt
  25. 43 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphySimpleTextItem.kt
  26. 39 0
      sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/listeners/SGraphyItemPosListener.kt
  27. 26 0
      sybotan-android-graphy/src/main/res/values/strings.xml

+ 169 - 0
sybotan-android-database/build.gradle

@@ -0,0 +1,169 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+group rootProject.group
+version rootProject.version
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'org.jetbrains.dokka-android'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion ANDROID_SDK_VERSION.toInteger()
+    buildToolsVersion ANDROID_BUILD_TOOLS_VERSION
+
+
+    defaultConfig {
+        minSdkVersion ANDROID_MIN_SDK_VERSION.toInteger()
+        targetSdkVersion ANDROID_SDK_VERSION.toInteger()
+        versionCode SYBOTAN_CODE.toInteger()
+        versionName SYBOTAN_ANDROID_VERSION
+
+        testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
+    }
+    buildTypes {
+        release {
+            postprocessing {
+//                removeUnusedCode false
+//                removeUnusedResources false
+//                obfuscate false
+//                optimizeCode false
+                proguardFile 'proguard-rules.pro'
+            }
+        }
+    }
+    // 解决lint语法检查错误(kotlin语法无法通过lint)
+    lintOptions {
+        abortOnError false
+    }
+
+    kotlinOptions {
+        jvmTarget = JVM_TARGET
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // kotlin 依赖
+    // https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib-jdk8
+    implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: KOTLIN_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // android支持
+    implementation group: 'com.android.support', name: 'appcompat-v7', version: ANDROID_SUPPORT_VERSION
+    implementation group: 'com.android.support', name: 'design', version: ANDROID_SUPPORT_VERSION
+    //implementation group: 'com.android.support', name: 'support-v4', version: ANDROID_SUPPORT_VERSION
+    implementation group: 'com.android.support.constraint', name: 'constraint-layout', version: CONSTRAINT_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // anko依赖
+    // https://mvnrepository.com/artifact/org.jetbrains.anko/anko-common
+    implementation group: 'org.jetbrains.anko', name: 'anko-common', version: ANKO_VERSION
+    // https://mvnrepository.com/artifact/org.jetbrains.anko/anko-sqlite
+    implementation group: 'org.jetbrains.anko', name: 'anko-sqlite', version: ANKO_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // https://mvnrepository.com/artifact/javax.persistence/persistence-api
+    implementation group: "javax.persistence", name: "persistence-api", version: PERSISTENCE_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Sybotan依赖
+    implementation group: 'com.sybotan', name: 'sybotan-kotlin-base', version: SYBOTAN_KOTLIN_VERSION
+    implementation group: 'com.sybotan', name: 'sybotan-kotlin-database', version: SYBOTAN_KOTLIN_VERSION
+    implementation project(':sybotan-android-base')
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 文档生成
+// 源码
+task sourcesJar(type: Jar) {
+    group = 'jar'
+    from "${android.sourceSets.main.java.srcDirs}"
+    classifier = 'sources'
+}
+
+// 文档
+task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
+    jdkVersion = JDK_VERSION.toInteger()
+    noStdlibLink = true
+    outputFormat = 'javadoc'
+    outputDirectory = "$buildDir/javadoc"
+//    sourceDirs = files('src/main/java')
+}
+
+// 上成上传的文档jar包
+task javadocJar(type: Jar, dependsOn: dokkaJavadoc) {
+    group = 'jar'
+    classifier = 'javadoc'
+    from "${dokka.outputDirectory}"
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// maven 仓库管理
+
+// 设置上传的包,默认上传aar与pom
+artifacts {
+    // 上传源码包
+    // archives sourcesJar
+    // 上传文档包
+    // archives javadocJar
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            pom.groupId = project.group
+            pom.version = project.version
+            pom.artifactId = project.name
+            pom.project {
+                name project.name
+                packaging 'jar'
+                url SYBOTAN_URL
+                description '斯伯坦机器人世界 android database库。'
+                licenses {
+                    license {
+                        name 'The Apache Software License, Version 2.0'
+                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                    }
+                }
+                developers {
+                    developer {
+                        id DEVELOPER_ID
+                        name DEVELOPER_NAME
+                    }
+                }
+            }
+            snapshotRepository(url: MAVEN_REPO_SNAPSHOT_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+            repository(url: MAVEN_REPO_RELEASE_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+        }
+    }
+}

+ 21 - 0
sybotan-android-database/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 25 - 0
sybotan-android-database/src/main/AndroidManifest.xml

@@ -0,0 +1,25 @@
+<!--
+  ~ *********************************************************************************************************************
+  ~
+  ~               iFHS7.
+  ~              ;BBMBMBMc                  rZMBMBR              BMB
+  ~              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+  ~     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+  ~     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+  ~      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+  ~     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+  ~     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+  ~     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+  ~         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+  ~     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+  ~      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+  ~         i0BM: SWKHKGO  MBDv
+  ~           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+  ~              ,  XMW  ..
+  ~                  r                                                                     All rights reserved.
+  ~
+  ~ *********************************************************************************************************************
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.sybotan.android.database"/>

+ 453 - 0
sybotan-android-database/src/main/java/com/sybotan/android/database/SAndroidDao.kt

@@ -0,0 +1,453 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.database
+
+import android.util.Log
+import com.sybotan.android.database.SDatabase.use
+import com.sybotan.base.exceptions.SFieldNotFoundException
+import com.sybotan.base.extensions.*
+import com.sybotan.database.SAbstractDao
+import com.sybotan.database.SFilter
+import com.sybotan.database.SQueryBuilder
+import com.sybotan.database.enums.SOps
+import com.sybotan.database.enums.SDirection
+import org.jetbrains.anko.db.*
+import java.io.Serializable
+import javax.persistence.Column
+
+/**
+ * 数据库访问对象
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+open class SAndroidDao<ENTITY: Serializable>(entityClazz: Class<ENTITY>): SAbstractDao<ENTITY>(entityClazz) {
+    companion object {
+        val TAG = SAndroidDao::class.java.name
+    } // Companion object
+
+    /** 行解析器 */
+    private val parser = SRowParser<ENTITY>(entityClass)
+
+    /**
+     * 在数据库中插入实体
+     *
+     * @param   entity      插入数据库的实体
+     * @return  返回实体。如果主键为数据库生成时,该实体主键将被填充。
+     */
+    override fun insert(entity: ENTITY): Boolean {
+        use {
+            val listPair= buildInsertValues(entity)
+            insert("`${tableName()}`", *Array(listPair.size){i-> listPair[i]})
+        }
+        return true
+    } // Function insert()
+
+    /**
+     * 根据条件删除
+     *
+     * @param   args        删除条件
+     */
+    override fun delete(args: List<SFilter>): Boolean {
+        use {
+            val whereArgs = ArrayList<Pair<String, Any>>()
+            val clause = buildWhereArgList(args, whereArgs)
+            delete("`${tableName()}`", clause, *Array(whereArgs.size){i-> whereArgs[i]})
+        }
+        return true
+    } // Function delete()
+
+    /**
+     * 删除所有
+     */
+    override fun deleteAll(): Boolean {
+        use {
+            delete(tableName())
+        }
+        return true
+    } // Function deleteAll()
+
+    /**
+     * 根据条件更新
+     *
+     * @param   projection  将被更新的字段列表(以逗号分隔),为空表示更新所有非空字段
+     * @param   args        删除条件
+     * @return  更新是否成功
+     */
+    override fun update(entity: ENTITY, projection: ArrayList<String>?, args: List<SFilter>): Boolean {
+        use {
+            val whereArgs = ArrayList<Pair<String, Any>>()
+            val clause = buildWhereArgList(args, whereArgs)
+            val valueList = buildUpdateValues(entity, projection)
+            update("`${tableName()}`", *Array(valueList.size){ i-> valueList[i]})
+                    .whereArgs(clause, *Array(whereArgs.size){i-> whereArgs[i]}).exec()
+        }
+        return true
+    } // Function update()
+
+    /**
+     * 根据条件查询记录
+     *
+     * @param   args        查询条件
+     * @return  查询到的记录
+     */
+    override fun select(args: List<SFilter>): SQueryBuilder<ENTITY> {
+        // 以下循环解决 “SingletonList cannot be cast to java.util.ArrayList” 错误。
+        val filterList = ArrayList<SFilter>()
+        args.forEach {
+            filterList.add(it)
+        }
+        return SQueryBuilder(this, filterList)
+    } // Function select()
+
+    /**
+     * 查询所有记录
+     *
+     * @return  查询到的记录
+     */
+    override fun selectAll(): SQueryBuilder<ENTITY> {
+        return SQueryBuilder(this, ArrayList())
+    } // Function selectAll()
+
+    /**
+     * 执行查询操作
+     *
+     * @param   builder     查询查询构造器
+     * @return  查询到的记录
+     */
+    override fun execQuery(builder: SQueryBuilder<ENTITY>): ArrayList<ENTITY> {
+        var list = ArrayList<ENTITY>()
+        use {
+            val queryBuilder = select("`${builder.tableName ?: tableName()}`")
+            // 设置查询条件
+            if (builder.whereArgs.isNotEmpty()) {       // 有查询条件
+                val whereArgs = ArrayList<Pair<String, Any>>()
+                val clause = buildWhereArgList(builder.whereArgs, whereArgs)
+                Log.d(TAG, "clause=$clause")
+                Log.d(TAG, "whereArgs=${whereArgs.toJson()}")
+                queryBuilder.whereArgs(clause, *Array(whereArgs.size) { i -> whereArgs[i] })
+            }
+
+            //queryBuilder.column(buildColumns(gennericClass()!!, builder.fieldList))
+
+            // 设置排序条件
+            for (order in builder.orderArgs) {       // 依次取出排序条件
+                if (order.direction == SDirection.ASC) {
+                    queryBuilder.orderBy("`${this@SAndroidDao.columnName(order.name)!!}`")
+                } else {
+                    queryBuilder.orderBy("`${this@SAndroidDao.columnName(order.name)!!}`", SqlOrderDirection.DESC)
+                }
+            }
+
+            if (builder.isDistinct) {       // 如果去重
+                queryBuilder.distinct()
+            }
+
+            if (builder.count != null) {        // 限定从指定记录开始取出的记录数与
+                if (builder.offset != null) {
+                    queryBuilder.limit(builder.offset!!, builder.count!!)
+                } else {
+                    queryBuilder.limit(builder.count!!)
+                }
+            }
+
+            // 执行查询
+            queryBuilder.exec {
+                list = this.parseList(parser) as ArrayList<ENTITY>
+            }
+        }
+        return list
+    } // Function exec()
+
+    /**
+     * 执行查询操作
+     *
+     * @param   builder     查询查询构造器
+     * @return  查询到的记录
+     */
+    override fun execCount(builder: SQueryBuilder<ENTITY>): Long {
+        var result = 0L
+        var list = ArrayList<Serializable>()
+        use {
+            val queryBuilder = select("`${builder.tableName ?: tableName()}`")
+            // 设置查询条件
+            if (builder.whereArgs.isNotEmpty()) {       // 有查询条件
+                val whereArgs = ArrayList<Pair<String, Any>>()
+                val clause = "count(1)"
+                Log.d(TAG, "clause=$clause")
+                Log.d(TAG, "whereArgs=${whereArgs.toJson()}")
+                queryBuilder.whereArgs(clause, *Array(whereArgs.size) { i -> whereArgs[i] })
+            }
+
+            // 执行查询
+            queryBuilder.exec {
+                result = this.parseSingle(object:RowParser<Long>{
+                    override fun parseRow(columns: Array<Any?>): Long {
+                        return columns[0]!!.toLong() ?: 0
+                    }
+                })
+            }
+        }
+        return result
+    } // Function exec()
+
+    /**
+     * 获得列名
+     *
+     * @param   name    属性名
+     * @return  对应的数据表字段名
+     */
+    private fun columnName(name: String): String? {
+        return try {
+            val clz = gennericClass()
+            val field = clz!!.getAllDeclaredField(name, true, true)
+            val anno = field?.getAnnotation(Column::class.java)
+            anno!!.name
+        } catch (e: Exception) {
+            null
+        }
+    } // Function columnName()
+
+    /**
+     * 构造where子句参数
+     *
+     * @param   filterList      过滤条件
+     * @param   args            子句参数列表
+     * @return  返回where子句
+     */
+    private fun buildWhereArgList(filterList: List<SFilter>, args: ArrayList<Pair<String, Any>>): String {
+        val builder = StringBuilder()
+        for (filter in filterList) {
+            val where = buildWhereArg(filter, args)
+            if (builder.isNotEmpty()) {
+                builder.append(" AND ")
+            }
+            builder.append(where)
+        }
+        return builder.toString()
+    } // Function buildWhereArgList()
+
+    /**
+     * 构造where子句参数
+     *
+     * @param   filter          过滤条件
+     * @param   args            子句参数列表
+     * @return  返回where子句
+     */
+    fun buildWhereArg(filter: SFilter, args: ArrayList<Pair<String, Any>>): String {
+        val builder = StringBuilder()
+        try {
+            val argName = "arg${args.size}"
+            when (filter.opt) {
+                SOps.EQ -> {     // 等于
+                    builder.append("`${columnName(filter.first!!.toString())}` = {$argName}")
+                    args.add(Pair(argName, filter.second!!))
+                }
+                SOps.NE -> {     // 不等于
+                    builder.append("`${columnName(filter.first!!.toString())}` <> {$argName}")
+                    args.add(Pair(argName, filter.second!!))
+                }
+                SOps.GT -> {     // 大于
+                    builder.append("`${columnName(filter.first!!.toString())}` > {$argName}")
+                    args.add(Pair(argName, filter.second!!))
+                }
+                SOps.GTE -> {    // 大于等于
+                    builder.append("`${columnName(filter.first!!.toString())}` >= {$argName}")
+                    args.add(Pair(argName, filter.second!!))
+                }
+                SOps.LT -> {     // 小于
+                    builder.append("`${columnName(filter.first!!.toString())}` < {$argName}")
+                    args.add(Pair(argName, filter.second!!))
+                }
+                SOps.LTE -> {    // 小于等行
+                    builder.append("`${columnName(filter.first!!.toString())}` <= {$argName}")
+                    args.add(Pair(argName, filter.second!!))
+                }
+                SOps.CONTAIN -> {    // 包含
+                    builder.append("`${columnName(filter.first!!.toString())}` LIKE \"%\" || {$argName} || \"%\"")
+                    args.add(Pair(argName, filter.second!!))
+                }
+                SOps.STARTWITH -> {    // 小于等行
+                    builder.append("`${columnName(filter.first!!.toString())}` LIKE {$argName} || \"%\"")
+                    args.add(Pair(argName, filter.second!!))
+                }
+                SOps.ENDWITH -> {    // 小于等行
+                    builder.append("`${columnName(filter.first!!.toString())}` LIKE \"%\" || {$argName}")
+                    args.add(Pair(argName, filter.second!!))
+                }
+                SOps.ISNULL -> {     // 为空
+                    builder.append("`${columnName(filter.first!!.toString())}` ISNULL")
+                }
+                SOps.NOTNULL -> {    // 不为空
+                    builder.append("`${columnName(filter.first!!.toString())}` NOTNULL")
+                }
+                SOps.IN -> {         // 在列表内
+                    val inArg = buidInArgs(filter, args, argName)
+                    builder.append("`${columnName(filter.first!!.toString())}` IN ($inArg)")
+                }
+                SOps.NOT -> {        // 逻辑非
+                    builder.append("NOT (${buildWhereArg(filter.first as SFilter, args)})")
+                }
+                SOps.AND -> {        // 逻辑与
+                    builder.append("(${buildWhereArg(filter.first as SFilter, args)}) AND (${buildWhereArg(filter.second as SFilter, args)})")
+                }
+                SOps.OR -> {         // 逻辑或
+                    builder.append("((${buildWhereArg(filter.first as SFilter, args)}) OR (${buildWhereArg(filter.second as SFilter, args)}))")
+                }
+                else -> {
+                }
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+        return builder.toString()
+    } // Function buildWhereArgList()
+
+    /**
+     * 生成IN操作参数
+     */
+    private fun buidInArgs(filter: SFilter, args: ArrayList<Pair<String, Any>>, argName: String): String {
+        val builder = StringBuilder()
+        val argList = filter.second!! as List<Any>
+        for ((index, item) in argList.withIndex()) {
+            if (index > 0) {
+                builder.append(", {${argName}_$index}")
+            } else {
+                builder.append("{${argName}_$index}")
+            }
+            args.add(Pair("${argName}_$index", item))
+        }
+        return builder.toString()
+    } // Function buidInArgs()
+
+    /**
+     * 构建values值
+     *
+     * @param   entity      实体对象
+     * @return  values列表
+     */
+    protected fun buildInsertValues(entity: ENTITY): ArrayList<Pair<String, Any?>> {
+        val clz = entity::class.java
+        val list = ArrayList<Pair<String, Any?>>()
+        for (field in clz.declaredFields) {
+            if (field.isAnnotationPresent(Column::class.java)) {
+                val anno = field.getAnnotation(Column::class.java) ?: continue
+                // 如果插入状态或更新状态为否,则跳过该字段
+                if (!anno.insertable || !anno.updatable) {
+                    continue
+                }
+
+                field.isAccessible = true
+                val column = fieldToPair(entity, field)
+                if (column.second != null) {
+                    list.add(column)
+                }
+                field.isAccessible = false
+            }
+        }
+        return list
+    } // Function buildValues()
+
+    /**
+     * 构建values值
+     *
+     * @param   entity      实体对象
+     * @param   projection  字段映射(未转换)
+     * @return  values列表
+     */
+    protected fun buildUpdateValues(entity: ENTITY, projection: ArrayList<String>?): ArrayList<Pair<String, Any?>> {
+        val clz = entity::class.java
+        val fieldList = ArrayList<String>()
+        try {
+            // 转换属性名
+            for (name in projection!!) {
+                fieldList.add(propertyName(entity.javaClass, name.trim()))
+            }
+        } catch (e: Exception) {
+            // DO NOTHING
+        }
+
+        val updateNull = fieldList.size > 0
+        val list = ArrayList<Pair<String, Any?>>()
+        for (field in clz.declaredFields) {
+            if (field.isAnnotationPresent(Column::class.java)) {
+                if (fieldList.size > 0 && !fieldList.contains(field.name)) {
+                    continue
+                }
+
+                val anno = field.getAnnotation(Column::class.java) ?: continue
+                // 如果更新状态为否,则跳过该字段
+                if (!anno.updatable) {
+                    continue
+                }
+
+                field.isAccessible = true
+                val column = fieldToPair(entity, field)
+                if (updateNull || column.second != null) {
+                    list.add(column)
+                }
+                field.isAccessible = false
+            }
+        }
+        return list
+    } // Function buildValues()
+
+    /**
+     * 构建columns值
+     *
+     * @param   clazz       实体类类型
+     * @param   fieldList   属性映射列表
+     * @return  values列表
+     */
+    protected fun buildColumns(clazz: Class<*>, fieldList: ArrayList<String>?): String {
+        val builder = StringBuilder()
+        for (field in clazz.getAllDeclaredFields()) {
+            if (fieldList != null && fieldList.size > 0 && !fieldList.contains(field.name)) {
+                continue
+            }
+            val anna = field.getAnnotation(Column::class.java) ?: continue
+
+            if (builder.isNotEmpty()) {
+                builder.append(", ")
+            }
+            builder.append("`${anna.name}` AS `${field.name}`")
+        }
+        return builder.toString()
+    } // Function buildColumns()
+
+    /**
+     * 获得实体类属性名
+     *
+     * @param   clazz   实体类类型
+     * @param   name    名称
+     * @return  实体类属性名
+     */
+    protected fun propertyName(clazz: Class<*>, name: String): String {
+        try {
+            val field = clazz.getAllDeclaredField(name, true, true)
+            return field!!.name
+        } catch (e: Exception) {
+            throw SFieldNotFoundException(name)
+        }
+    } // Function propertyName()
+} // Class SAndroidDao

+ 76 - 0
sybotan-android-database/src/main/java/com/sybotan/android/database/SDatabase.kt

@@ -0,0 +1,76 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.database
+
+import android.database.sqlite.SQLiteDatabase
+import com.sybotan.android.base.SApplication
+import com.sybotan.android.database.extensions.execSQL
+import org.jetbrains.anko.db.ManagedSQLiteOpenHelper
+import java.io.InputStream
+
+/**
+ * 数据库操作对象
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+object SDatabase
+    : ManagedSQLiteOpenHelper(SApplication.app.applicationContext, "SDatabase.db", null, SApplication.app.databaseVersion) {
+    /**
+     * 创建数据库回调函数。执生"assets/sql/database.sql"完成数据库创建。
+     *
+     * @param   db      指向创建的数据库
+     */
+    override fun onCreate(db: SQLiteDatabase) {
+        var input: InputStream? = null
+        try {
+            input = SApplication.app.applicationContext.assets.open("sql/database.sql")
+            db.execSQL(input)
+        } catch (e: Exception) {
+            e.printStackTrace()
+        } finally {
+            input?.close()
+        }
+        return
+    } // Function onCreate()
+
+    /**
+     * 数据库升级回调函数。执行"assets/sql/database_$oldVersion-$newVersion.sql"完成升级。
+     *
+     * @param   db              指向数据库实例
+     * @param   oldVersion      数据库旧版本号
+     * @param   newVersion      数据库新版本号
+     */
+    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
+        var input: InputStream? = null
+        try {
+            input = SApplication.app.applicationContext.assets.open("sql/database_$oldVersion-$newVersion.sql")
+            db.execSQL(input)
+        } catch (e: Exception) {
+            e.printStackTrace()
+        } finally {
+            input?.close()
+        }
+        return
+    } // Function onUpgrade()
+} // Class SDatabase

+ 89 - 0
sybotan-android-database/src/main/java/com/sybotan/android/database/SRowParser.kt

@@ -0,0 +1,89 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.database
+
+import com.sybotan.base.extensions.*
+import com.sybotan.base.utils.SJsonUtil
+import org.jetbrains.anko.db.MapRowParser
+import java.io.Serializable
+import java.lang.reflect.ParameterizedType
+import javax.persistence.Column
+
+/**
+ * 记录行解析器
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+open class SRowParser<out ENTITY: Serializable>(var clz: Class<*>? = null) : MapRowParser<ENTITY> {
+
+    /**
+     * 解析记录行
+     *
+     * @param   columns     字段映射
+     * @return  实体类
+     */
+    override fun parseRow(columns: Map<String, Any?>): ENTITY {
+        if (clz == null) {
+            clz = gennericClass()
+        }
+
+        val entity = clz!!.newInstance() as ENTITY
+        for (field in clz!!.getAllDeclaredFields()) {
+            if (field.isAnnotationPresent(Column::class.java)) {
+                val anno = field.getAnnotation(Column::class.java)
+                field.isAccessible = true
+                val value = columns[anno.name] ?: continue
+                try {
+                    field.set(entity, when (field.type.simpleName) {
+                        "byte", "Byte" -> value.toByte()
+                        "double", "Double" -> value.toDouble()
+                        "float", "Float" -> value.toFloat()
+                        "int", "Integer" -> value.toInt()
+                        "long", "Long" -> value.toLong()
+                        "short", "Short" -> value.toShort()
+                        "Boolean" -> value.toBoolean()
+                        "String" -> value.toString()
+                        "List", "ArrayList" -> {
+                            val type = (field.genericType as ParameterizedType).actualTypeArguments[0]
+                            //SJsonUtil.fromJsonAsList<type>(value.toString())
+                            null
+                        }
+                        "HashMap", "Map" -> {
+                            //val type = object : TypeToken<HashMap<Any, Any>>(){}.type
+                            SJsonUtil.fromJson<HashMap<Any, Any>>(value.toString())
+                        }
+                        else ->{
+                             // SJsonUtil.fromJson<field.javaClass>(value.toString())
+                            null
+                        }
+                    })
+                } catch (e: Exception) {
+                    e.printStackTrace()
+                }
+                field.isAccessible = false
+            }
+        }
+        return entity
+    } // Function parseRow()
+} // Class SRowParser

+ 50 - 0
sybotan-android-database/src/main/java/com/sybotan/android/database/extensions/SSQLiteDatabaseExtension.kt

@@ -0,0 +1,50 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.database.extensions
+
+import android.database.sqlite.SQLiteDatabase
+import java.io.InputStream
+import java.io.InputStreamReader
+
+/**
+ * SQLiteDatabase扩展执行sql功能。
+ *
+ * @param   input       输入流
+ */
+fun SQLiteDatabase.execSQL(input: InputStream) {
+    val reader = InputStreamReader(input)
+    val sqlBuilder = StringBuilder()
+
+    reader.readLines().forEach {                            // 一次读取一行字符
+        if (!it.trim().startsWith("/*")) {          // 如果读取的行不是注释
+            sqlBuilder.append("\n$it")
+            if (it.trim().endsWith(";")) {          // 如果读到分号,则执行读取的sql
+                this.execSQL(sqlBuilder.toString())
+                sqlBuilder.delete(0, sqlBuilder.length)
+            }
+        }
+    }
+
+    return
+} // Function execSQL()

+ 26 - 0
sybotan-android-database/src/main/res/values/strings.xml

@@ -0,0 +1,26 @@
+<!--
+  ~ *********************************************************************************************************************
+  ~
+  ~               iFHS7.
+  ~              ;BBMBMBMc                  rZMBMBR              BMB
+  ~              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+  ~     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+  ~     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+  ~      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+  ~     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+  ~     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+  ~     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+  ~         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+  ~     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+  ~      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+  ~         i0BM: SWKHKGO  MBDv
+  ~           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+  ~              ,  XMW  ..
+  ~                  r                                                                     All rights reserved.
+  ~
+  ~ *********************************************************************************************************************
+  -->
+
+<resources>
+    <string name="app_name">sybotan-android-mvc</string>
+</resources>

+ 161 - 0
sybotan-android-graphy/build.gradle

@@ -0,0 +1,161 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+group rootProject.group
+version rootProject.version
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'org.jetbrains.dokka-android'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion ANDROID_SDK_VERSION.toInteger()
+    buildToolsVersion ANDROID_BUILD_TOOLS_VERSION
+
+
+    defaultConfig {
+        minSdkVersion ANDROID_MIN_SDK_VERSION.toInteger()
+        targetSdkVersion ANDROID_SDK_VERSION.toInteger()
+        versionCode SYBOTAN_CODE.toInteger()
+        versionName SYBOTAN_ANDROID_VERSION
+
+        testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
+
+    }
+
+    buildTypes {
+        release {
+//            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    // 解决lint语法检查错误(kotlin语法无法通过lint)
+    lintOptions {
+        abortOnError false
+    }
+
+    kotlinOptions {
+        jvmTarget = JVM_TARGET
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // kotlin 依赖
+    implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: KOTLIN_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // android支持
+    implementation group: 'com.android.support', name: 'appcompat-v7', version: ANDROID_SUPPORT_VERSION
+    implementation group: 'com.android.support', name: 'design', version: ANDROID_SUPPORT_VERSION
+    implementation  group: 'com.android.support', name: 'support-v4', version: ANDROID_SUPPORT_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // anko依赖
+    // https://mvnrepository.com/artifact/org.jetbrains.anko/anko-common
+    implementation group: 'org.jetbrains.anko', name: 'anko-commons', version: ANKO_VERSION
+
+    // https://mvnrepository.com/artifact/org.greenrobot/eventbus
+    implementation group: 'org.greenrobot', name: 'eventbus', version: EVENTBUS_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Sybotan依赖
+    implementation group: 'com.sybotan', name: 'sybotan-kotlin-base', version: SYBOTAN_KOTLIN_VERSION
+    implementation project(':sybotan-android-base')
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 文档生成
+// 源码
+task sourcesJar(type: Jar) {
+    group = 'jar'
+    from "${android.sourceSets.main.java.srcDirs}"
+    classifier = 'sources'
+}
+
+// 文档
+task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
+    jdkVersion = JDK_VERSION.toInteger()
+    noStdlibLink = true
+    outputFormat = 'javadoc'
+    outputDirectory = "$buildDir/javadoc"
+//    sourceDirs = files('src/main/java')
+}
+
+// 上成上传的文档jar包
+task javadocJar(type: Jar, dependsOn: dokkaJavadoc) {
+    group = 'jar'
+    classifier = 'javadoc'
+    from "${dokka.outputDirectory}"
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// maven 仓库管理
+
+// 设置上传的包,默认上传aar与pom
+artifacts {
+    // 上传源码包
+    // archives sourcesJar
+    // 上传文档包
+    // archives javadocJar
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            pom.groupId = project.group
+            pom.version = project.version
+            pom.artifactId = project.name
+            pom.project {
+                name project.name
+                packaging 'jar'
+                url SYBOTAN_URL
+                description '斯伯坦机器人世界 android Graphy框架库。'
+                licenses {
+                    license {
+                        name 'The Apache Software License, Version 2.0'
+                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                    }
+                }
+                developers {
+                    developer {
+                        id DEVELOPER_ID
+                        name DEVELOPER_NAME
+                    }
+                }
+            }
+            snapshotRepository(url: MAVEN_REPO_SNAPSHOT_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+            repository(url: MAVEN_REPO_RELEASE_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+        }
+    }
+}

+ 21 - 0
sybotan-android-graphy/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 24 - 0
sybotan-android-graphy/src/main/AndroidManifest.xml

@@ -0,0 +1,24 @@
+<!--
+  ~ *********************************************************************************************************************
+  ~
+  ~               iFHS7.
+  ~              ;BBMBMBMc                  rZMBMBR              BMB
+  ~              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+  ~     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+  ~     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+  ~      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+  ~     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+  ~     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+  ~     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+  ~         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+  ~     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+  ~      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+  ~         i0BM: SWKHKGO  MBDv
+  ~           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+  ~              ,  XMW  ..
+  ~                  r                                                                     All rights reserved.
+  ~
+  ~ *********************************************************************************************************************
+  -->
+
+<manifest package="com.sybotan.android.graphy"/>

+ 583 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/SGraphyItem.kt

@@ -0,0 +1,583 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy
+
+import android.graphics.Canvas
+import android.graphics.PointF
+import android.graphics.RectF
+import android.util.Log
+import android.view.MotionEvent
+import com.sybotan.android.graphy.enums.SGraphyItemFlag
+import com.sybotan.android.graphy.listeners.SGraphyItemPosListener
+import org.jetbrains.anko.doAsync
+import java.util.*
+
+/**
+ * SGraphy图形引擎Item类
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+open class SGraphyItem(parent: SGraphyItem? = null) {
+    /**
+     * 类对象
+     */
+    companion object {
+        private val TAG = SGraphyItem::class.java.name
+        /** 当前移动Item */
+        private var currentMoveItem: SGraphyItem? = null
+        /** 当前焦点Item */
+        private var currentFocusItem: SGraphyItem? = null
+
+        /**
+         * MotionEvent转子对象MotionEvent
+         *
+         * @param   child   子item对象
+         * @param   e       事件参数
+         * @return  子对象MotionEvent
+         */
+        fun toChildMotionEvent(child: SGraphyItem, e: MotionEvent): MotionEvent {
+            val se = MotionEvent.obtain(e)
+            se.setLocation((e.x - child.pos.x) / child.scale.x, (e.y - child.pos.y) / child.scale.y)
+            return se
+        } // Function toSceneMotionEvent()
+    } // companion object
+
+    /** 父节点 */
+    var parent:SGraphyItem? = parent
+        set(value) {
+            if (field == value) {   // 如果值没变化
+                return
+            }
+            if (null != field) {    // 如果原parent不为空
+                // 将节点从原parent节点中摘除
+                field!!.children.remove(this)
+            }
+            field = value
+
+            if (null != field) {    // 如果新parent不为空
+                // 将节点加入到新parent节点中
+                field!!.children.add(this)
+                field!!.children.sortByDescending { it.zOrder }
+            }
+            invalidate()
+        }
+
+    /** 子节点列表 */
+    val children =  ArrayList<SGraphyItem>()
+
+    /** item所属场景 */
+    var scene: SGraphyScene? = null
+        get() {
+            return if (null != parent) {
+                parent!!.scene
+            } else {
+                field
+            }
+        }
+
+    /** item位置 */
+    var pos: PointF = PointF(0f, 0f)
+        set(value) {
+            if (field == value) {   // 如果值没变化
+                return
+            }
+            field = value
+            invalidate()
+            posListener?.onMoveTo(field.x, field.y)
+        }
+    /** item位置变更监听器 */
+    var posListener: SGraphyItemPosListener? = null
+
+    /** z轴坐标,数值越大,越靠上 */
+    var zOrder = 0f
+        set(value) {
+            if (field == value) {   // 如果值没变化
+                return
+            }
+            field = value
+            if (null != parent) {   // 如果你节点不空
+                parent!!.children.sortByDescending { it.zOrder }
+            }
+            invalidate()
+        }
+    /** 缩放比例 */
+    var scale = PointF(1f, 1f)
+
+    /** item标志 */
+    var flags: EnumSet<SGraphyItemFlag> = EnumSet.noneOf(SGraphyItemFlag::class.java)
+
+    /** item是否可见 */
+    var isVisible = true
+        set(value) {
+            if (field == value) {   // 如果值没变化
+                return
+            }
+            field = value
+            invalidate()
+        }
+
+    /** item是否获得焦点 */
+    var focus = false
+        set(value) {
+            if (!flags.contains(SGraphyItemFlag.ItemIsFocusable)) {     // 如果item不能获得焦点
+                return
+            }
+            if (field == value) {   // 如果值没变化
+                return
+            }
+            field = value
+            if (value) {            // 如果获得焦点
+                currentFocusItem?.focus = false
+                onGetFocus()
+                currentFocusItem = this
+            } else {
+                onLostFocus()
+                if (currentFocusItem === this) {        // 如果当前焦点对象失去焦点
+                    currentFocusItem = null
+                }
+            }
+        }
+
+    /**
+     * 当前获得焦点的item对象
+     */
+    val focusItem: SGraphyItem?
+        get() = currentFocusItem
+
+    /** item是否被选中 */
+    var isSelected = false
+        set(value) {
+            if (field == value) {   // 如果值没变化
+                return
+            }
+            field = value
+            invalidate()
+        }
+
+
+    /**
+     * Item对象边界区域
+     *
+     * @return  边界区域
+     */
+    open fun boundingRect(): RectF {
+        return RectF()
+    } // Function boundingRect()
+
+    /**
+     * 更新Item
+     *
+     * @param   rect        更新区域
+     */
+    fun invalidate(rect: RectF = RectF()) {
+        scene?.update()
+        return
+    } // Function update()
+
+    /**
+     * 延迟刷新
+     *
+     * @param   delayed     延时的时间
+     */
+    fun postInvalidateDelayed(delayed: Long) {
+        doAsync {
+            Thread.sleep(delayed)
+            invalidate()
+        }
+
+        return
+    } // Function postInvalidateDelayed()
+
+    /**
+     * 隐藏item
+     */
+    fun hide() {
+        isVisible = false
+        return
+    } // Function hide()
+
+    /**
+     * 显示item
+     */
+    fun show() {
+        isVisible = true
+        return
+    } // Function show()
+
+    /**
+     * 移动item到指定位置
+     *
+     * @param   pos         新的位置
+     */
+    fun moveTo(pos: PointF)
+    {
+        this.pos = pos
+        return
+    } // Function move()
+
+    /**
+     * 移动item到指定位置
+     *
+     * @param   x           新位置的x坐标
+     * @param   y           新位置的y坐标
+     */
+    fun moveTo(x: Float, y: Float)
+    {
+        pos = PointF(x, y)
+        return
+    } // Function move()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 坐标换算相关操作
+    /**
+     * 将场景中的xy坐标转换成item坐标。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
+     *
+     * @param   x       场景中的横坐标
+     * @param   y       场景中的纵坐标
+     *
+     * @return  在item中的坐标
+     */
+    fun mapFromScene(x: Float, y: Float): PointF {
+        val list = itemPath()
+        var x0 = x
+        var y0 = y
+        for (item in list) {
+            x0 = (x0 - item.pos.x) / item.scale.x
+            y0 = (y0 - item.pos.y) / item.scale.y
+        }
+
+        return PointF(x0, y0)
+    } // Function mapFromScene()
+
+    /**
+     * 将场景中的xy坐标转换成item坐标。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
+     *
+     * @param   point   场景中的坐标
+     *
+     * @return  在item中的坐标
+     */
+    fun mapFromScene(point: PointF): PointF {
+        return PointF(point.x, point.y)
+    } // Function mapFromScene()
+
+    /**
+     * 将item中的xy坐标转换成场景坐标。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
+     *
+     * @param   x       item中的横坐标
+     * @param   y       item中的纵坐标
+     *
+     * @return  在场景中的坐标
+     */
+    fun mapToScene(x: Float, y: Float): PointF {
+        if (null == parent) {
+            return PointF(x, y)
+        }
+
+        return parent!!.mapToScene(x * scale.x + pos.x, y * scale.y + pos.y)
+    } // Function mapToScene()
+
+    /**
+     * 将item中的xy坐标转换成场景坐标。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
+     *
+     * @param   point   item中的坐标
+     *
+     * @return  在场景中的坐标
+     */
+    fun mapToScene(point: PointF): PointF {
+        return PointF(point.x, point.y)
+    } // Function mapToScene()
+
+    /**
+     * 获得item的路径节点列表。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
+     *
+     * @return  从根节点到当前节点的路径节点列表。
+     */
+    fun itemPath(): MutableList<SGraphyItem> {
+        if (null != parent) {
+            val list = parent!!.itemPath()
+            list.add(this)
+            return list
+        }
+
+        return mutableListOf(this)
+    } // Function itemPath()
+
+    /**
+     * 判断item是否包含点x,y
+     *
+     * @param   x       横坐标(当前item)
+     * @param   y       纵坐标(当前item)
+     *
+     * @return  true包含;false不包含。
+     */
+    open fun contains(x: Float, y: Float): Boolean {
+        return boundingRect().contains(x, y)
+    } // Function ()
+
+    /**
+     * 判断item是否包含点x,y
+     *
+     * @param   point   点坐标(当前item)
+     *
+     * @return  true包含;false不包含。
+     */
+    fun contains(point: PointF): Boolean {
+        return contains(point.x, point.y)
+    } // Function ()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 事件回调
+    /**
+     * Item绘制操作
+     *
+     * @param   canvas      画布对象
+     * @param   rect        更新区域
+     */
+    open fun onDraw(canvas : Canvas, rect: RectF)
+    {
+        for (i in 1 .. children.size) {        // 倒序依次取item列中的所有item。将所有item的边界做并交处理。
+            val item = children[children.size - i]
+            if (!item.isVisible) {      // 如果对象不可见
+                continue
+            }
+
+            try {
+                // 保存画布状态
+                canvas.save()
+                // item位移到指定位置绘制
+                canvas.translate(item.pos.x, item.pos.y)
+                // 设置绘制区域
+                canvas.clipRect(item.boundingRect())
+                // 绘制item
+                item.onDraw(canvas, rect)
+                // 恢复画布状态
+                canvas.restore()
+            } catch (e: Exception) {
+                e.printStackTrace()
+            }
+        }
+        return
+    } // Function paint()
+
+    /**
+     * 获得焦点变更回调
+     */
+    open fun onGetFocus() {
+        // DO NOTHING
+        return
+    } // Function onGetFocus()
+
+    /**
+     * 失去焦点变更回调
+     */
+    open fun onLostFocus() {
+        // DO NOTHING
+        return
+    } // Function onLostFocus()
+
+    /**
+     * 用户按下触发.
+     *
+     * @param   e       按下手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onDown(e: MotionEvent): Boolean {
+        for (item in children) {
+            if (!item.isVisible) {      // 如果对象不可见
+                continue
+            }
+            val ce = toChildMotionEvent(item, e)
+            if (item.contains(ce.x, ce.y)                    // 如果点在子项目上
+                    && item.onDown(ce)) {                    // 且子项目处理了事件
+                return true
+            }
+        }
+
+        if (flags.contains(SGraphyItemFlag.ItemIsFocusable)) {    // 如果是可获得焦点Item
+            focus = true
+            return true
+        }
+
+        return false
+    } // Function onDown()
+
+    /**
+     * 用户释放触发.
+     *
+     * @param   e       释放手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onSingleTapUp(e: MotionEvent): Boolean {
+        Log.d(TAG, "onSingleTapUp: releaseItem")
+        releaseItem()
+
+        for (item in children) {
+            if (!item.isVisible) {      // 如果对象不可见
+                continue
+            }
+            val ce = toChildMotionEvent(item, e)
+            if (item.contains(ce.x, ce.y)                                   // 如果点在子项目上
+                    && item.onSingleTapUp(ce)) {                            // 且子项目处理了事件
+                return true
+            }
+        }
+
+        return false
+    } // Function onSingleTapUp()
+
+    /**
+     * 用户点击触发
+     *
+     * @param   e       手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onShowPress(e: MotionEvent): Boolean {
+        Log.d(TAG, "onShowPress: releaseItem")
+        for (item in children) {
+            if (!item.isVisible) {      // 如果对象不可见
+                continue
+            }
+            val ce = toChildMotionEvent(item, e)
+            if (item.contains(ce.x, ce.y)                                   // 如果点在子项目上
+                    && item.onShowPress(ce)) {                              // 且子项目处理了事件
+                return true
+            }
+        }
+
+        return false
+    } // Function onShowPress()
+
+    /**
+     * 长按触摸屏超过一定时间。
+     *
+     * @param   e       长按手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onLongPress(e: MotionEvent): Boolean {
+        Log.d(TAG, "onLongPress: releaseItem")
+        releaseItem()
+
+        for (item in children) {
+            if (!item.isVisible) {      // 如果对象不可见
+                continue
+            }
+            val ce = toChildMotionEvent(item, e)
+            if (item.contains(ce.x, ce.y)                                   // 如果点在子项目上
+                    && item.onLongPress(ce)) {                              // 且子项目处理了事件
+                return true
+            }
+        }
+
+        return false
+    } // Function onLoagPress()
+
+    /**
+     * 用户按下触摸屏,并拖动。
+     *
+     * @param   e1          第1个 ACTION_DOWN 手势事件
+     * @param   e2          最后一个 ACTION_MOVE 手势事件
+     * @param   distanceX   X轴上的移动距离,单位:像素
+     * @param   distanceY   Y轴上的移动距离,单位:像素
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
+        if (flags.contains(SGraphyItemFlag.ItemIsMovable) && currentMoveItem === this) {
+            moveTo(pos.x - distanceX * scale.x, pos.y - distanceY * scale.y)
+            return true
+        }
+
+        for (item in children) {
+            if (!item.isVisible) {      // 如果对象不可见
+                continue
+            }
+            val ce1 = toChildMotionEvent(item, e1)
+            val ce2 = toChildMotionEvent(item, e2)
+            // 如果点在子项目上且子项目目处理了事件
+            if (item.contains(ce2.x, ce2.y)              // 如果点在子项目上
+                    && item.onScroll(ce1, ce2, distanceX / item.scale.x, distanceY / item.scale.y)) {   // 且子项目处理了事件
+                return true
+            }
+        }
+
+        if (flags.contains(SGraphyItemFlag.ItemIsMovable) && null == currentMoveItem) {    // 如果是可移动Item
+            grabItem(this)
+            currentMoveItem = this
+            return true
+        }
+
+        return false
+    } // Function onScroll()
+
+    /**
+     * 用户按下触摸屏、快速移动后松开。即滑动操作。
+     *
+     * @param   e1          第1个 ACTION_DOWN 手势事件
+     * @param   e2          最后一个 ACTION_MOVE 手势事件
+     * @param   velocityX   X轴上的移动速度,像素/秒
+     * @param   velocityY   Y轴上的移动速度,像素/秒
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+     open fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
+        Log.d(TAG, "onFling: releaseItem")
+        releaseItem()
+
+        for (item in children) {
+            if (!item.isVisible) {      // 如果对象不可见
+                continue
+            }
+            val ce1 = toChildMotionEvent(item, e1)
+            val ce2 = toChildMotionEvent(item, e2)
+            // 如果点在子项目上且子项目目处理了事件
+            if (item.contains(ce2.x, ce2.y)              // 如果点在子项目上
+                    && item.onFling(ce1, ce2, velocityX / item.scale.x, velocityY / item.scale.y)) {   // 且子项目处理了事件
+                return true
+            }
+        }
+
+        return false
+    } // Function onFling()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 私有方法
+    /**
+     * 锁定item
+     *
+     * @param   item    被锁定的item
+     */
+    private fun grabItem(item: SGraphyItem) {
+        Log.d(TAG, "grabItem")
+        isSelected = true
+        scene!!.grabItem = item
+        return
+    } // Function grabItem
+
+    /**
+     * 释放被锁定的item
+     */
+    private fun releaseItem() {
+        isSelected = false
+        scene!!.grabItem = null
+        return
+    } // Function grabItem
+} // Class SGraphyItem

+ 336 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/SGraphyScene.kt

@@ -0,0 +1,336 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy
+
+import android.graphics.*
+import android.util.Log
+import android.view.MotionEvent
+import com.sybotan.android.base.extensions.adjusted
+import com.sybotan.android.graphy.data.SLineF
+import com.sybotan.android.graphy.enums.SGraphyItemFlag
+import com.sybotan.android.graphy.items.SGraphyImageItem
+import com.sybotan.android.graphy.items.SGraphyLineItem
+import com.sybotan.android.graphy.items.SGraphyRectItem
+
+/**
+ * SGraphy图形引擎场景类
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+open class SGraphyScene {
+
+    /**
+     * 类对象
+     */
+    companion object {
+        private val TAG = SGraphyScene::class.java.name
+    }
+
+    /** 根节点 */
+    private val rootNode: SGraphyItem by lazy {
+        val item = SGraphyItem()
+        item.scene = this
+        item.flags.add(SGraphyItemFlag.ItemIsFocusable)
+        item
+    }
+    /** 当前被锁定的item */
+    var grabItem: SGraphyItem? = null
+    /** 指向scene被加载到的视图 */
+    var view: SGraphyView? = null
+
+    /**
+     * 绘制场景
+     *
+     * @param   canvas      画布
+     * @param   rect        更新绘制区域
+     */
+    open fun drawScene(canvas: Canvas, rect: RectF) {
+        rootNode.onDraw(canvas, rect)
+        return
+    } // Function paint()
+
+    /**
+     * 绘制背景
+     *
+     * @param   canvas      画布
+     * @param   rect        更新绘制区域
+     */
+    open fun drawBackground(canvas: Canvas, rect: RectF) {
+        canvas.drawColor(Color.WHITE)
+        return
+    } // Function drawBackground()
+
+    /**
+     * 绘制前景
+     *
+     * @param   canvas      画布
+     * @param   rect        更新区域
+     */
+    open fun drawForeground(canvas: Canvas, rect: RectF) {
+        return
+    } // Function drawBackground()
+
+    /**
+     * 添加item对象到场景。
+     *
+     * @param   item        添加的对象
+     */
+    fun addItem(item: SGraphyItem) {
+        item.parent = rootNode
+        return
+    } // Function addItem()
+
+    /**
+     * 添加图片item对象到场景
+     *
+     * @param   image       图片
+     * @return  添加的SGraphyImageItem对象
+     */
+    fun addImageItem(image: Bitmap): SGraphyImageItem {
+        val item = SGraphyImageItem(image)
+        addItem(item)
+        return item
+    } // Function addImageItem()
+
+    /**
+     * 添加线段item对象到场景
+     *
+     * @param   line        线段
+     * @return  添加的SGraphyLineItem对象
+     */
+    fun addLineItem(line: SLineF): SGraphyLineItem {
+        val item = SGraphyLineItem(line)
+        addItem(item)
+        return item
+    } // Function addLineItem()
+
+    /**
+     * 添加线段item对象到场景
+     *
+     * @param   x1          端点1的x坐标
+     * @param   y1          端点1的y坐标
+     * @param   x2          端点2的x坐标
+     * @param   y2          端点2的y坐标
+     * @return  添加的SGraphyLineItem对象
+     */
+    fun addLineItem(x1: Float, y1: Float, x2: Float, y2: Float): SGraphyLineItem {
+        val item = SGraphyLineItem(x1, y1, x2, y2)
+        addItem(item)
+        return item
+    } // Function addLineItem()
+
+    /**
+     * 添加矩item对象到场景
+     *
+     * @param   rect        矩形
+     * @return  添加的SGraphyRectItem对象
+     */
+    fun addRectItem(rect: RectF): SGraphyRectItem {
+        val item = SGraphyRectItem(rect)
+        addItem(item)
+        return item
+    } // Function addRectItem()
+
+    /**
+     * 添加矩item对象到场景
+     *
+     * @param   left        左坐标
+     * @param   top         顶坐标
+     * @param   right       右坐标
+     * @param   bottom      底坐标
+     * @return  添加的SGraphyRectItem对象
+     */
+    fun addRectItem(left: Float, top: Float, right: Float, bottom: Float): SGraphyRectItem {
+        val item = SGraphyRectItem(left, top, right, bottom)
+        addItem(item)
+        return item
+    } // Function addRectItem()
+
+    /**
+     * 从场景中移除Item。
+     *
+     * @param   item        被移除的对象
+     */
+    fun removeItem(item: SGraphyItem) {
+        item.parent = null
+        return
+    } // Function removeItem()
+
+    /**
+     * 返回场景的item边界。即所有item边界的并集。
+     *
+     * @return  场景的item边界。
+     */
+    fun worldRect(): RectF {
+        val rect = RectF()
+
+        for (item in rootNode.children) {        // 依次取item列中的所有item。将所有item的边界做并焦处理。
+            rect.union(item.boundingRect().adjusted(item.pos))
+        }
+
+        return rect
+    } // Function worldsRect()
+
+    /**
+     * 更新
+     */
+    fun update() {
+        Log.d(TAG, "update")
+        return
+    } // Function update()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 手势相关操作
+    /**
+     * 用户按下屏幕就会触发.
+     *
+     * @param   e       按下手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onDown(e: MotionEvent): Boolean {
+        if (null != grabItem) {     // 如果当前锁定item不为空
+            return true
+        }
+
+        return rootNode.onDown(e)
+    } // Function onDown()
+
+    /**
+     * 用户释放屏幕触发.
+     *
+     * @param   e       释放手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onSingleTapUp(e: MotionEvent): Boolean {
+        if (null != grabItem) {     // 如果当前锁定item不为空
+            val ie = toGrabItemMotionEvent(grabItem!!, e)
+            return grabItem!!.onSingleTapUp(ie)
+        }
+
+        return rootNode.onSingleTapUp(e)
+    } // Function onSingleTapUp()
+
+    /**
+     *
+     * @param   e       手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    fun onShowPress(e: MotionEvent): Boolean {
+        if (null != grabItem) {     // 如果当前锁定item不为空
+            val ie = toGrabItemMotionEvent(grabItem!!, e)
+            grabItem!!.onShowPress(ie)
+            return true
+        }
+
+        return rootNode.onShowPress(e)
+    } // Function onShowPress()
+
+    /**
+     * 长按触摸屏超过一定时间。
+     *
+     * @param   e       长按手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onLongPress(e: MotionEvent): Boolean {
+        if (null != grabItem) {     // 如果当前锁定item不为空
+            val ie = toGrabItemMotionEvent(grabItem!!, e)
+            return grabItem!!.onLongPress(ie)
+        }
+
+        return rootNode.onLongPress(e)
+    } // Function onLongPress()
+    /**
+     * 用户按下触摸屏,并拖动。
+     *
+     * @param   e1          第1个 ACTION_DOWN 手势事件
+     * @param   e2          最后一个 ACTION_MOVE 手势事件
+     * @param   distanceX   X轴上的移动距离,单位:像素
+     * @param   distanceY   Y轴上的移动距离,单位:像素
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
+        if (null != grabItem) {     // 如果当前锁定item不为空
+            val ie1 = toGrabItemMotionEvent(grabItem!!, e1)
+            val ie2 = toGrabItemMotionEvent(grabItem!!, e2)
+            val distance = toGrabItemDistance(grabItem!!, distanceX, distanceY)
+            return grabItem!!.onScroll(ie1, ie2, distance.x, distance.y)
+        }
+
+        return rootNode.onScroll(e1, e2, distanceX, distanceY)
+    } // Function onScroll()
+
+    /**
+     * 用户按下触摸屏、快速移动后松开。即滑动操作。
+     *
+     * @param   e1          第1个 ACTION_DOWN 手势事件
+     * @param   e2          最后一个 ACTION_MOVE 手势事件
+     * @param   velocityX   X轴上的移动速度,像素/秒
+     * @param   velocityY   Y轴上的移动速度,像素/秒
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
+        if (null != grabItem) {     // 如果当前锁定item不为空
+            val ie1 = toGrabItemMotionEvent(grabItem!!, e1)
+            val ie2 = toGrabItemMotionEvent(grabItem!!, e2)
+            val velocity = toGrabItemDistance(grabItem!!, velocityX, velocityY)
+            return grabItem!!.onFling(ie1, ie2, velocity.x, velocity.y)
+        }
+
+        return rootNode.onFling(e1, e2, velocityX, velocityY)
+    } // Function onFling()
+
+    /**
+     * 转换场景事件坐标到指定Item坐标事件
+     *
+     * @param   item        指定的item对象
+     * @param   e           场景事件
+     * @return  item事件
+     */
+    private fun toGrabItemMotionEvent(item: SGraphyItem, e: MotionEvent): MotionEvent {
+        val se = MotionEvent.obtain(e)
+        val p = item.mapFromScene(e.x, e.y)
+        se.setLocation(p.x, p.y)
+        return se
+    } // Function toItemMotionEvent()
+
+    /**
+     * 转换场景中的变化距到指定Item中的距离变化
+     *
+     * @param   item        指定的item对象
+     * @param   distanceX   X轴向上的距
+     * @param   distanceY   X轴向上的距
+     * @return  item事件
+     */
+    private fun toGrabItemDistance(item: SGraphyItem, distanceX: Float, distanceY: Float): PointF {
+        val p = PointF(distanceX, distanceY)
+        val list = item.itemPath()
+        for (child in list) {
+            p.x = p.x / child.scale.x
+            p.y = p.y / child.scale.y
+        }
+
+        return p
+    } // Function toGrabItemDistance()
+
+} // Class SGraphyScene

+ 492 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/SGraphyView.kt

@@ -0,0 +1,492 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy
+
+import android.content.Context
+import android.graphics.*
+import android.util.AttributeSet
+import android.util.Log
+import android.view.*
+import android.view.MotionEvent
+import com.sybotan.android.graphy.enums.SGraphyViewTouchState
+import com.sybotan.android.graphy.events.SGraphyViewMoveEvent
+import com.sybotan.android.graphy.events.SGraphyViewZoomEvent
+import org.greenrobot.eventbus.EventBus
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * SGraphy图形引擎视图类
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+open class SGraphyView(context: Context, attrs: AttributeSet? = null)
+    : SurfaceView(context, attrs), SurfaceHolder.Callback, View.OnTouchListener, GestureDetector.OnGestureListener, Runnable {
+
+    /**
+     * 类对象
+     */
+    companion object {
+        private val TAG = SGraphyView::class.java.name
+    } // companion object
+
+    /** 指向加载到视图中的场景 */
+    var scene: SGraphyScene? = null
+        set(value) {
+            if (field == value) {       // 如果场景没有变化
+                return
+            }
+            field?.view = null
+            value?.view = this
+            // 保存场景
+            field = value
+        }
+
+    /** 是否支持二指缩放操作 */
+    var canZoom = true
+    /** 缩放比例 */
+    var scale = 1f
+        get() = max(min(field, maxScale), minScale)
+    /** 最小缩放比例 */
+    var minScale = 0.1f
+        set(value) {
+            field = min(value, maxScale)
+        }
+    /** 最大缩放比例 */
+    var maxScale = 10f
+        set(value) {
+            field = max(value, minScale)
+        }
+
+    /** 是否支持移动场景操作 */
+    var canMove = true
+    /** 场景原点位置在视图中的位置 */
+    var pos = PointF(0f, 0f)
+        get() {
+            field.x = min(max(field.x, minPos.x), maxPos.x)
+            field.y = min(max(field.y, minPos.y), maxPos.y)
+            return field
+        }
+    /** 视图原点最小值 */
+    var minPos = PointF(Float.MIN_VALUE, Float.MIN_VALUE)
+    /** 视图原点最大值 */
+    var maxPos = PointF(Float.MAX_VALUE, Float.MAX_VALUE)
+
+
+    /** 手执状态 */
+    private var touchState = SGraphyViewTouchState.NoneState
+
+    /** 是否在绘制状态 */
+    private var isDrawing = false
+    /** 手势解析器 */
+    private val gesture by lazy {
+        GestureDetector(this)
+    }
+
+    /** 未缩放时二指间距离 */
+    private var beforeLength = 0.0f
+    /** 缩放后二指间距离 */
+    private var afterLength = 0.0f
+    /** 后台绘制线程 */
+    private var paintThread: Thread? = null
+
+    /** 默认构造函数 */
+    init {
+        Log.d(TAG, "init")
+        initView()
+    } // init
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Surface相关操作
+    /**
+     * Surface创建成功时回调函数。
+     *
+     * @param   holder      surface创建成功时的保持器。
+     */
+    override fun surfaceCreated(holder: SurfaceHolder?) {
+        Log.d(TAG, "surfaceCreated")
+
+        isDrawing = true
+        // 启动后台绘制线程
+        paintThread = Thread(this)
+        paintThread!!.start()
+        return
+    } // Function surfaceCreated()
+
+    /**
+     * Surface变现时回调函数。
+     *
+     * @param   holder      Surface保持器
+     * @param   format      新的surface格式
+     * @param   width       宽度
+     * @param   height      高度
+     */
+    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
+        Log.d(TAG, "surfaceChanged:width=$width, height=$height")
+
+        return
+    } // Function surfaceChanged()
+
+    /**
+     * Surface被销毁时回调函数。
+     *
+     * @param   holder      surface销毁时的保持器。
+     */
+    override fun surfaceDestroyed(holder: SurfaceHolder?) {
+        Log.d(TAG, "surfaceDestroyed")
+
+        paintThread!!.interrupt()
+        paintThread = null
+        return
+    } // Function surfaceDestroyed()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 坐标换算相关操作
+    /**
+     * 将场景中的xy坐标转换成视图坐标。
+     *
+     * @param   x       场景中的横坐标
+     * @param   y       场景中的纵坐标
+     *
+     * @return  在item中的坐标
+     */
+    fun mapFromScene(x: Float, y: Float): PointF {
+        return PointF(x * scale + pos.x, y * scale + pos.y)
+    } // Function mapFromScene()
+
+    /**
+     * 将场景中的xy坐标转换成视图坐标。
+     *
+     * @param   point   场景中的坐标
+     * @return  在item中的坐标
+     */
+    fun mapFromScene(point: PointF): PointF {
+        return mapFromScene(point.x, point.y)
+    } // Function mapFromScene()
+
+    /**
+     * 将item中的xy坐标转换成场景坐标。
+     *
+     * @param   x       item中的横坐标
+     * @param   y       item中的纵坐标
+     * @return  在场景中的坐标
+     */
+    fun mapToScene(x: Float, y: Float): PointF {
+        return PointF((x - pos.x) / scale, (y - pos.y) / scale)
+    } // Function mapToScene()
+
+    /**
+     * 将item中的xy坐标转换成场景坐标。
+     *
+     * @param   point   item中的坐标
+     * @return  在场景中的坐标
+     */
+    fun mapToScene(point: PointF): PointF {
+        return mapToScene(point.x, point.y)
+    } // Function mapToScene()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 手势相关操作
+    /**
+     * 用户对视图进行触摸操作时触发
+     *
+     * @param   v       触摸的视图对象
+     * @param   event   保存触摸事件参数
+     * @return  如果处理了该事件返回true, 否则返回false
+     */
+    override fun onTouch(v: View, event: MotionEvent): Boolean {
+        try {
+            return when (event.action and MotionEvent.ACTION_MASK) {
+                MotionEvent.ACTION_POINTER_DOWN -> {        // 多点触控
+                    if (canZoom) {      // 如果可以缩放
+                        touchState = SGraphyViewTouchState.ZoomState
+                        beforeLength = getDistance(event)       // 获取两点的距离
+                    }
+                    true
+                }
+                MotionEvent.ACTION_POINTER_UP -> {          // 多点释放
+                    touchState = SGraphyViewTouchState.NoneState
+                    true
+                }
+                MotionEvent.ACTION_MOVE -> {
+                    if (SGraphyViewTouchState.ZoomState == touchState && canZoom) { // 两点绽放操作
+                        viewZoom(event)
+                        true
+                    } else {
+                        gesture.onTouchEvent(event)
+                    }
+                }
+                else -> {
+                    gesture.onTouchEvent(event)
+                }
+            }
+        } catch (e: Exception) {
+            return true
+        }
+    } // Function onTouch()
+
+    /**
+     * 用户点击屏幕时触发。
+     *
+     * @param   e   触摸事件
+     */
+    override fun onShowPress(e: MotionEvent) {
+        scene?.onShowPress(toSceneMotionEvent(e))
+        return
+    } // Function onShowPress()
+
+    /**
+     * 用户按下屏幕就会触发.
+     *
+     * @param   e       按下手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    override fun onDown(e: MotionEvent): Boolean {
+        scene?.onDown(toSceneMotionEvent(e))
+        return true
+    } // Function onDown()
+
+    /**
+     * 用户释放屏幕触发.
+     *
+     * @param   e       释放手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    override fun onSingleTapUp(e: MotionEvent): Boolean {
+        scene?.onSingleTapUp(toSceneMotionEvent(e))
+        return true
+    } // Function onSingleTapUp()
+
+    /**
+     * 长按触摸屏超过一定时间。
+     *
+     * @param   e       长按手势事件。
+     */
+    override fun onLongPress(e: MotionEvent) {
+        scene?.onLongPress(toSceneMotionEvent(e))
+        return
+    } // Function onLoagPress()
+
+    /**
+     * 用户按下触摸屏,并拖动。
+     *
+     * @param   e1          第1个 ACTION_DOWN 手势事件
+     * @param   e2          最后一个 ACTION_MOVE 手势事件
+     * @param   distanceX   X轴上的移动距离,单位:像素
+     * @param   distanceY   Y轴上的移动距离,单位:像素
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
+        if (scene == null) {
+            return true
+        }
+
+        // 如果场景处理了该事件,则返回
+        if (scene!!.onScroll(toSceneMotionEvent(e1), toSceneMotionEvent(e2), distanceX / scale, distanceY / scale)) {
+            return true
+        }
+
+        if (canMove) {      // 如果可以移动场景
+            // 移动场景
+            pos.x = pos.x - distanceX
+            pos.y = pos.y - distanceY
+            EventBus.getDefault().post(SGraphyViewMoveEvent(this, pos.x, pos.y))
+        }
+
+        return true
+    } // Function onScroll()
+
+    /**
+     * 用户按下触摸屏、快速移动后松开。即滑动操作。
+     *
+     * @param   e1          第1个 ACTION_DOWN 手势事件
+     * @param   e2          最后一个 ACTION_MOVE 手势事件
+     * @param   velocityX   X轴上的移动速度,像素/秒
+     * @param   velocityY   Y轴上的移动速度,像素/秒
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
+        scene?.onFling(toSceneMotionEvent(e1), toSceneMotionEvent(e2), velocityX / scale, velocityY / scale)
+        return true
+    } // Function onFling()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 绘制相关操作
+    /**
+     * 重载线程run函数。
+     */
+    override fun run() {
+        while(null != paintThread && !paintThread!!.isInterrupted) {      // 循环控制绘制
+            var canvas: Canvas? = null
+            try {
+                Thread.sleep(100)
+                canvas = holder.lockCanvas()
+                if (null == canvas) {   // 如果canvas为空
+                    continue
+                }
+                synchronized(holder) {
+                    drawScene(canvas)
+                }
+            } catch (e: Exception) {
+                e.printStackTrace()
+
+            } finally {
+                if (null != canvas) {
+                    holder.unlockCanvasAndPost(canvas)
+                }
+            }
+        }
+
+        return
+    } // Function run()
+
+    /**
+     * 后台场景绘制线程
+     *
+     * @param   canvas      画布
+     */
+    open fun drawScene(canvas: Canvas) {
+        // 绘制背景
+        canvas.save()
+        scene?.drawBackground(canvas, RectF())
+        canvas.restore()
+
+        // 绘制场景
+        canvas.save()
+        canvas.translate(pos.x, pos.y)
+        canvas.scale(scale, scale)
+        scene?.drawScene(canvas, RectF())
+        canvas.restore()
+
+        // 绘制前景
+        canvas.save()
+        scene?.drawForeground(canvas, RectF())
+        canvas.restore()
+        return
+    } // Function drawScene()
+
+    /**
+     * 缩放视图时计算视图的位置与缩放比例
+     *
+     * @param   zoom        缩放比例
+     * @param   midPoint    缩放计算的中心点
+     */
+    fun scaleByPoint(zoom: Float, midPoint: PointF) {
+        var z = zoom
+        /**
+         * 缩放比例在最小比例和最大比例范围内
+         */
+        when {
+            scale * zoom >= maxScale -> {
+                z = maxScale / scale
+                scale = maxScale
+            }
+            scale * zoom <= minScale -> {
+                z = minScale / scale
+                scale = minScale
+            }
+            else -> {
+                scale *= zoom
+            }
+        }
+
+        pos.x = midPoint.x - (midPoint.x - pos.x) * z
+        pos.y = midPoint.y - (midPoint.y - pos.y) * z
+
+        EventBus.getDefault().post(SGraphyViewZoomEvent(this, scale))
+        EventBus.getDefault().post(SGraphyViewMoveEvent(this, pos.x, pos.y))
+        return
+    } // Function scaleByPoint()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 初始化相关操作
+    /**
+     * 初始化视图控件
+     */
+    private fun initView() {
+        holder.addCallback(this)
+        // 设置支持透明色
+        holder.setFormat(PixelFormat.TRANSPARENT)
+        isFocusable = true
+        isFocusableInTouchMode = true
+
+        setOnTouchListener(this)
+
+        return
+    } // Function initView()
+
+    /**
+     * 缩放视图
+     *
+     * @param   event       触摸事件
+     */
+    private fun viewZoom(event: MotionEvent): Boolean {
+        if (touchState == SGraphyViewTouchState.ZoomState) {
+            afterLength = getDistance(event)// 获取两点的距离
+            val gapLenght = afterLength - beforeLength// 变化的长度
+            if (Math.abs(gapLenght) > 5f && beforeLength != 0.0f) {
+                val tempScale = afterLength / beforeLength// 求的缩放的比例
+                val p = getMiddlePoint(event)
+                scaleByPoint(tempScale, p)    //重设置
+                beforeLength = afterLength
+            }
+        }
+        return true
+    } // Function onTouchMove()
+
+    /**
+     * 获取两手指之间的距离
+     *
+     * @param   event       触摸事件
+     * @return 两手指之间的距离
+     */
+    private fun getDistance(event: MotionEvent): Float {
+        val x = (event.getX(0) - event.getX(1)).toDouble()
+        val y = (event.getY(0) - event.getY(1)).toDouble()
+        return Math.sqrt(x * x + y * y).toFloat()
+    } // Function getDistance()
+
+    /**
+     * 获得两个手指触摸点的中点坐标
+     *
+     * @param   event
+     * @return  得到视图的x坐标中点
+     */
+    private fun getMiddlePoint(event: MotionEvent): PointF {
+        return PointF((event.getX(1) + event.getX(0)) / 2f,
+                (event.getY(1) + event.getY(0)) / 2f)
+    } // Function getMiddlePoint()
+
+    /**
+     * MotionEvent转场景对象MotionEvent
+     *
+     * @param   e       MotionEvent
+     * @return  子对象MotionEvent
+     */
+    private fun toSceneMotionEvent(e: MotionEvent): MotionEvent {
+        val se = MotionEvent.obtain(e)
+        se.setLocation((e.x - pos.x) / scale, (e.y - pos.y) / scale)
+        return se
+    } // Function toSceneMotionEvent()
+} // Class SGraphyView

+ 52 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/data/SLineF.kt

@@ -0,0 +1,52 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.data
+
+import android.graphics.PointF
+
+/**
+ * 线段数据类
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+data class SLineF(
+        /** 顶点1 X坐标 */
+        var x1: Float = 0f,
+        /** 顶点1 Y坐标 */
+        var y1: Float = 0f,
+        /** 顶点2 X坐标 */
+        var x2: Float = 0f,
+        /** 顶点2 Y坐标 */
+        var y2: Float = 0f
+)
+{
+
+    /** 顶点1 坐标 */
+    var p1: PointF = PointF(x1, y1)
+        get() = PointF(x1,y1)
+
+    /** 顶点2 坐标 */
+    var p2: PointF = PointF(x2, y2)
+        get() = PointF(x2, y2)
+} // Data Class SLineF

+ 38 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/enums/SGraphyItemFlag.kt

@@ -0,0 +1,38 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.enums
+
+/**
+ * SGraphyItem标志
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+enum class SGraphyItemFlag(flag: Int) {
+    /** 可以移动 */
+    ItemIsMovable(0x01),
+    /** 可被选择 */
+    ItemIsSelectable(0x02),
+    /** 可以获得焦点 */
+    ItemIsFocusable(0x04)
+} // Enum SGraphyItemFlag

+ 38 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/enums/SGraphyViewTouchState.kt

@@ -0,0 +1,38 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.enums
+
+/**
+ * SGraphyView手柄状态
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+enum class SGraphyViewTouchState {
+    /** 标准状态 */
+    NoneState,
+    /** 移动状态 */
+    MoveState,
+    /** 缩放状态 */
+    ZoomState
+} // Enum SGraphyItemFlag

+ 40 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/events/SGraphyViewMoveEvent.kt

@@ -0,0 +1,40 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.events
+
+import com.sybotan.android.graphy.SGraphyView
+
+/**
+ * 视图原点移动事件
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+data class SGraphyViewMoveEvent(
+        /** 事件来源视图 */
+        val view: SGraphyView,
+        /** 原点x坐标 */
+        val x: Float,
+        /** 原点y坐标 */
+        val y: Float
+) // Class SGraphyViewMoveEvent

+ 38 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/events/SGraphyViewZoomEvent.kt

@@ -0,0 +1,38 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.events
+
+import com.sybotan.android.graphy.SGraphyView
+
+/**
+ * 视图缩放事件
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+data class SGraphyViewZoomEvent(
+        /** 事件来源视图 */
+        val view: SGraphyView,
+        /** 视图缩放比例 */
+        val zoom: Float
+) // Class SGraphyViewZoomEvent

+ 250 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphyClockItem.kt

@@ -0,0 +1,250 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.items
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.RectF
+import android.util.SizeF
+import com.sybotan.android.graphy.SGraphyItem
+import java.util.*
+import kotlin.math.*
+
+/**
+ * SGraphy图形引擎时钟Item
+ *
+ * @author  庞利祥(sybotan@126.com)
+ *
+ * @param   size        时间大小
+ * @param   parent      指向父节点
+ */
+class SGraphyClockItem(var size: SizeF, parent: SGraphyItem? = null) : SGraphyItem(parent) {
+    /** 是否显示刻度 */
+    var isShowScale = true
+    /** 刻度颜色 */
+    var scaleColor = Color.BLACK
+    /** 刻度文本颜色 */
+    var textColor = Color.BLACK
+    /** 时针颜色 */
+    var hourColor = Color.BLACK
+    /** 分针颜色 */
+    var minuteColor = Color.BLACK
+    /** 秒针颜色 */
+    var secondColor = Color.RED
+    /** 是否显示秒针 */
+    var isShowSecond = true
+    /** 是否平滑移动秒针 */
+    var isSmooth = true
+    /** 边缘宽度 */
+    var padding = 100f
+
+    /** 画笔 */
+    private val pen = Paint()
+
+    /** 表半径 */
+    private val radius: Float
+        get() = min(size.width, size.height) / 2f - padding
+
+    /** 绘制间隔时间 */
+    private val interval: Long
+        get() = if (isShowSecond && isSmooth) {     // 如果显示秒针,且要平滑移动秒针,间隔40毫秒
+            40
+        } else {                                    // 否则,间隔1秒
+            1000
+        }
+
+    /** 分解时间的日历对象 */
+    private val calendar = Calendar.getInstance()
+
+    /**
+     * Item对象边界区域
+     *
+     * @return  边界区域
+     */
+    override fun boundingRect(): RectF {
+        return RectF(0f, 0f, size.width, size.height)
+    } // Function boundingRect()
+
+    /**
+     * 绘制时钟
+     *
+     * @param   canvas      画布
+     * @param   rect        更新区域
+     */
+    override fun onDraw(canvas: Canvas, rect: RectF) {
+        // 定位坐标原点为控件的中心
+        canvas.translate(size.width / 2f, size.height / 2f)
+
+        if (isShowScale) {          // 如果显示刻度
+            drawScale(canvas)
+            drawScaleText(canvas)
+        }
+
+
+        calendar.time = Date()
+        val hour = calendar.get(Calendar.HOUR)
+        val minute = calendar.get(Calendar.MINUTE)
+        var second = calendar.get(Calendar.SECOND).toFloat()
+
+        if (isSmooth) {         // 如果是平滑移动秒针
+            second += calendar.get(Calendar.MILLISECOND) / 1000f
+        }
+
+        drawHour(canvas, hour, minute, second)
+        drawMinute(canvas, minute, second)
+
+        if (isShowSecond) {         // 如果显示秒针
+            drawSecond(canvas, second)
+        }
+
+        postInvalidateDelayed(interval)
+
+        return
+    } // Function onDraw()
+
+    /**
+     * 绘制刻度
+     *
+     * @param   canvas      画布
+     */
+    private fun drawScale(canvas: Canvas) {
+        // 刻度长度
+        val scaleLength = max(radius / 10f, 2f)
+        val scaleLength1 = scaleLength * 1.2f
+        val strokeWidth = max(radius / 100f, 2f)
+        val strokeWidth1 = strokeWidth * 2f
+
+        canvas.save()
+        pen.color = scaleColor
+
+        for (i in 1..12) {          // 12小时刻度
+            pen.strokeWidth = strokeWidth1
+            canvas.drawLine(0f, - radius, 0f, - radius + scaleLength1, pen)
+
+            if (radius >= 50) {     // 如果半度大于40显示分钟刻度
+                canvas.rotate(6f)
+                for (j in 1..4) {       // 分钟刻度
+                    pen.strokeWidth = strokeWidth
+                    canvas.drawLine(0f, -radius, 0f, -radius + scaleLength, pen)
+                    canvas.rotate(6.0f)
+                }
+            } else {
+                canvas.rotate(30f)
+            }
+        }
+
+        canvas.restore()
+        return
+    } // Function drawScale()
+
+    /**
+     * 绘制刻度数字
+     *
+     * @param   canvas      画布
+     */
+    private fun drawScaleText(canvas: Canvas) {
+        val fontSize = radius / 5f
+        if (fontSize < 20) {         // 字体小于20,则不绘制刻度数字
+            return
+        }
+
+        pen.color = textColor
+        pen.textAlign = Paint.Align.CENTER
+        pen.textSize = fontSize
+        val offset = (pen.fontMetrics.bottom + pen.fontMetrics.top) / 2
+        val r = radius - radius / 3.8f
+        for (i in 1 .. 12) {    // 依次画12个时钟刻度
+            val x1 = (r * cos(i * PI / 6f - PI / 2f)).toFloat()
+            val y1 = (r * sin(i * PI / 6f - PI / 2f) - offset).toFloat()
+            canvas.drawText("$i", x1, y1, pen)
+        }
+
+        return
+    } // Function drawScaleText()
+
+    /**
+     * 绘制时针
+     *
+     * @param   canvas      画布
+     * @param   hour        时
+     * @param   minute      分
+     * @param   second      秒
+     */
+    private fun drawHour(canvas: Canvas, hour: Int, minute: Int, second: Float) {
+        canvas.save()
+        pen.strokeCap = Paint.Cap.ROUND
+        pen.strokeWidth = max(radius / 30f, 4f)
+        pen.color = hourColor
+        canvas.rotate(hour * 30f + minute * 30f / 60 + second * 30f / 60 / 60)
+        canvas.drawLine(0f, radius / 10f, 0f, - radius / 2.5f, pen)
+        canvas.drawCircle(0f, 0f, radius / 30f, pen)
+        canvas.restore()
+
+        return
+    } // Function drawHour()
+
+    /**
+     * 绘制分针
+     *
+     * @param   canvas      画布
+     * @param   minute      分
+     * @param   second      秒
+     */
+    private fun drawMinute(canvas: Canvas, minute: Int, second: Float) {
+        canvas.save()
+        pen.strokeCap = Paint.Cap.ROUND
+        pen.strokeWidth = max(radius / 40f, 4f)
+        pen.color = minuteColor
+        canvas.rotate(minute * 6f + second * 6f / 60f)
+        canvas.drawLine(0f, radius / 10f, 0f, - radius * 3f / 5f, pen)
+        pen.color = Color.YELLOW
+        canvas.drawCircle(0f, 0f, radius / 100f, pen)
+        canvas.restore()
+
+        return
+    } // Function drawMinute()
+
+    /**
+     * 绘制秒针
+     *
+     * @param   canvas      画布
+     * @param   second      秒
+     */
+    private fun drawSecond(canvas: Canvas, second: Float) {
+        canvas.save()
+        pen.strokeCap = Paint.Cap.ROUND
+        pen.strokeWidth = max(radius / 100f, 3f)
+        pen.color = secondColor
+        canvas.rotate(second * 6f)
+        canvas.drawLine(0f, radius / 5f, 0f, - radius + radius / 10f, pen)
+        canvas.drawCircle(0f, 0f, radius / 30f, pen)
+        canvas.drawCircle(0f, - radius + radius / 5f, radius / 60f, pen)
+        pen.color = Color.YELLOW
+        canvas.drawCircle(0f, 0f, radius / 100f, pen)
+        canvas.restore()
+
+        return
+    } // Function drawSecond()
+} // Class SGraphyClockItem

+ 71 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphyImageItem.kt

@@ -0,0 +1,71 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.items
+
+import android.graphics.*
+import com.sybotan.android.graphy.SGraphyItem
+import com.sybotan.android.graphy.enums.SGraphyItemFlag
+import java.util.*
+
+/**
+ * SGraphy图形引擎图像Item
+ *
+ * @author  庞利祥(sybotan@126.com)
+ *
+ * @param   image       图像
+ * @param   parent      指向父节点
+ */
+open class SGraphyImageItem(var image: Bitmap, parent: SGraphyItem? = null) : SGraphyItem(parent) {
+
+    /** 画笔 */
+    private var pen = Paint()
+
+    /**
+     * 默认构造函数
+     */
+    init {
+        flags = EnumSet.of(SGraphyItemFlag.ItemIsMovable)
+    } // init()
+
+    /**
+     * Item对象边界区域
+     *
+     * @return  边界区域
+     */
+    override fun boundingRect(): RectF {
+        return RectF(0f, 0f, image.width.toFloat(), image.height.toFloat())
+    } // Function boundingRect()
+
+    /**
+     * Item绘制操作
+     *
+     * @param   canvas      画布对象
+     * @param   rect        更新区域
+     */
+    override fun onDraw(canvas: Canvas, rect: RectF) {
+        canvas.drawBitmap(image, 0f, 0f, pen)
+        return
+    } // Function paint()
+
+} // Class SGraphyImageItem

+ 86 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphyLineItem.kt

@@ -0,0 +1,86 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.items
+
+import android.graphics.*
+import com.sybotan.android.graphy.SGraphyItem
+import com.sybotan.android.graphy.data.SLineF
+
+/**
+ * SGraphy图形引擎线段Item
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+open class SGraphyLineItem(line: SLineF = SLineF(), parent: SGraphyItem? = null) : SGraphyItem(parent) {
+
+    /** 保存线段的坐标 */
+    var line = line
+        set(value) {
+            field = value
+            invalidate()
+        }
+
+    /** 画笔 */
+    private var pen = Paint()
+
+    /**
+     * 默认构造函数
+     */
+    init {
+        pen.color = Color.RED
+        pen.strokeWidth = 5f
+    } // init
+
+    /**
+     * 构造函数
+     *
+     * @param   x1          端点1的x坐标
+     * @param   y1          端点1的y坐标
+     * @param   x2          端点2的x坐标
+     * @param   y2          端点2的y坐标
+     * @param   parent      指向父节点
+     */
+    constructor(x1: Float, y1: Float, x2: Float, y2: Float, parent: SGraphyItem? = null) : this(SLineF(x1, y1, x2, y2), parent)
+
+    /**
+     * Item对象边界区域
+     *
+     * @return  边界区域
+     */
+    override fun boundingRect(): RectF {
+        return RectF(minOf(line.x1, line.x2) -5, minOf(line.y1, line.y2) - 5,
+                maxOf(line.x1, line.x2) + 5, maxOf(line.y1, line.y2) + 5)
+    } // Function boundingRect()
+
+    /**
+     * Item绘制操作
+     *
+     * @param   canvas      画布对象
+     * @param   rect        更新区域
+     */
+    override fun onDraw(canvas: Canvas, rect: RectF) {
+        canvas.drawLine(line.x1, line.y1, line.x2, line.y2, pen)
+        return
+    } // Function paint()
+} // Class SGraphyLineItem

+ 43 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphyPolygonItem.kt

@@ -0,0 +1,43 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.items
+
+import android.graphics.RectF
+import com.sybotan.android.graphy.SGraphyItem
+
+/**
+ * SGraphy图形引擎多边型Item
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+open class SGraphyPolygonItem(parent: SGraphyItem? = null) : SGraphyItem(parent){
+    /**
+     * Item对象边界区域
+     *
+     * @return  边界区域
+     */
+    override fun boundingRect(): RectF {
+        return RectF()
+    } // Function boundingRect()
+} // Class SGraphyPolygonItem

+ 101 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphyRectItem.kt

@@ -0,0 +1,101 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.items
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.RectF
+import android.util.Log
+import com.sybotan.android.graphy.SGraphyItem
+import com.sybotan.android.graphy.enums.SGraphyItemFlag
+import java.util.*
+
+/**
+ * SGraphy图形引擎矩形框Item
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+open class SGraphyRectItem(rect: RectF, parent: SGraphyItem? = null) : SGraphyItem(parent){
+    /**
+     * 类对象
+     */
+    companion object {
+        private val TAG = SGraphyRectItem::class.java.name
+    } // companion object
+
+    /** 矩形区域 */
+    var rect: RectF = rect
+        set(value) {
+            field = value
+            invalidate()
+        }
+    /** 画笔 */
+    private var pen = Paint()
+
+    init {
+        pen.color = Color.RED
+        flags = EnumSet.of(SGraphyItemFlag.ItemIsMovable)
+        isVisible = true
+    } // init()
+
+    /**
+     * 构造函数
+     *
+     * @param   left        矩形左上角的x坐标
+     * @param   top         矩形左上角的y坐标
+     * @param   right       矩形右下角的x坐标
+     * @param   bottom      矩形右下角的y坐标
+     * @param   parent      指向父节点
+     */
+    constructor(left: Float, top: Float, right: Float, bottom: Float, parent: SGraphyItem? = null) : this(RectF(left, top, right, bottom), parent)
+
+    /**
+     * Item对象边界区域
+     *
+     * @return  边界区域
+     */
+    override fun boundingRect(): RectF {
+        return rect
+    } // Function boundingRect()
+
+    /**
+     * Item绘制操作
+     *
+     * @param   canvas      画布对象
+     * @param   rect        更新区域
+     */
+    override fun onDraw(canvas: Canvas, rect: RectF) {
+        pen.color = if (this.isSelected) {
+            Color.RED
+        } else {
+            Color.BLUE
+        }
+
+        canvas.drawRect(rect, pen)
+
+        super.onDraw(canvas, rect)
+        return
+    } // Function paint()
+} // Class SGraphyRectItem()

+ 43 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/items/SGraphySimpleTextItem.kt

@@ -0,0 +1,43 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.items
+
+import android.graphics.RectF
+import com.sybotan.android.graphy.SGraphyItem
+
+/**
+ * SGraphy图形引擎简单文本Item
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+open class SGraphySimpleTextItem(parent: SGraphyItem? = null): SGraphyItem(parent) {
+    /**
+     * Item对象边界区域
+     *
+     * @return  边界区域
+     */
+    override fun boundingRect(): RectF {
+        return RectF()
+    } // Function boundingRect()
+} // Class SGraphySimpleTextItem

+ 39 - 0
sybotan-android-graphy/src/main/java/com/sybotan/android/graphy/listeners/SGraphyItemPosListener.kt

@@ -0,0 +1,39 @@
+/*
+ * ********************************************************************************************************************
+ *
+ *               iFHS7.
+ *              ;BBMBMBMc                  rZMBMBR              BMB
+ *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+ *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+ *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+ *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+ *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+ *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+ *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+ *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+ *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+ *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+ *         i0BM: SWKHKGO  MBDv
+ *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+ *              ,  XMW  ..
+ *                  r                                                                     All rights reserved.
+ *
+ * ********************************************************************************************************************
+ */
+
+package com.sybotan.android.graphy.listeners
+
+/**
+ * item移动监听器
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+interface SGraphyItemPosListener {
+    /**
+     * item移动回调
+     *
+     * @param   x       移动到位置的x坐标
+     * @param   y       移动到位置的y坐标
+     */
+    fun onMoveTo(x: Float, y: Float)
+} // Interface SGraphyItemPosListener

+ 26 - 0
sybotan-android-graphy/src/main/res/values/strings.xml

@@ -0,0 +1,26 @@
+<!--
+  ~ *********************************************************************************************************************
+  ~
+  ~               iFHS7.
+  ~              ;BBMBMBMc                  rZMBMBR              BMB
+  ~              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
+  ~     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
+  ~     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
+  ~      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
+  ~     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
+  ~     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
+  ~     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
+  ~         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
+  ~     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
+  ~      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
+  ~         i0BM: SWKHKGO  MBDv
+  ~           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
+  ~              ,  XMW  ..
+  ~                  r                                                                     All rights reserved.
+  ~
+  ~ *********************************************************************************************************************
+  -->
+
+<resources>
+    <string name="app_name">sybotan-android-graphy</string>
+</resources>