Przeglądaj źródła

@fun: adm-graph

bai 3 lat temu
rodzic
commit
6183417e93
24 zmienionych plików z 2475 dodań i 0 usunięć
  1. 1 0
      adm-graph/.gitignore
  2. 47 0
      adm-graph/build.gradle.kts
  3. 21 0
      adm-graph/proguard-rules.pro
  4. 24 0
      adm-graph/src/androidTest/java/com/ys/bdtp/graph/ExampleInstrumentedTest.kt
  5. 5 0
      adm-graph/src/main/AndroidManifest.xml
  6. 685 0
      adm-graph/src/main/java/com/ys/bdtp/graph/SGraphyItem.kt
  7. 340 0
      adm-graph/src/main/java/com/ys/bdtp/graph/SGraphyScene.kt
  8. 679 0
      adm-graph/src/main/java/com/ys/bdtp/graph/SGraphyView.kt
  9. 52 0
      adm-graph/src/main/java/com/ys/bdtp/graph/SMotionEvent.kt
  10. 29 0
      adm-graph/src/main/java/com/ys/bdtp/graph/data/SLineF.kt
  11. 16 0
      adm-graph/src/main/java/com/ys/bdtp/graph/enums/SGraphyItemFlag.kt
  12. 15 0
      adm-graph/src/main/java/com/ys/bdtp/graph/enums/SGraphyViewTouchState.kt
  13. 17 0
      adm-graph/src/main/java/com/ys/bdtp/graph/events/SGraphyViewMoveEvent.kt
  14. 15 0
      adm-graph/src/main/java/com/ys/bdtp/graph/events/SGraphyViewZoomEvent.kt
  15. 227 0
      adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphyClockItem.kt
  16. 48 0
      adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphyImageItem.kt
  17. 63 0
      adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphyLineItem.kt
  18. 20 0
      adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphyPolygonItem.kt
  19. 76 0
      adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphyRectItem.kt
  20. 20 0
      adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphySimpleTextItem.kt
  21. 16 0
      adm-graph/src/main/java/com/ys/bdtp/graph/listeners/SGraphyItemPosListener.kt
  22. 21 0
      adm-graph/src/main/java/com/ys/bdtp/graph/utils/MatrixTools.kt
  23. 21 0
      adm-graph/src/main/java/com/ys/bdtp/graph/utils/MatrixUtil.kt
  24. 17 0
      adm-graph/src/test/java/com/ys/bdtp/graph/ExampleUnitTest.kt

+ 1 - 0
adm-graph/.gitignore

@@ -0,0 +1 @@
+/build

+ 47 - 0
adm-graph/build.gradle.kts

@@ -0,0 +1,47 @@
+plugins {
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+android {
+    compileSdk = 31
+
+    defaultConfig {
+        minSdk = 28
+        targetSdk = 31
+        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            isMinifyEnabled = false
+            proguardFiles(
+                getDefaultProguardFile("proguard-android-optimize.txt"),
+                "proguard-rules.pro"
+            )
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility = JavaVersion.VERSION_1_8
+        targetCompatibility = JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = "1.8"
+    }
+}
+
+dependencies {
+
+    implementation("androidx.core:core-ktx:1.7.0")
+    implementation("androidx.appcompat:appcompat:1.4.0")
+    implementation("com.google.android.material:material:1.4.0")
+    implementation("org.greenrobot:eventbus:3.3.1")
+    //implementation("org.jetbrains.anko:anko-commons:0.10.8")
+    //implementation("cn.sagacloud:saga-kotlin-base:1.4.105")
+
+    testImplementation("junit:junit:4.13.2")
+    androidTestImplementation("androidx.test.ext:junit:1.1.3")
+    androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
+}

+ 21 - 0
adm-graph/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
adm-graph/src/androidTest/java/com/ys/bdtp/graph/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package com.ys.bdtp.graph
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.ys.bdtp.graph.test", appContext.packageName)
+    }
+}

+ 5 - 0
adm-graph/src/main/AndroidManifest.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.ys.bdtp.graph">
+
+</manifest>

+ 685 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/SGraphyItem.kt

@@ -0,0 +1,685 @@
+package com.ys.bdtp.graph
+
+import android.graphics.Canvas
+import android.graphics.Matrix
+import android.graphics.PointF
+import android.graphics.RectF
+import android.util.Log
+import android.view.MotionEvent
+import com.ys.bdtp.graph.enums.SGraphyItemFlag
+import com.ys.bdtp.graph.listeners.SGraphyItemPosListener
+import com.ys.bdtp.graph.utils.MatrixUtil
+//import com.sybotan.base.extensions.toJson
+//import org.jetbrains.anko.doAsync
+import java.util.*
+import kotlin.concurrent.thread
+
+/**
+ * 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: SMotionEvent): SMotionEvent {
+            val ce = SMotionEvent(e)
+//            ce.matrix.postTranslate(child.pos.x, child.pos.y);
+//            ce.matrix.postScale(child.scale.x, child.scale.y);
+//            ce.matrix.postRotate(child.rotate,0f, 0f);
+            ce.matrix.preTranslate(child.pos.x, child.pos.y);
+            ce.matrix.preScale(child.scale.x, child.scale.y);
+            ce.matrix.preRotate(child.rotate, 0f, 0f);
+
+            // 不跟随缩放
+            if (!child.isTransform) {
+                val src = kotlin.floatArrayOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f)
+//                ce.matrix.postScale(child._inverseScaleX, child._inverseScaleY);
+                ce.matrix.getValues(src)
+                val matrix = Matrix()
+                matrix.preTranslate(src[2], src[5])
+                matrix.preScale(child.scale.x, child.scale.y)
+                matrix.preRotate(child.rotate)
+                ce.matrix = matrix
+            }
+//            se.setLocation((e.x - child.pos.x) / child.scale.x, (e.y - child.pos.y) / child.scale.y)
+            val matrixMat = Matrix()
+            ce.matrix.invert(matrixMat)
+            val matrixTransform = MatrixUtil.matrixTransform(matrixMat, e.viewX, e.viewY)
+            ce.x = matrixTransform.x
+            ce.y = matrixTransform.y
+            return ce
+        } // 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)
+
+    /** 旋转 */
+    var rotate = 0.0f
+
+    /** 是否进行变形 */
+    var isTransform = true;
+
+    /** 放缩反比例 */
+    var _inverseScaleX = 1f;
+    var _inverseScaleY = 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()
+        }*/
+
+        thread {
+            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 m = this.scene2itemMattrix()
+        val matrix = Matrix()
+        m.invert(matrix)
+        return MatrixUtil.matrixTransform(matrix, x, y)
+    } // Function mapFromScene()
+
+    /**
+     * 将场景中的xy坐标转换成item坐标。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
+     *
+     * @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 {
+        if (null == parent) {
+            return PointF(x, y)
+        }
+        val m = this.scene2itemMattrix()
+        return return MatrixUtil.matrixTransform(m, 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 mapToScene(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 {
+//        Log.e("boundingRect", boundingRect().contains(x, y).toString())
+        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 onPaint(canvas: Canvas, rect: RectF) {
+        canvas.save()
+//        val mat1 = SJsonUtil.fromJson(canvas.matrix.toJson(), Matrix::class.java)
+        this.onDraw(canvas)
+//        canvas.matrix = mat1
+        canvas.restore()
+        val src = kotlin.floatArrayOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f)
+        for (i in 1..children.size) {        // 倒序依次取item列中的所有item。将所有item的边界做并交处理。
+            val item = children[children.size - i]
+            if (!item.isVisible) {      // 如果对象不可见
+                continue
+            }
+            try {
+                // 保存画布状态
+                canvas.save()
+//                val mat = SJsonUtil.fromJson(canvas.matrix.toJson(), Matrix::class.java)
+                // item位移到指定位置绘制
+                canvas.translate(item.pos.x, item.pos.y)
+                canvas.scale(item.scale.x, item.scale.y)
+                canvas.rotate(item.rotate)
+
+                if (!item.isTransform) {
+                    canvas.matrix.getValues(src)
+                    val matrix = Matrix()
+                    matrix.preTranslate(src[2], src[5])
+                    matrix.preScale(item.scale.x, item.scale.y)
+                    matrix.preRotate(item.rotate)
+                    canvas.matrix = matrix
+//                    matrix.getValues(src)
+//                    item._inverseScaleX = 1.0f/src[0]
+//                    item._inverseScaleY = 1.0f/src[4]
+//                    canvas.scale(item._inverseScaleX,item._inverseScaleY)
+
+//                    matrix.preTranslate(src[2], src[5])
+//                    matrix.preTranslate(item.scale.x,item.scale.y)
+//                    matrix.preRotate(item.rotate)
+//                    canvas.matrix = matrix
+                }
+                // 设置绘制区域
+                // canvas.clipRect(item.boundingRect())
+                // 绘制item
+                item.onPaint(canvas, rect)
+                // 恢复画布状态
+                canvas.restore()
+//                canvas.matrix = mat;
+            } catch (e: Exception) {
+                e.printStackTrace()
+            }
+        }
+        return
+    } // Function paint()
+
+    /**
+     * Item 绘制操作
+     *
+     * @param painter    绘制对象
+     */
+    open fun onDraw(canvas: Canvas) {
+
+    }
+
+    /**
+     * 获得焦点变更回调
+     */
+    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: SMotionEvent): 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: SMotionEvent): Boolean {
+        Log.d(TAG, "onSingleTapUp: releaseItem")
+        releaseItem()
+        for (item in children) {
+            if (!item.isVisible) {      // 如果对象不可见
+                Log.e("手势3scene", "$e")
+                continue
+            }
+            val ce = toChildMotionEvent(item, e)
+//            Log.e("手势view", "${scene?.view?.scale}")
+//            Log.e("手势e", "${e.x}, ${e.y}")
+//            Log.e("手势ce", "${ce.x}, ${ce.y}")
+//            Log.e("手势1scene", (item.contains(ce.x, ce.y)).toString())
+            if (item.contains(ce.x, ce.y)                                   // 如果点在子项目上
+                    && item.onSingleTapUp(ce)) {                            // 且子项目处理了事件
+//                Log.e("手势4scene", e.toJson())
+                return true
+            }
+        }
+//        Log.e("手势2scene", e.toJson())
+        return false
+    } // Function onSingleTapUp()
+
+    open fun onActionUp() {
+        releaseItem()
+    }
+
+    open fun onActionMove(event: MotionEvent) {
+
+    }
+
+    /**
+     * 用户点击触发
+     *
+     * @param   e       手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onShowPress(e: SMotionEvent): 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: SMotionEvent): 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: SMotionEvent, e2: SMotionEvent, 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: SMotionEvent, e2: SMotionEvent, 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
+        currentMoveItem = null
+        scene!!.grabItem = null
+        return
+    } // Function grabItem
+
+    /**
+     * 场景对象到 item 对象的转换矩阵
+     *
+     * @return 转换矩阵
+     */
+    fun scene2itemMattrix(): Matrix {
+        var m = Matrix()
+        val list = this.itemPath()
+        for (item in list) {
+//            m.postTranslate(item.pos.x, item.pos.y);
+//            m.postScale(item.scale.x, item.scale.y);
+//            m.postRotate(item.rotate)
+            m.preTranslate(item.pos.x, item.pos.y);
+            m.preScale(item.scale.x, item.scale.y);
+            m.preRotate(item.rotate)
+
+            // 如果不进行变形处理,则取消 painter 的变型操作
+            if (!item.isTransform) {
+//                m.postScale(item._inverseScaleX, item._inverseScaleY);
+                /****************************************** 待确认 ************************************************/
+                val src = kotlin.floatArrayOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f)
+                m.getValues(src)
+                val matrix = Matrix()
+                matrix.preTranslate(src[2], src[5])
+                matrix.preScale(item.scale.x, item.scale.y)
+                matrix.preRotate(item.rotate)
+                m = matrix
+            }
+        }
+        return m
+    }
+
+
+} // Class SGraphyItem

+ 340 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/SGraphyScene.kt

@@ -0,0 +1,340 @@
+package com.ys.bdtp.graph
+
+import android.graphics.*
+import android.util.Log
+import android.view.MotionEvent
+import com.sybotan.android.base.extensions.adjusted
+import com.ys.bdtp.graph.data.SLineF
+import com.ys.bdtp.graph.enums.SGraphyItemFlag
+import com.ys.bdtp.graph.items.SGraphyImageItem
+import com.ys.bdtp.graph.items.SGraphyLineItem
+import com.ys.bdtp.graph.items.SGraphyRectItem
+import com.ys.bdtp.graph.utils.MatrixUtil
+//import com.sybotan.base.extensions.toJson
+
+/**
+ * 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.onPaint(canvas, rect)
+        return
+    } // Function paint()
+
+    /**
+     * 绘制背景
+     *
+     * @param   canvas      画布
+     * @param   rect        更新绘制区域
+     */
+    open fun drawBackground(canvas: Canvas, rect: RectF) {
+        canvas.drawColor(Color.WHITE)
+//        canvas.drawColor(Color.parseColor("#191B26"))
+        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: SMotionEvent): Boolean {
+        if (null != grabItem) {     // 如果当前锁定item不为空
+            return true
+        }
+
+        return rootNode.onDown(e)
+    } // Function onDown()
+
+    /**
+     * 用户释放屏幕触发.
+     *
+     * @param   e       释放手势事件。
+     * @return  如果事件被处理返回 true , 否则返回 false 。
+     */
+    open fun onSingleTapUp(e: SMotionEvent): Boolean {
+        Log.e("手势scene","$e")
+        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: SMotionEvent): 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: SMotionEvent): 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: SMotionEvent, e2: SMotionEvent, 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: SMotionEvent, e2: SMotionEvent, 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()
+
+
+    fun onActionUp(event: MotionEvent) {
+        rootNode.onActionUp()
+    }
+    fun onActionMove(event: MotionEvent) {
+        rootNode.onActionMove(event)
+    }
+
+    /**
+     * 转换场景事件坐标到指定Item坐标事件
+     *
+     * @param   item        指定的item对象
+     * @param   e           场景事件
+     * @return  item事件
+     */
+    private fun toGrabItemMotionEvent(item: SGraphyItem, e: SMotionEvent): SMotionEvent {
+        val se = SMotionEvent(e)
+        se.matrix = Matrix()
+        if (this.view != null ){
+//            se.matrix.postTranslate(this.view!!.pos.x, this.view!!.pos.y);
+//            se.matrix.postScale(this.view!!.scale, this.view!!.scale)
+//            se.matrix.postRotate(this.view!!.rotate)
+            se.matrix.preTranslate(this.view!!.pos.x, this.view!!.pos.y);
+            se.matrix.preScale(this.view!!.scale, this.view!!.scale)
+            se.matrix.preRotate(this.view!!.rotate)
+
+        }
+        se.matrix.preConcat(item.scene2itemMattrix())
+        val matrix = Matrix()
+        se.matrix.invert(matrix)
+        val matrixTransform = MatrixUtil.matrixTransform(matrix, se.viewX, se.viewY)
+        se.x = matrixTransform.x
+        se.y = matrixTransform.y
+//        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

+ 679 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/SGraphyView.kt

@@ -0,0 +1,679 @@
+
+package com.ys.bdtp.graph
+
+import android.content.Context
+import android.graphics.*
+import android.util.AttributeSet
+import android.util.Log
+import android.view.*
+import com.ys.bdtp.graph.enums.SGraphyViewTouchState
+import com.ys.bdtp.graph.events.SGraphyViewMoveEvent
+import com.ys.bdtp.graph.events.SGraphyViewZoomEvent
+import com.ys.bdtp.graph.utils.MatrixUtil
+import com.sybotan.base.extensions.toJson
+import org.greenrobot.eventbus.EventBus
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+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
+        private var fitrate = 0.8f
+    } // companion object
+
+    /** 指向加载到视图中的场景 */
+    var scene: SGraphyScene? = null
+        set(value) {
+            if (field == value) {       // 如果场景没有变化
+                return
+            }
+            field?.view = null
+            value?.view = this
+            // 保存场景
+            field = value
+        }
+
+    /** 旋转 */
+    var rotate = 0.0f
+
+    /** 是否支持二指缩放操作 */
+    var canZoom = true
+
+    /** 缩放比例 */
+    var scale = 1f
+        get() = max(min(field, maxScale), minScale)
+        set(value) {
+            field = value
+            moveRange()
+        }
+
+    /** 最小缩放比例 */
+    var minScale = 0.0000001f
+        set(value) {
+            field = min(value, maxScale)
+        }
+
+    /** 最大缩放比例 */
+    var maxScale = 1000000f
+        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
+    private var canvas: Canvas? = null
+
+    /** 线程池*/
+    private var executorService: ExecutorService? = null
+    private var runable = Runnable {
+        run {
+            while (true) {
+                if (Thread.interrupted()) {
+                    Log.e("sajkskjd", "线程终止了")
+                    break
+                }
+                if (isDrawing) {
+                    try {
+//                    Thread.sleep(100)
+                        canvas = holder.lockCanvas()
+                        if (null == canvas) {   // 如果canvas为空
+                            continue
+                        }
+                        drawScene(canvas!!)
+                        /*synchronized(holder) {
+                            drawScene(canvas!!)
+                        }*/
+                    } catch (e: Exception) {
+                        e.printStackTrace()
+                    } finally {
+                        try {
+                            if (null != canvas && null != holder) {
+                                holder.unlockCanvasAndPost(canvas)
+                            }
+                        } catch (e: Exception) {
+                            e.printStackTrace()
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fun runableTask() {
+        executorService = Executors.newSingleThreadExecutor()
+        executorService!!.execute(runable)
+    }
+
+    fun shutDown() {
+        if (null != executorService) {
+            executorService!!.shutdown()
+            executorService!!.shutdownNow()
+        }
+    }
+
+
+    /** 默认构造函数 */
+    init {
+        Log.d(TAG, "init")
+        initView()
+    } // init
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        Log.e("w", widthMeasureSpec.toString())
+        Log.e("h", heightMeasureSpec.toString())
+    }
+
+    /**
+     * 适配视图到视图
+     */
+    fun fitSceneToView() {
+        // 未设置场景
+        if (null == scene) {
+            return
+        }
+//        val w = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
+//        val h = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
+//        this.measure(width, height)
+//        val h: Int = this.getMeasuredHeight()
+//        val w: Int = this.getMeasuredWidth()
+
+        this.post {
+            val w = width.toFloat()
+            val h = height.toFloat()
+
+            val rect = scene!!.worldRect()
+            setCenterPoint(
+                PointF(rect.centerX(), rect.centerY()),
+                min(w / rect.width(), h / rect.height()) * fitrate
+            )
+//            Log.e("w",w.toString())
+//            Log.e("h",h.toString())
+//            Log.e("rect1",rect.toJson())
+//            Log.e("min",(min(w / rect.width(), h / rect.height())).toJson())
+        }
+
+    } // Function FitView()
+
+    /**
+     * 设置中心点
+     *
+     * @param scale 场景的缩放比例
+     */
+    fun setCenterPoint(pos: PointF, scale: Float? = null) {
+        if (scale != null) {
+            this.scale = scale
+        }
+        this.pos = PointF(
+            -pos.x * this.scale + this.width.toFloat() / 2f,
+            -pos.y * this.scale + this.height.toFloat() / 2f
+        )
+    } // Function setCenterPoint()
+
+    /**
+     * 限定移动范围
+     */
+    fun moveRange() {
+        if (scene == null) {
+            return
+        }
+        val w = this.width.toFloat()
+        val h = this.height.toFloat()
+        val rect = scene!!.worldRect()
+        this.minPos = PointF(
+            -rect.centerX() * this.scale + w / 2f - rect.width() / 2f * this.scale,
+            -rect.centerY() * this.scale + h / 2f - rect.height() / 2f * this.scale
+        )
+        /**
+         *   -(rect.left + rect.right) / 2f * graphyView.scale  + width / 2 图形的中心点,
+         *   rect.width() / 2 * graphyView.scale / 0.8f 图形宽度的一半乘以缩放比例除以适配百分比
+         *
+         */
+        this.maxPos = PointF(
+            -rect.centerX() * this.scale + w / 2f + rect.width() / 2f * this.scale,
+            -rect.centerY() * this.scale + h / 2f + rect.height() / 2f * this.scale
+        )
+    } // Function moveRange()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Surface相关操作
+    /**
+     * Surface创建成功时回调函数。
+     *
+     * @param   holder      surface创建成功时的保持器。
+     */
+    override fun surfaceCreated(holder: SurfaceHolder?) {
+        Log.d(TAG, "surfaceCreated")
+
+        isDrawing = true
+        // 启动后台绘制线程
+        paintThread = Thread(this)
+        paintThread!!.start()
+//        runableTask()
+        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
+        isDrawing = false
+//        shutDown()
+        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 -> {
+                    scene?.onActionMove(event)
+                    if (SGraphyViewTouchState.ZoomState == touchState && canZoom) { // 两点绽放操作
+                        viewZoom(event)
+                        true
+                    } else {
+                        gesture.onTouchEvent(event)
+                    }
+                }
+                MotionEvent.ACTION_UP -> {
+                    scene?.onActionUp(event)
+                    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 {
+        Log.e("手势view", e.toString())
+        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
+            Log.e("x", pos.x.toString())
+            Log.e("y", pos.y.toString())
+            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): SMotionEvent {
+        val src1 = kotlin.floatArrayOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f)
+        val src2 = kotlin.floatArrayOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f)
+        val se = SMotionEvent(e)
+//        se.matrix.postTranslate(pos.x,pos.y)
+//        se.matrix.postScale(this.scale, this.scale);
+//        se.matrix.postRotate(this.rotate)
+
+        se.matrix.preTranslate(pos.x, pos.y)
+        se.matrix.preScale(this.scale, this.scale);
+        se.matrix.preRotate(this.rotate)
+
+
+        val matrixMat = Matrix()
+        se.matrix.invert(matrixMat)
+
+        se.matrix.getValues(src1)
+        Log.e("src1= ", src1.toJson())
+        matrixMat.getValues(src2)
+        Log.e("src2= ", src2.toJson())
+
+        val mp = MatrixUtil.matrixTransform(matrixMat, e.x, e.y)
+        se.x = mp.x
+        se.y = mp.y
+//        Log.e("ex = ",e.x.toString())
+//        Log.e("ey = ",e.y.toString())
+//        Log.e("sex = ",se.x .toString())
+//        Log.e("sey = ",se.y.toString())
+//        se.setLocation((e.x - pos.x) / scale, (e.y - pos.y) / scale)
+        return se
+    } // Function toSceneMotionEvent()
+
+
+} // Class SGraphyView

+ 52 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/SMotionEvent.kt

@@ -0,0 +1,52 @@
+package com.ys.bdtp.graph
+
+
+import android.graphics.Matrix
+import android.view.MotionEvent
+
+/**
+ * 自定义处理事件
+ *
+ * @author wx <zhangweixin@sagacloud.cn>
+ */
+class SMotionEvent() {
+
+    /** x 坐标 */
+    var y  = 0.0f
+    /** y 坐标 */
+    var x = 0.0f
+    /** 事件类型 */
+    var action: Int = 0
+    /** 图坐标 X */
+    var viewX = 0.0f
+    /** 图坐标 Y */
+    var viewY = 0.0f
+    /** 矩阵 */
+    var matrix = Matrix()
+
+    /**
+     * 构造器
+     *
+     * @param   e   事件
+     */
+    constructor(e: MotionEvent) : this() {
+        this.viewX = e.x
+        this.viewY = e.y
+        this.action = e.action
+    }
+
+    /**
+     * 构造器
+     *
+     * @param   e   事件
+     */
+    constructor(e: SMotionEvent) : this() {
+        this.x = e.x
+        this.y = e.y
+        this.viewX = e.viewX
+        this.viewY = e.viewY
+        this.action = e.action
+        matrix.set(e.matrix)
+    }
+
+}

+ 29 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/data/SLineF.kt

@@ -0,0 +1,29 @@
+package com.ys.bdtp.graph.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

+ 16 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/enums/SGraphyItemFlag.kt

@@ -0,0 +1,16 @@
+
+package com.ys.bdtp.graph.enums
+
+/**
+ * SGraphyItem标志
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+enum class SGraphyItemFlag(flag: Int) {
+    /** 可以移动 */
+    ItemIsMovable(0x01),
+    /** 可被选择 */
+    ItemIsSelectable(0x02),
+    /** 可以获得焦点 */
+    ItemIsFocusable(0x04)
+} // Enum SGraphyItemFlag

+ 15 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/enums/SGraphyViewTouchState.kt

@@ -0,0 +1,15 @@
+package com.ys.bdtp.graph.enums
+
+/**
+ * SGraphyView手柄状态
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+enum class SGraphyViewTouchState {
+    /** 标准状态 */
+    NoneState,
+    /** 移动状态 */
+    MoveState,
+    /** 缩放状态 */
+    ZoomState
+} // Enum SGraphyItemFlag

+ 17 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/events/SGraphyViewMoveEvent.kt

@@ -0,0 +1,17 @@
+package com.ys.bdtp.graph.events
+
+import com.ys.bdtp.graph.SGraphyView
+
+/**
+ * 视图原点移动事件
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+data class SGraphyViewMoveEvent(
+    /** 事件来源视图 */
+        val view: SGraphyView,
+    /** 原点x坐标 */
+        val x: Float,
+    /** 原点y坐标 */
+        val y: Float
+) // Class SGraphyViewMoveEvent

+ 15 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/events/SGraphyViewZoomEvent.kt

@@ -0,0 +1,15 @@
+package com.ys.bdtp.graph.events
+
+import com.ys.bdtp.graph.SGraphyView
+
+/**
+ * 视图缩放事件
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+data class SGraphyViewZoomEvent(
+    /** 事件来源视图 */
+        val view: SGraphyView,
+    /** 视图缩放比例 */
+        val zoom: Float
+) // Class SGraphyViewZoomEvent

+ 227 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphyClockItem.kt

@@ -0,0 +1,227 @@
+package com.ys.bdtp.graph.items
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.RectF
+import android.util.SizeF
+import com.ys.bdtp.graph.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) {
+        // 定位坐标原点为控件的中心
+        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

+ 48 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphyImageItem.kt

@@ -0,0 +1,48 @@
+package com.ys.bdtp.graph.items
+
+import android.graphics.*
+import com.ys.bdtp.graph.SGraphyItem
+import com.ys.bdtp.graph.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) {
+        canvas.drawBitmap(image, 0f, 0f, pen)
+        return
+    } // Function paint()
+
+} // Class SGraphyImageItem

+ 63 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphyLineItem.kt

@@ -0,0 +1,63 @@
+package com.ys.bdtp.graph.items
+
+import android.graphics.*
+import com.ys.bdtp.graph.SGraphyItem
+import com.ys.bdtp.graph.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) {
+        canvas.drawLine(line.x1, line.y1, line.x2, line.y2, pen)
+        return
+    } // Function paint()
+} // Class SGraphyLineItem

+ 20 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphyPolygonItem.kt

@@ -0,0 +1,20 @@
+package com.ys.bdtp.graph.items
+
+import android.graphics.RectF
+import com.ys.bdtp.graph.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

+ 76 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphyRectItem.kt

@@ -0,0 +1,76 @@
+package com.ys.bdtp.graph.items
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.RectF
+import com.ys.bdtp.graph.SGraphyItem
+import com.ys.bdtp.graph.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, SGraphyItemFlag.ItemIsSelectable, SGraphyItemFlag.ItemIsFocusable)
+        isVisible = true
+        this.rect = rect
+
+    } // 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) {
+        pen.color = if (this.isSelected) {
+            Color.RED
+        } else {
+            Color.BLUE
+        }
+        canvas.drawRect(rect, pen)
+        return
+    } // Function paint()
+} // Class SGraphyRectItem()

+ 20 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/items/SGraphySimpleTextItem.kt

@@ -0,0 +1,20 @@
+package com.ys.bdtp.graph.items
+
+import android.graphics.RectF
+import com.ys.bdtp.graph.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

+ 16 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/listeners/SGraphyItemPosListener.kt

@@ -0,0 +1,16 @@
+package com.ys.bdtp.graph.listeners
+
+/**
+ * item移动监听器
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+interface SGraphyItemPosListener {
+    /**
+     * item移动回调
+     *
+     * @param   x       移动到位置的x坐标
+     * @param   y       移动到位置的y坐标
+     */
+    fun onMoveTo(x: Float, y: Float)
+} // Interface SGraphyItemPosListener

+ 21 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/utils/MatrixTools.kt

@@ -0,0 +1,21 @@
+package com.ys.bdtp.graph.utils
+
+import android.graphics.Matrix
+import android.graphics.PointF
+
+object MatrixTools {
+
+    /**
+     *
+     */
+    fun matrixTransform(mat: Matrix,x : Float, y : Float ) : PointF {
+        val pointF = PointF()
+        val src = kotlin.floatArrayOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f)
+        mat.getValues(src)
+        pointF.x = x * src[0] + y * src[1] + src[2];
+        pointF.y = x * src[3] + y * src[4] + src[5];
+        return pointF;
+    }
+
+
+}

+ 21 - 0
adm-graph/src/main/java/com/ys/bdtp/graph/utils/MatrixUtil.kt

@@ -0,0 +1,21 @@
+package com.ys.bdtp.graph.utils
+
+import android.graphics.Matrix
+import android.graphics.PointF
+
+object MatrixUtil {
+
+    /**
+     *
+     */
+    fun matrixTransform(mat: Matrix,x : Float, y : Float ) : PointF {
+        val pointF = PointF()
+        val src = kotlin.floatArrayOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f)
+        mat.getValues(src)
+        pointF.x = x * src[0] + y * src[1] + src[2];
+        pointF.y = x * src[3] + y * src[4] + src[5];
+        return pointF;
+    }
+
+
+}

+ 17 - 0
adm-graph/src/test/java/com/ys/bdtp/graph/ExampleUnitTest.kt

@@ -0,0 +1,17 @@
+package com.ys.bdtp.graph
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        assertEquals(4, 2 + 2)
+    }
+}