瀏覽代碼

feat poc2.0无模型设备添加

lihao1 3 年之前
父節點
當前提交
986f98fa20
共有 25 個文件被更改,包括 1505 次插入89 次删除
  1. 1 1
      cadengine/src/main/java/cn/sagacloud/android/cadengine/items/AreaNodeItem.kt
  2. 1 1
      cadengine/src/main/java/cn/sagacloud/android/cadengine/items/PipeNodeItem.kt
  3. 9 9
      demo/src/main/AndroidManifest.xml
  4. 9 2
      demo/src/main/java/com/framework/mvvm/model/db/dao/ObjectDao.kt
  5. 6 0
      demo/src/main/java/com/framework/mvvm/model/db/dao/ServeAreaDao.kt
  6. 2 0
      demo/src/main/java/com/framework/mvvm/model/db/entity/task/ObjectEntity.kt
  7. 17 1
      demo/src/main/java/com/framework/mvvm/model/repository/AdmRepository.kt
  8. 129 0
      demo/src/main/java/com/sybotan/android/demo/activities/EquipContainerActivity.kt
  9. 36 5
      demo/src/main/java/com/sybotan/android/demo/activities/EquipDetailActivity.kt
  10. 53 10
      demo/src/main/java/com/sybotan/android/demo/activities/GraphyActivity.kt
  11. 84 39
      demo/src/main/java/com/sybotan/android/demo/activities/ServeAreaActivity.kt
  12. 5 3
      demo/src/main/java/com/sybotan/android/demo/activities/SpaceMapActivity.kt
  13. 129 0
      demo/src/main/java/com/sybotan/android/demo/adapter/ContainerExpandAdapter.kt
  14. 10 0
      demo/src/main/java/com/sybotan/android/demo/bean/ContainerParentBean.java
  15. 106 0
      demo/src/main/java/com/sybotan/android/demo/view/SwipMenuLayout/CstViewPager.java
  16. 623 0
      demo/src/main/java/com/sybotan/android/demo/view/SwipMenuLayout/SwipeMenuLayout.java
  17. 78 0
      demo/src/main/java/com/sybotan/android/demo/viewmodel/ContainerVM.kt
  18. 32 3
      demo/src/main/java/com/sybotan/android/demo/viewmodel/GraphyVM.kt
  19. 61 1
      demo/src/main/java/com/sybotan/android/demo/viewmodel/SpaceMapVM.kt
  20. 24 0
      demo/src/main/res/layout/activity_equip_container.xml
  21. 37 13
      demo/src/main/res/layout/activity_graphy.xml
  22. 17 1
      demo/src/main/res/layout/activity_serve_area.xml
  23. 30 0
      demo/src/main/res/layout/child_expand_container.xml
  24. 1 0
      demo/src/main/res/layout/group_expand_relation.xml
  25. 5 0
      demo/src/main/res/values/attrs.xml

+ 1 - 1
cadengine/src/main/java/cn/sagacloud/android/cadengine/items/AreaNodeItem.kt

@@ -48,7 +48,7 @@ class AreaNodeItem(val data: PointF, parent: SGraphyItem? = null) : SGraphyItem(
     } // Constructor
 
     override fun boundingRect(): RectF {
-        return RectF(data.x - 600f, -data.y - 600f, data.x + 600f, -data.y + 600f)
+        return RectF(data.x - 800f, -data.y - 800f, data.x + 800f, -data.y + 800f)
     }
 
     override fun onScroll(e1: SMotionEvent, e2: SMotionEvent, distanceX: Float, distanceY: Float): Boolean {

+ 1 - 1
cadengine/src/main/java/cn/sagacloud/android/cadengine/items/PipeNodeItem.kt

@@ -47,7 +47,7 @@ class PipeNodeItem(val data: PointF, parent: SGraphyItem? = null) : SGraphyItem(
     } // Constructor
 
     override fun boundingRect(): RectF {
-        return RectF(data.x - 200f, -data.y - 200f, data.x + 200f, -data.y + 200f)
+        return RectF(data.x - 800f, -data.y - 800f, data.x + 800f, -data.y + 800f)
     }
 
     override fun onScroll(e1: SMotionEvent, e2: SMotionEvent, distanceX: Float, distanceY: Float): Boolean {

+ 9 - 9
demo/src/main/AndroidManifest.xml

@@ -25,7 +25,6 @@
 
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
 
-
     <application
         android:name=".DemoApp"
         android:allowBackup="true"
@@ -36,14 +35,15 @@
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
         android:theme="@style/AppTheme">
-
-        <!--        <meta-data-->
-        <!--            android:name="design_width_in_dp"-->
-        <!--            android:value="960" />-->
-        <!--        <meta-data-->
-        <!--            android:name="design_height_in_dp"-->
-        <!--            android:value="600" />-->
-
+        <activity
+            android:name=".activities.EquipContainerActivity"
+            android:exported="true" />
+        <!-- <meta-data -->
+        <!-- android:name="design_width_in_dp" -->
+        <!-- android:value="960" /> -->
+        <!-- <meta-data -->
+        <!-- android:name="design_height_in_dp" -->
+        <!-- android:value="600" /> -->
         <provider
             android:name=".tools.MyProvider"
             android:authorities="com.sybotan.android.demo.fileProvider"

+ 9 - 2
demo/src/main/java/com/framework/mvvm/model/db/dao/ObjectDao.kt

@@ -23,12 +23,19 @@ interface ObjectDao {
     @Query("select * from object where building_id = :buildingId and valid = 1")
     suspend fun getObjectByBuildingId(buildingId: String): List<ObjectEntity>
 
-    @Query("select * from object where floor_id = :floorId and obj_type=:type and valid = 1")
-    suspend fun getEquipByFloorId(floorId: String, type: String): List<ObjectEntity>
+    @Query("select * from object where floor_id = :floorId and obj_type=:type and container_id=:containerId and valid = 1")
+    suspend fun getEquipByFloorId(
+        floorId: String,
+        type: String,
+        containerId: String
+    ): List<ObjectEntity>
 
     @Query("select * from object where class_code = :code and valid = 1")
     suspend fun getObjectByCode(code: String): List<ObjectEntity>
 
+    @Query("select * from object where container_id = :objId and valid = 1")
+    suspend fun getContainerObjs(objId: String): List<ObjectEntity>
+
     @Query("select * from object where local_id = UPPEr(:localId) and valid = 1")
     suspend fun getObjectByLocalId(localId: String): ObjectEntity?
 

+ 6 - 0
demo/src/main/java/com/framework/mvvm/model/db/dao/ServeAreaDao.kt

@@ -18,6 +18,12 @@ interface ServeAreaDao {
     @Query("select * from serve_area where building_id = :id")
     suspend fun getAreasByBuildingId(id: String): List<ServeAreaEntity>
 
+    @Query("select * from serve_area where obj_id = :id and valid = 1")
+    suspend fun getAreasByObjId(id: String): ServeAreaEntity?
+
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     suspend fun insertAreas(areas: List<ServeAreaEntity>)
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insertArea(areas: ServeAreaEntity)
 }

+ 2 - 0
demo/src/main/java/com/framework/mvvm/model/db/entity/task/ObjectEntity.kt

@@ -1,11 +1,13 @@
 package com.framework.mvvm.model.db.entity.task
 
+import android.os.Parcelable
 import androidx.room.ColumnInfo
 import androidx.room.Entity
 import androidx.room.PrimaryKey
 import cn.sagacloud.android.cadengine.types.PointZ
 import com.framework.mvvm.model.db.entity.BaseEntity
 import com.google.gson.JsonElement
+import java.io.Serializable
 
 /**
  * 对象表

+ 17 - 1
demo/src/main/java/com/framework/mvvm/model/repository/AdmRepository.kt

@@ -127,7 +127,7 @@ class AdmRepository(
     }
 
     suspend fun getEquipByFloorId(id: String): List<ObjectEntity> {
-        return db.objectDao().getEquipByFloorId(id, "equipment")
+        return db.objectDao().getEquipByFloorId(id, "equipment", "")
     }
 
     suspend fun getEquipByCode(code: String): EquipmentEntity? {
@@ -230,10 +230,26 @@ class AdmRepository(
         return db.pipeDao().insPipe(relation)
     }
 
+    suspend fun insServeArea(serveAreaEntity: ServeAreaEntity) {
+        return db.serveAreaDao().insertArea(serveAreaEntity)
+    }
+
+    suspend fun getServeAreaByObjId(objId: String): ServeAreaEntity? {
+        return db.serveAreaDao().getAreasByObjId(objId)
+    }
+
     suspend fun insProblemEquipEntity(problem: ProblemEquipEntity) {
         return db.problemEquipDao().insProblem(problem)
     }
 
+    suspend fun getContainerConfig(container: String): List<ContainerConfigEntity> {
+        return db.containerConfigDao().getWidgetsByContainer(container)
+    }
+
+    suspend fun getContainerObjs(deviceId: String): List<ObjectEntity> {
+        return db.objectDao().getContainerObjs(deviceId)
+    }
+
 
     /**
      * 请求框架数据

+ 129 - 0
demo/src/main/java/com/sybotan/android/demo/activities/EquipContainerActivity.kt

@@ -0,0 +1,129 @@
+package com.sybotan.android.demo.activities
+
+import android.content.Intent
+import android.graphics.PointF
+import android.os.Bundle
+import android.widget.TextView
+import com.framework.mvvm.model.db.entity.task.ObjectEntity
+import com.google.gson.Gson
+import com.sybotan.android.demo.R
+import com.sybotan.android.demo.adapter.ContainerExpandAdapter
+import com.sybotan.android.demo.bean.ContainerParentBean
+import com.sybotan.android.demo.bean.result.Position
+import com.sybotan.android.demo.bean.result.Z
+import com.sybotan.android.demo.view.SuperExpandableListView
+import com.sybotan.android.demo.viewmodel.ContainerVM
+import java.util.HashMap
+
+/**
+ * 无模型设备添加设备页
+ */
+class EquipContainerActivity : BaseActivity() {
+    private lateinit var title: TextView
+    private lateinit var expandlv: SuperExpandableListView
+
+    /** 建筑Id */
+    private lateinit var buildingId: String
+
+    /** 楼层Id */
+    private lateinit var floorId: String
+
+    /** 项目Id */
+    private lateinit var projectId: String
+
+    /** 设备Id */
+    private lateinit var deviceId: String
+    private lateinit var spaceId: String
+    private var position: Position? = null
+    lateinit var containerVM: ContainerVM
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_equip_container)
+        initView()
+        initData()
+    }
+
+    private fun initData() {
+        containerVM = ContainerVM(this.application, { name, param ->
+            when (name) {
+                ContainerVM.CONTAINER -> {
+                    val hashMap = param as HashMap<String, Any>
+                    title.setText(hashMap["title"].toString())
+                    val parent = hashMap["parent"] as ArrayList<ContainerParentBean>
+                    val children = hashMap["child"] as MutableList<List<ObjectEntity>>
+                    val adapter = ContainerExpandAdapter(this, parent, children)
+                    expandlv.setAdapter(adapter)
+                    for (index in 0 until expandlv.count) {
+                        expandlv.expandGroup(index)
+                    }
+                    adapter.setOnAdd(object : ContainerExpandAdapter.AddRel {
+                        override fun onAdd(group: ContainerParentBean) {
+                            val intent =
+                                Intent(this@EquipContainerActivity, EquipDetailActivity::class.java)
+                            val bundle = Bundle()
+                            bundle.putSerializable("position", position)
+                            bundle.putString("buildingId", buildingId)
+                            bundle.putString("floorId", floorId)
+                            bundle.putString("spaceId", spaceId)
+                            bundle.putString("containerId", deviceId)
+                            bundle.putString("widgetCode", group.widgetCode)
+                            bundle.putString("projectId", projectId)
+                            bundle.putParcelable("centerPoint", PointF(0f, 0f))
+                            intent.putExtra("equip", bundle)
+                            startActivity(intent)
+                        }
+                    })
+                    adapter.setOnDelete(object : ContainerExpandAdapter.DeleteRel {
+                        override fun ondelete(item: ObjectEntity) {
+                            containerVM.deleteObject(item, deviceId)
+                        }
+                    })
+                    adapter.setOnItemClick(object : ContainerExpandAdapter.ItemClick {
+                        override fun onclick(item: ObjectEntity) {
+                            val intent =
+                                Intent(this@EquipContainerActivity, EquipDetailActivity::class.java)
+                            val bundle = Bundle()
+                            val positon = Position()
+                            positon.x = position!!.x.toString()
+                            positon.y = position!!.y.toString()
+                            val z = Z()
+                            z.offset = position!!.z.offset
+                            z.region = position!!.z.region
+                            positon.z = z
+                            bundle.putString("spaceId", spaceId)
+                            bundle.putSerializable("position", positon)
+                            bundle.putString("buildingId", buildingId)
+                            bundle.putString("floorId", floorId)
+                            bundle.putString("deviceId", item.id)
+                            bundle.putString("projectId", projectId)
+                            bundle.putString("containerId", deviceId)
+                            bundle.putParcelable("centerPoint", PointF(0f, 0f))
+                            intent.putExtra("equip", bundle)
+                            startActivity(intent)
+                        }
+                    })
+
+                }
+            }
+        }, this, buildingId, floorId, projectId)
+        expandlv.setGroupIndicator(null)
+        expandlv.divider = null
+    }
+
+    override fun onResume() {
+        super.onResume()
+        containerVM.getConfig(deviceId)
+    }
+
+    private fun initView() {
+        title = findViewById(R.id.title)
+        expandlv = findViewById(R.id.expandlv)
+        val params = intent.getBundleExtra("params")!!
+        buildingId = params.getString("buildingId", "")!!
+        floorId = params.getString("floorId", "")!!
+        projectId = params.getString("projectId", "")!!
+        deviceId = params.getString("deviceId", "")!!
+        spaceId = params.getString("spaceId", "")!!
+        position = params?.getSerializable("position") as? Position
+    }
+}

+ 36 - 5
demo/src/main/java/com/sybotan/android/demo/activities/EquipDetailActivity.kt

@@ -96,6 +96,8 @@ class EquipDetailActivity : BaseActivity(), View.OnTouchListener {
     /** 楼层Id */
     private lateinit var floorId: String
     private lateinit var projectId: String
+    private lateinit var containerId: String
+    private lateinit var widgetCode: String
     private lateinit var deviceId: String
     private lateinit var floorMap: String
     private var scale: Float = 0f
@@ -131,8 +133,10 @@ class EquipDetailActivity : BaseActivity(), View.OnTouchListener {
         spaceId = bundle?.getString("spaceId") ?: ""
         position = bundle?.getSerializable("position") as? Position
         projectId = bundle?.getString("projectId")!!
+        containerId = bundle?.getString("containerId", "")
         deviceId = bundle?.getString("deviceId", "")
-        floorMap = bundle.getString("floorMap")!!
+        floorMap = bundle.getString("floorMap", "")
+        widgetCode = bundle.getString("widgetCode", "")
         scale = bundle.getFloat("scale", 0f)
         centerPoint = bundle.getParcelable("centerPoint")!!
         uuid = IdGenerator.getId(projectId, IdGenerator.EQUIPMENT)
@@ -168,9 +172,14 @@ class EquipDetailActivity : BaseActivity(), View.OnTouchListener {
         pictureAdapter = PictureAdapter(this, beans, "", false, 0, 85)
         viewRecycler.adapter = pictureAdapter
         pictureAdapter.setOnPicAdd { addPic() }
-        height.setTvRight(position!!.z.offset)
-        locationWhere.setTvRight(position!!.z.region)
-        location.setTvRight("(x=${position!!.x},y=${position!!.y})")
+        height.setTvRight(position?.z?.offset)
+        locationWhere.setTvRight(position?.z?.region)
+        location.setTvRight("(x=${position?.x},y=${position?.y})")
+        if (!TextUtils.isEmpty(containerId)) {
+            location.setRightIconGone(true)
+            equipClass.isEnabled = false
+            location.isEnabled = false
+        }
         doneSwitch.addSwitchObserver { switchView, state ->
             when (state) {
                 RMSwitch.STATE_LEFT -> {
@@ -207,7 +216,7 @@ class EquipDetailActivity : BaseActivity(), View.OnTouchListener {
                 equipEntity!!,
                 localNameString.text.toString(),
                 localIdString.text.toString(),
-                doneSwitch.state, position!!
+                doneSwitch.state, position!!, containerId
             )
         }
         findViewById<ImageView>(R.id.back).setOnClickListener { finish() }
@@ -227,6 +236,7 @@ class EquipDetailActivity : BaseActivity(), View.OnTouchListener {
             bundle.putFloat("scale", scale)
             bundle.putString("floorMap", floorMap)
             bundle.putParcelable("centerPoint", centerPoint)
+            bundle.putSerializable("position", position)
             val id = if (TextUtils.isEmpty(deviceId)) uuid else deviceId
             bundle.putString("deviceId", id)
             intent.putExtra("params", bundle)
@@ -278,6 +288,19 @@ class EquipDetailActivity : BaseActivity(), View.OnTouchListener {
             when (name) {
                 GraphyVM.EQUIP_CLASS -> {
                     equips = params as List<EquipmentEntity>
+                    if (this.equipEntity == null) {
+                        for (eq in equips) {
+                            if (widgetCode.equals(eq.code)) {
+                                this.equipEntity = eq
+                                graphyVM.getRelConfig(eq, deviceId, uuid)
+                                equipClass.setTvRight(equipEntity!!.systemName + "/" + equipEntity!!.name + "-" + equipEntity!!.code)
+                                graphyVM.getEquipInfosConfig(equipEntity!!)
+                                graphyVM.getEquipment(deviceId)
+                                localId.setEtRight(eq.code + "-")
+                                break
+                            }
+                        }
+                    }
                     dialog.setList(equips);
                 }
                 GraphyVM.EQUIP_INFO -> {
@@ -459,11 +482,19 @@ class EquipDetailActivity : BaseActivity(), View.OnTouchListener {
                         }
                     }
                 }
+                GraphyVM.SERVE_AREA -> {
+                    serveArea.setTvRight(params.toString())
+                }
             }
         }, this, buildingId, floorId, projectId)
         graphyVM.getEquipment(deviceId)
     }
 
+    override fun onResume() {
+        super.onResume()
+        graphyVM.getServeArea(if (TextUtils.isEmpty(deviceId)) uuid else deviceId)
+    }
+
     private fun addPic() {
         val config: ImgSelConfig = ImgSelConfig.Builder(this, loader) // 是否多选
             .multiSelect(true) // 是否记住上次选中记录, 仅当multiSelect为true的时候配置,默认为true

+ 53 - 10
demo/src/main/java/com/sybotan/android/demo/activities/GraphyActivity.kt

@@ -58,6 +58,7 @@ import com.arashivision.sdkcamera.camera.InstaCameraManager
 import com.arashivision.sdkcamera.camera.callback.ICameraChangedCallback
 import com.arashivision.sdkcamera.camera.callback.ICaptureStatusListener
 import com.bumptech.glide.Glide
+import com.framework.mvvm.model.db.entity.task.ObjectEntity
 import com.framework.mvvm.model.db.entity.task.StrInfosforEquip
 import com.framework.mvvm.mv.AdmViewModel
 import com.google.gson.Gson
@@ -97,6 +98,7 @@ import java.text.SimpleDateFormat
 import java.util.*
 import java.util.concurrent.atomic.AtomicInteger
 import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
 
 
 @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
@@ -216,6 +218,7 @@ class GraphyActivity : BaseActivity(), ICaptureStatusListener, ICameraChangedCal
     private lateinit var webview: WebView
     private lateinit var quesIma: ImageView
     private lateinit var equipDetail: TextView
+    private lateinit var equipContainer: TextView
     private lateinit var equipDelete: TextView
 
     /**
@@ -363,6 +366,7 @@ class GraphyActivity : BaseActivity(), ICaptureStatusListener, ICameraChangedCal
         webview = findViewById(R.id.webview)
         quesIma = findViewById(R.id.quesIma)
         equipDetail = findViewById(R.id.equipDetail)
+        equipContainer = findViewById(R.id.equipContainer)
         equipDelete = findViewById(R.id.equipDelete)
 
     }
@@ -625,6 +629,7 @@ class GraphyActivity : BaseActivity(), ICaptureStatusListener, ICameraChangedCal
                             equipDelete.alpha = 1f
                             equipDetail.isEnabled = true
                             equipDelete.isEnabled = true
+                            graphyVM.getContainer(point.data.deviceId!!)
                             point.data.equipState = 4
                             equipDetail.setOnClickListener {
                                 val intent =
@@ -658,20 +663,37 @@ class GraphyActivity : BaseActivity(), ICaptureStatusListener, ICameraChangedCal
                                 intent.putExtra("equip", bundle)
                                 startActivityForResult(intent, REQUEST_CODE_EQUIP)
                             }
+                            equipContainer.setOnClickListener {
+                                val intent =
+                                    Intent(this@GraphyActivity, EquipContainerActivity::class.java)
+                                val bundle = Bundle()
+                                bundle.putString("buildingId", buildingId)
+                                bundle.putString("floorId", floorId)
+                                bundle.putString("projectId", projectId)
+                                bundle.putString("deviceId", point.data.deviceId)
+                                val position =
+                                    gson.fromJson(point.data.position, Position::class.java)
+                                bundle.putSerializable("position", position)
+                                bundle.putString("spaceId", scene.choseSpace!!.data.id)
+                                intent.putExtra("params", bundle)
+                                startActivity(intent)
+                            }
                             equipDelete.setOnClickListener {
                                 val builder: AlertDialog.Builder =
                                     AlertDialog.Builder(this@GraphyActivity)
                                         .setTitle("确认标记/删除设备吗")
-                                        .setPositiveButton("确定",
-                                            DialogInterface.OnClickListener { dialogInterface, i ->
-                                                graphyVM.insProblemEquip(
-                                                    scene.choseSpace!!,
-                                                    point.data.deviceId!!
-                                                )
-                                            }).setNegativeButton("取消",
-                                            DialogInterface.OnClickListener { dialogInterface, i ->
-                                                dialogInterface.dismiss()
-                                            })
+                                        .setPositiveButton(
+                                            "确定"
+                                        ) { _, _ ->
+                                            graphyVM.insProblemEquip(
+                                                scene.choseSpace!!,
+                                                point.data.deviceId!!
+                                            )
+                                        }.setNegativeButton(
+                                            "取消"
+                                        ) { dialogInterface, _ ->
+                                            dialogInterface.dismiss()
+                                        }
                                 builder.create().show()
 
 
@@ -683,8 +705,10 @@ class GraphyActivity : BaseActivity(), ICaptureStatusListener, ICameraChangedCal
                     }
                     if (!hasChooseEquip) {
                         equipDetail.alpha = 0.5f
+                        equipContainer.alpha = 0.5f
                         equipDelete.alpha = 0.5f
                         equipDetail.isEnabled = false
+                        equipContainer.isEnabled = false
                         equipDelete.isEnabled = false
                     } else {
                         return
@@ -1362,6 +1386,21 @@ class GraphyActivity : BaseActivity(), ICaptureStatusListener, ICameraChangedCal
                     graphyVM.getSpaceEq(scene.choseSpace)
                     graphyVM.floorPipe()
                 }
+
+                GraphyVM.CONTAINER_EQUIP -> {
+                    val hashMap = params as HashMap<String, Any>
+                    val objs = hashMap.get("objs") as ArrayList<ObjectEntity>
+                    val configs = hashMap.get("configs") as ArrayList<ObjectEntity>
+                    if (!CommonUtils.IsNull(configs)) {
+                        equipContainer.setText("无模型设备(${objs.size})")
+                        equipContainer.isEnabled = true
+                        equipContainer.alpha = 1f
+                    } else {
+                        equipContainer.setText("无模型设备")
+                        equipContainer.isEnabled = false
+                        equipContainer.alpha = 0.5f
+                    }
+                }
             }
         }, this, buildingId, floorId, projectId)
         graphyVM.getMap()
@@ -1762,8 +1801,10 @@ class GraphyActivity : BaseActivity(), ICaptureStatusListener, ICameraChangedCal
             }
             position.z = z
             equipDetail.alpha = 0.5f
+            equipContainer.alpha = 0.5f
             equipDelete.alpha = 0.5f
             equipDetail.isEnabled = false
+            equipContainer.isEnabled = false
             equipDelete.isEnabled = false
             graphyVM.insEquip(bus.equip, position)
         }
@@ -1774,8 +1815,10 @@ class GraphyActivity : BaseActivity(), ICaptureStatusListener, ICameraChangedCal
             scene.eqItem?.let { it1 -> scene.removeItem(it1) }
             scene.type = "normal"
             equipDetail.alpha = 0.5f
+            equipContainer.alpha = 0.5f
             equipDelete.alpha = 0.5f
             equipDetail.isEnabled = false
+            equipContainer.isEnabled = false
             equipDelete.isEnabled = false
             graphyVM.getSpaceEq(scene.choseSpace)
         }

+ 84 - 39
demo/src/main/java/com/sybotan/android/demo/activities/ServeAreaActivity.kt

@@ -24,55 +24,31 @@
 package com.sybotan.android.demo.activities
 
 import android.annotation.SuppressLint
+import android.content.DialogInterface
 import android.graphics.Color
 import android.graphics.PointF
 import android.os.Bundle
 import android.widget.TextView
-import android.widget.Toast
-import android.widget.*
-import androidx.appcompat.widget.AppCompatEditText
-import androidx.appcompat.widget.LinearLayoutCompat
-import androidx.core.content.ContextCompat
-import androidx.recyclerview.widget.GridLayoutManager
+import androidx.appcompat.app.AlertDialog
 import cn.sagacloud.android.cadengine.FloorScene
-import cn.sagacloud.android.cadengine.Lasso
 import cn.sagacloud.android.cadengine.Opt
 import cn.sagacloud.android.cadengine.items.*
 import cn.sagacloud.android.cadengine.types.*
 import cn.sagacloud.android.cadengine.types.Space
-import com.bumptech.glide.Glide
 import com.google.gson.Gson
-import com.google.gson.JsonArray
-import com.google.gson.JsonElement
-import com.google.gson.JsonObject
-import com.google.gson.reflect.TypeToken
 import com.sybotan.android.demo.R
-import com.sybotan.android.demo.adapter.PictureAdapter
 import com.sybotan.android.demo.bean.result.*
 import com.sybotan.android.demo.graphy.GraphyHelper
-import com.sybotan.android.demo.retrofit.Constant
-import com.sybotan.android.demo.tools.*
-import com.sybotan.android.demo.view.MaxHeightRecyclerView
+import com.sybotan.android.demo.tools.ToastUtils
 import com.sybotan.android.demo.viewmodel.GraphyVM
 import com.sybotan.android.demo.viewmodel.SpaceMapVM
 import com.sybotan.android.graphy.SGraphyView
-import com.yuyh.library.imgsel.ImageLoader
-import com.yuyh.library.imgsel.ImgSelConfig
-import com.zbar.lib.CaptureActivity
-import io.reactivex.ObservableEmitter
 import org.jetbrains.anko.doAsync
 import org.jetbrains.anko.uiThread
-import org.json.JSONException
-import org.json.JSONObject
 import org.kodein.di.DI
 import org.kodein.di.DIAware
 import org.kodein.di.android.closestDI
-import java.io.*
-import java.util.*
 import kotlin.collections.ArrayList
-import kotlin.math.abs
-import kotlin.math.pow
-import kotlin.math.sqrt
 
 /**
  * 设备服务区域绘制
@@ -94,6 +70,7 @@ class ServeAreaActivity : BaseActivity(), DIAware {
     private var scale: Float = 0f
     private lateinit var area: ServeAreaItem
     private lateinit var centerPoint: PointF
+    private lateinit var position: Position
 
     /** 楼层地图 */
     private lateinit var floorMap: String
@@ -107,6 +84,7 @@ class ServeAreaActivity : BaseActivity(), DIAware {
     private lateinit var graphyView: SGraphyView
     private lateinit var close: TextView
     private lateinit var save: TextView
+    private lateinit var delete: TextView
     val graphyHelper: GraphyHelper = GraphyHelper(scene, this)
 
     /**
@@ -138,20 +116,28 @@ class ServeAreaActivity : BaseActivity(), DIAware {
         graphyView = findViewById(R.id.graphyView)
         close = findViewById(R.id.close)
         save = findViewById(R.id.save)
+        delete = findViewById(R.id.delete)
     }
 
     /**
      * 初始化数据
      */
     private fun initData() {
-        val floor = intent.getBundleExtra("params")!!
-        buildingId = floor.getString("buildingId", "")!!
-        floorId = floor.getString("floorId", "")!!
-        floorMap = floor.getString("floorMap", "")!!
-        projectId = floor.getString("projectId", "")!!
-        deviceId = floor.getString("deviceId", "")!!
-        scale = floor.getFloat("scale")!!
-        centerPoint = floor.getParcelable("centerPoint")!!
+        val params = intent.getBundleExtra("params")!!
+        buildingId = params.getString("buildingId", "")!!
+        floorId = params.getString("floorId", "")!!
+        floorMap = params.getString("floorMap", "")!!
+        projectId = params.getString("projectId", "")!!
+        deviceId = params.getString("deviceId", "")!!
+        scale = params.getFloat("scale")!!
+        centerPoint = params.getParcelable("centerPoint")!!
+        position = (params.getSerializable("position") as Position?)!!
+        val p = Point()
+        p.mX = position.x.toFloat()
+        p.mY = position.y.toFloat()
+        val equipItem = EquipItem(this, p)
+        equipItem.zOrder = 100000f
+        scene.addItem(equipItem)
     }
 
 
@@ -173,11 +159,11 @@ class ServeAreaActivity : BaseActivity(), DIAware {
                         override fun onScroll(x: Float, y: Float) {
                             hasMove = true
                             scene.removeItem(area)
-                            val newData = ArrayList<PointF>()
+                            points.clear()
                             for (it in items) {
-                                newData.add(PointF(it.x, it.y))
+                                points.add(PointF(it.x, it.y))
                             }
-                            area = ServeAreaItem(newData)
+                            area = ServeAreaItem(points)
                             area.zOrder = 100000f
                             scene.addItem(area)
                         }
@@ -197,8 +183,34 @@ class ServeAreaActivity : BaseActivity(), DIAware {
      */
     private fun initListener() {
         close.setOnClickListener { finish() }
-        save.setOnClickListener { spaceMapVM.insServeArea() }
+        save.setOnClickListener {
+            if (points.size < 2) {
+                ToastUtils.showMyToast("请绘制服务区域")
+                return@setOnClickListener
+            }
+            spaceMapVM.insServeArea(deviceId, points)
+        }
+        delete.setOnClickListener {
+            val builder: AlertDialog.Builder =
+                AlertDialog.Builder(this@ServeAreaActivity)
+                    .setTitle("确认删除服务区域吗")
+                    .setPositiveButton("确定",
+                        DialogInterface.OnClickListener { dialogInterface, i ->
+                            spaceMapVM.deleteServeArea(deviceId)
+                            scene.removeItem(area)
+                            for (item in items) {
+                                scene.removeItem(item)
+                            }
+                            items.clear()
+                            points.clear()
+                            hasMove = false
 
+                        }).setNegativeButton("取消",
+                        DialogInterface.OnClickListener { dialogInterface, i ->
+                            dialogInterface.dismiss()
+                        })
+            builder.create().show()
+        }
     }
 
     /**
@@ -261,9 +273,42 @@ class ServeAreaActivity : BaseActivity(), DIAware {
                     }
                 }
 
+                /** 查询空间和任务 */
+                SpaceMapVM.SERVE_AREA -> {
+                    val pa = params as ArrayList<PointF>
+                    for (i in 0..pa.size) {
+                        if (i == 0 || i == 2) {
+                            val point = pa[i]
+                            points.add(point)
+                            val item = AreaNodeItem(point)
+                            item.zOrder = 100000f
+                            scene.addItem(item)
+                            items.add(item)
+                            item.setOnScrollListener(object : AreaNodeItem.OnScroll {
+                                override fun onScroll(x: Float, y: Float) {
+                                    hasMove = true
+                                    scene.removeItem(area)
+                                    points.clear()
+                                    for (it in items) {
+                                        points.add(PointF(it.x, it.y))
+                                    }
+                                    area = ServeAreaItem(points)
+                                    area.zOrder = 100000f
+                                    scene.addItem(area)
+                                }
+                            })
+                        }
+                    }
+                    hasMove = true
+                    area = ServeAreaItem(points)
+                    area.zOrder = 100000f
+                    scene.addItem(area)
+                }
+
             }
         }, this, buildingId, floorId, projectId)
         spaceMapVM.getMap()
+        spaceMapVM.getServeArea(deviceId)
 //        spaceMapVM.getSpaceJob()
     }
 

+ 5 - 3
demo/src/main/java/com/sybotan/android/demo/activities/SpaceMapActivity.kt

@@ -333,7 +333,7 @@ class SpaceMapActivity : BaseActivity(), DIAware {
                                 val h = Lasso.beeline(point1, point2, point3)
                                 if (h < dist) {
                                     if (oprateItem != null) {
-                                        scene.addItem(oprateItem!!)
+                                        scene.removeItem(oprateItem!!)
                                         oprateItem = null
                                     }
                                     hasChoose = true
@@ -555,7 +555,8 @@ class SpaceMapActivity : BaseActivity(), DIAware {
                         spaceMapVM.getProblem(scene.choseSpace)
                         /** 跳转空间详情 */
                         addSpaceDetail.setOnClickListener {
-                            val intent = Intent(this@SpaceMapActivity, SpaceDetailActivity::class.java)
+                            val intent =
+                                Intent(this@SpaceMapActivity, SpaceDetailActivity::class.java)
                             val bundle = Bundle()
                             bundle.putString("buildingId", buildingId)
                             bundle.putString("floorId", floorId)
@@ -574,7 +575,8 @@ class SpaceMapActivity : BaseActivity(), DIAware {
                         }
                         /** 跳转空间详情 */
                         addSpaceDetail2.setOnClickListener {
-                            val intent = Intent(this@SpaceMapActivity, SpaceDetailActivity::class.java)
+                            val intent =
+                                Intent(this@SpaceMapActivity, SpaceDetailActivity::class.java)
                             val bundle = Bundle()
                             bundle.putString("buildingId", buildingId)
                             bundle.putString("floorId", floorId)

+ 129 - 0
demo/src/main/java/com/sybotan/android/demo/adapter/ContainerExpandAdapter.kt

@@ -0,0 +1,129 @@
+package com.sybotan.android.demo.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseExpandableListAdapter
+import android.widget.ImageView
+import android.widget.TextView
+import com.framework.mvvm.model.db.entity.task.ObjectEntity
+import com.framework.mvvm.model.db.entity.task.RelationEntity
+import com.sybotan.android.demo.R
+import com.sybotan.android.demo.bean.ContainerParentBean
+import com.sybotan.android.demo.bean.RelConfigEntityPackClass
+import com.sybotan.android.demo.bean.RelationPackClass
+import java.lang.StringBuilder
+
+
+class ContainerExpandAdapter(
+    context: Context,
+    groups: List<ContainerParentBean>,
+    items: MutableList<List<ObjectEntity>>
+) : BaseExpandableListAdapter() {
+    private var addRel: AddRel? = null
+    private var deleteRel: DeleteRel? = null
+    private var itemClick: ItemClick? = null
+    private var mContext: Context = context
+    private var mGroups: List<ContainerParentBean> = groups
+    private var mItems: List<List<ObjectEntity>> = items
+    private var mLayoutInflater: LayoutInflater = LayoutInflater.from(context)
+
+    override fun getGroupCount(): Int {
+        return mGroups.size
+    }
+
+    override fun getChildrenCount(groupPosition: Int): Int {
+        return mItems[groupPosition].size
+    }
+
+    override fun getGroup(groupPosition: Int): ContainerParentBean {
+        return mGroups[groupPosition]
+    }
+
+    override fun getChild(groupPosition: Int, childPosition: Int): ObjectEntity {
+        return mItems[groupPosition][childPosition]
+    }
+
+    override fun getGroupId(groupPosition: Int): Long {
+        return groupPosition.toLong()
+    }
+
+    override fun getChildId(groupPosition: Int, childPosition: Int): Long {
+        return childPosition.toLong()
+    }
+
+    override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean {
+        return true
+    }
+
+    override fun hasStableIds(): Boolean {
+        return false
+    }
+
+    override fun getGroupView(
+        groupPosition: Int,
+        isExpanded: Boolean,
+        convertView: View?,
+        parent: ViewGroup?
+    ): View {
+        var holder = convertView
+        if (holder == null) {
+            holder = mLayoutInflater.inflate(R.layout.group_expand_relation, parent, false)
+        }
+        val group: ContainerParentBean = mGroups[groupPosition]
+        val tvGroup = holder?.findViewById(R.id.title) as TextView
+        tvGroup.text = group.widgetName
+        val add = holder?.findViewById(R.id.add) as ImageView
+        add.setOnClickListener {
+            addRel?.onAdd(group)
+        }
+        return holder
+    }
+
+    override fun getChildView(
+        groupPosition: Int,
+        childPosition: Int,
+        isLastChild: Boolean,
+        convertView: View?,
+        parent: ViewGroup?
+    ): View {
+        var holder = convertView
+        if (holder == null) {
+            holder = mLayoutInflater.inflate(R.layout.child_expand_container, parent, false)
+        }
+        val itemTv = holder?.findViewById(R.id.title) as TextView
+        val item = mItems[groupPosition][childPosition]
+        itemTv.text = item.localId
+        holder.findViewById<TextView>(R.id.delete).setOnClickListener {
+            deleteRel?.ondelete(item)
+        }
+        holder.findViewById<TextView>(R.id.title).setOnClickListener {
+            itemClick?.onclick(item)
+        }
+        return holder
+    }
+
+    public interface AddRel {
+        fun onAdd(group: ContainerParentBean)
+    }
+
+    fun setOnAdd(addRel: AddRel) {
+        this.addRel = addRel
+    }
+
+    public interface DeleteRel {
+        fun ondelete(item: ObjectEntity)
+    }
+
+    fun setOnDelete(deleteRel: DeleteRel) {
+        this.deleteRel = deleteRel
+    }
+    public interface ItemClick {
+        fun onclick(item: ObjectEntity)
+    }
+
+    fun setOnItemClick(itemClick: ItemClick) {
+        this.itemClick = itemClick
+    }
+}

+ 10 - 0
demo/src/main/java/com/sybotan/android/demo/bean/ContainerParentBean.java

@@ -0,0 +1,10 @@
+package com.sybotan.android.demo.bean;
+
+/**
+ * Created by lihao.
+ * Date: 2021/11/4
+ */
+public class ContainerParentBean {
+    public String widgetCode;
+    public String widgetName;
+}

+ 106 - 0
demo/src/main/java/com/sybotan/android/demo/view/SwipMenuLayout/CstViewPager.java

@@ -0,0 +1,106 @@
+package com.sybotan.android.demo.view.SwipMenuLayout;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import androidx.viewpager.widget.PagerAdapter;
+import androidx.viewpager.widget.ViewPager;
+
+/**
+ * github SwipeMenuLayout
+ * 侧滑item
+ */
+public class CstViewPager extends ViewPager {
+    private static final String TAG = "zxt/CstViewPager";
+
+    private int mLastX, mLastY;
+
+
+    public CstViewPager(Context context) {
+        super(context);
+    }
+
+    public CstViewPager(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        //Log.i(TAG, "onInterceptTouchEvent() called with: ev = [" + ev + "]");
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+        boolean intercept = false;
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (isHorizontalScroll(x, y)) {
+                    //除了在 第一页的手指向右滑 , 最后一页的左滑,其他时刻都是父控件需要拦截事件
+                    if (isReactFirstPage() && isScrollRight(x)) {
+                        //Log.e(TAG, "第一页的手指向右滑]");
+                        intercept = false;
+                    } else if (isReachLastPage() && isScrollLeft(x)) {
+                        //Log.e(TAG, "最后一页的左滑");
+                        intercept = false;
+                    } else {
+                        //Log.e(TAG, "其他情况");
+                        intercept = true;
+                    }
+
+                } else {
+
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                break;
+            default:
+                break;
+        }
+
+        mLastX = x;
+        mLastY = y;
+
+        boolean onInterceptTouchEvent = super.onInterceptTouchEvent(ev);
+        return intercept || onInterceptTouchEvent;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        //Log.i(TAG, "onTouchEvent() called with: ev = [" + ev + "]");
+        return super.onTouchEvent(ev);
+    }
+
+    //是否在水平滑动
+    private boolean isHorizontalScroll(int x, int y) {
+        return Math.abs(y - mLastY) < Math.abs(x - mLastX);
+    }
+
+    //是否未到达最后一页
+    private boolean isReachLastPage() {
+        PagerAdapter adapter = getAdapter();
+        if (null != adapter && adapter.getCount() - 1 == getCurrentItem()) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    //是否在第一页
+    private boolean isReactFirstPage() {
+        if (getCurrentItem() == 0) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    //是否左滑
+    private boolean isScrollLeft(int x) {
+        return x - mLastX < 0;
+    }
+
+    private boolean isScrollRight(int x) {
+        return x - mLastX > 0;
+    }
+}

+ 623 - 0
demo/src/main/java/com/sybotan/android/demo/view/SwipMenuLayout/SwipeMenuLayout.java

@@ -0,0 +1,623 @@
+package com.sybotan.android.demo.view.SwipMenuLayout;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.PointF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.OvershootInterpolator;
+
+import com.sybotan.android.demo.R;
+
+
+/**
+ * github SwipeMenuLayout
+ * 侧滑item
+ */
+public class SwipeMenuLayout extends ViewGroup {
+    private static final String TAG = "zxt/SwipeMenuLayout";
+
+    private int mScaleTouchSlop;//为了处理单击事件的冲突
+    private int mMaxVelocity;//计算滑动速度用
+    private int mPointerId;//多点触摸只算第一根手指的速度
+    private int mHeight;//自己的高度
+    //右侧菜单宽度总和(最大滑动距离)
+    private int mRightMenuWidths;
+
+    //滑动判定临界值(右侧菜单宽度的40%) 手指抬起时,超过了展开,没超过收起menu
+    private int mLimit;
+
+    private View mContentView;//2016 11 13 add ,存储contentView(第一个View)
+
+    //private Scroller mScroller;//以前item的滑动动画靠它做,现在用属性动画做
+    //上一次的xy
+    private PointF mLastP = new PointF();
+    //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击除侧滑菜单之外的区域,关闭侧滑菜单。
+    //增加一个布尔值变量,dispatch函数里,每次down时,为true,move时判断,如果是滑动动作,设为false。
+    //在Intercept函数的up时,判断这个变量,如果仍为true 说明是点击事件,则关闭菜单。
+    private boolean isUnMoved = true;
+
+    //2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
+    //up-down的坐标,判断是否是滑动,如果是,则屏蔽一切点击事件
+    private PointF mFirstP = new PointF();
+    private boolean isUserSwiped;
+
+    //存储的是当前正在展开的View
+    private static SwipeMenuLayout mViewCache;
+
+    //防止多只手指一起滑我的flag 在每次down里判断, touch事件结束清空
+    private static boolean isTouching;
+
+    private VelocityTracker mVelocityTracker;//滑动速度变量
+    private android.util.Log LogUtils;
+
+    /**
+     * 右滑删除功能的开关,默认开
+     */
+    private boolean isSwipeEnable;
+
+    /**
+     * IOS、QQ式交互,默认开
+     */
+    private boolean isIos;
+
+    private boolean iosInterceptFlag;//IOS类型下,是否拦截事件的flag
+
+    /**
+     * 20160929add 左滑右滑的开关,默认左滑打开菜单
+     */
+    private boolean isLeftSwipe;
+
+    public SwipeMenuLayout(Context context) {
+        this(context, null);
+    }
+
+    public SwipeMenuLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SwipeMenuLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs, defStyleAttr);
+    }
+
+    public boolean isSwipeEnable() {
+        return isSwipeEnable;
+    }
+
+    /**
+     * 设置侧滑功能开关
+     *
+     * @param swipeEnable
+     */
+    public void setSwipeEnable(boolean swipeEnable) {
+        isSwipeEnable = swipeEnable;
+    }
+
+
+    public boolean isIos() {
+        return isIos;
+    }
+
+    /**
+     * 设置是否开启IOS阻塞式交互
+     *
+     * @param ios
+     */
+    public SwipeMenuLayout setIos(boolean ios) {
+        isIos = ios;
+        return this;
+    }
+
+    public boolean isLeftSwipe() {
+        return isLeftSwipe;
+    }
+
+    /**
+     * 设置是否开启左滑出菜单,设置false 为右滑出菜单
+     *
+     * @param leftSwipe
+     * @return
+     */
+    public SwipeMenuLayout setLeftSwipe(boolean leftSwipe) {
+        isLeftSwipe = leftSwipe;
+        return this;
+    }
+
+    /**
+     * 返回ViewCache
+     *
+     * @return
+     */
+    public static SwipeMenuLayout getViewCache() {
+        return mViewCache;
+    }
+
+    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+        mScaleTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mMaxVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
+        //初始化滑动帮助类对象
+        //mScroller = new Scroller(context);
+
+        //右滑删除功能的开关,默认开
+        isSwipeEnable = true;
+        //IOS、QQ式交互,默认开
+        isIos = true;
+        //左滑右滑的开关,默认左滑打开菜单
+        isLeftSwipe = true;
+        TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SwipeMenuLayout, defStyleAttr, 0);
+        int count = ta.getIndexCount();
+        for (int i = 0; i < count; i++) {
+            int attr = ta.getIndex(i);
+            //如果引用成AndroidLib 资源都不是常量,无法使用switch case
+            if (attr == R.styleable.SwipeMenuLayout_swipeEnable) {
+                isSwipeEnable = ta.getBoolean(attr, true);
+            } else if (attr == R.styleable.SwipeMenuLayout_ios) {
+                isIos = ta.getBoolean(attr, true);
+            } else if (attr == R.styleable.SwipeMenuLayout_leftSwipe) {
+                isLeftSwipe = ta.getBoolean(attr, true);
+            }
+        }
+        ta.recycle();
+
+
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        //Log.d(TAG, "onMeasure() called with: " + "widthMeasureSpec = [" + widthMeasureSpec + "], heightMeasureSpec = [" + heightMeasureSpec + "]");
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        setClickable(true);//令自己可点击,从而获取触摸事件
+
+        mRightMenuWidths = 0;//由于ViewHolder的复用机制,每次这里要手动恢复初始值
+        mHeight = 0;
+        int contentWidth = 0;//2016 11 09 add,适配GridLayoutManager,将以第一个子Item(即ContentItem)的宽度为控件宽度
+        int childCount = getChildCount();
+
+        //add by 2016 08 11 为了子View的高,可以matchParent(参考的FrameLayout 和LinearLayout的Horizontal)
+        final boolean measureMatchParentChildren = MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
+        boolean isNeedMeasureChildHeight = false;
+
+        for (int i = 0; i < childCount; i++) {
+            View childView = getChildAt(i);
+            //令每一个子View可点击,从而获取触摸事件
+            childView.setClickable(true);
+            if (childView.getVisibility() != GONE) {
+                //后续计划加入上滑、下滑,则将不再支持Item的margin
+                measureChild(childView, widthMeasureSpec, heightMeasureSpec);
+                //measureChildWithMargins(childView, widthMeasureSpec, 0, heightMeasureSpec, 0);
+                final MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
+                mHeight = Math.max(mHeight, childView.getMeasuredHeight()/* + lp.topMargin + lp.bottomMargin*/);
+                if (measureMatchParentChildren && lp.height == LayoutParams.MATCH_PARENT) {
+                    isNeedMeasureChildHeight = true;
+                }
+                if (i > 0) {//第一个布局是Left item,从第二个开始才是RightMenu
+                    mRightMenuWidths += childView.getMeasuredWidth();
+                } else {
+                    mContentView = childView;
+                    contentWidth = childView.getMeasuredWidth();
+                }
+            }
+        }
+        setMeasuredDimension(getPaddingLeft() + getPaddingRight() + contentWidth,
+                mHeight + getPaddingTop() + getPaddingBottom());//宽度取第一个Item(Content)的宽度
+        mLimit = mRightMenuWidths * 4 / 10;//滑动判断的临界值
+        //Log.d(TAG, "onMeasure() called with: " + "mRightMenuWidths = [" + mRightMenuWidths);
+        if (isNeedMeasureChildHeight) {//如果子View的height有MatchParent属性的,设置子View高度
+            forceUniformHeight(childCount, widthMeasureSpec);
+        }
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new MarginLayoutParams(getContext(), attrs);
+    }
+
+    /**
+     * 给MatchParent的子View设置高度
+     *
+     * @param count
+     * @param widthMeasureSpec
+     * @see android.widget.LinearLayout# 同名方法
+     */
+    private void forceUniformHeight(int count, int widthMeasureSpec) {
+        // Pretend that the linear layout has an exact size. This is the measured height of
+        // ourselves. The measured height should be the max height of the children, changed
+        // to accommodate the heightMeasureSpec from the parent
+        int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(),
+                MeasureSpec.EXACTLY);//以父布局高度构建一个Exactly的测量参数
+        for (int i = 0; i < count; ++i) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+                if (lp.height == LayoutParams.MATCH_PARENT) {
+                    // Temporarily force children to reuse their old measured width
+                    // FIXME: this may not be right for something like wrapping text?
+                    int oldWidth = lp.width;//measureChildWithMargins 这个函数会用到宽,所以要保存一下
+                    lp.width = child.getMeasuredWidth();
+                    // Remeasure with new dimensions
+                    measureChildWithMargins(child, widthMeasureSpec, 0, uniformMeasureSpec, 0);
+                    lp.width = oldWidth;
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        //LogUtils.e(TAG, "onLayout() called with: " + "changed = [" + changed + "], l = [" + l + "], t = [" + t + "], r = [" + r + "], b = [" + b + "]");
+        int childCount = getChildCount();
+        int left = 0 + getPaddingLeft();
+        int right = 0 + getPaddingLeft();
+        for (int i = 0; i < childCount; i++) {
+            View childView = getChildAt(i);
+            if (childView.getVisibility() != GONE) {
+                if (i == 0) {//第一个子View是内容 宽度设置为全屏
+                    childView.layout(left, getPaddingTop(), left + childView.getMeasuredWidth(), getPaddingTop() + childView.getMeasuredHeight());
+                    left = left + childView.getMeasuredWidth();
+                } else {
+                    if (isLeftSwipe) {
+                        childView.layout(left, getPaddingTop(), left + childView.getMeasuredWidth(), getPaddingTop() + childView.getMeasuredHeight());
+                        left = left + childView.getMeasuredWidth();
+                    } else {
+                        childView.layout(right - childView.getMeasuredWidth(), getPaddingTop(), right, getPaddingTop() + childView.getMeasuredHeight());
+                        right = right - childView.getMeasuredWidth();
+                    }
+
+                }
+            }
+        }
+        //Log.d(TAG, "onLayout() called with: " + "maxScrollGap = [" + maxScrollGap + "], l = [" + l + "], t = [" + t + "], r = [" + r + "], b = [" + b + "]");
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        //LogUtils.d(TAG, "dispatchTouchEvent() called with: " + "ev = [" + ev + "]");
+        if (isSwipeEnable) {
+            acquireVelocityTracker(ev);
+            final VelocityTracker verTracker = mVelocityTracker;
+            switch (ev.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    isUserSwiped = false;//2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
+                    isUnMoved = true;//2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。
+                    iosInterceptFlag = false;//add by 2016 09 11 ,每次DOWN时,默认是不拦截的
+                    if (isTouching) {//如果有别的指头摸过了,那么就return false。这样后续的move..等事件也不会再来找这个View了。
+                        return false;
+                    } else {
+                        isTouching = true;//第一个摸的指头,赶紧改变标志,宣誓主权。
+                    }
+                    mLastP.set(ev.getRawX(), ev.getRawY());
+                    mFirstP.set(ev.getRawX(), ev.getRawY());//2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
+
+                    //如果down,view和cacheview不一样,则立马让它还原。且把它置为null
+                    if (mViewCache != null) {
+                        if (mViewCache != this) {
+                            mViewCache.smoothClose();
+
+                            iosInterceptFlag = isIos;//add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。
+                        }
+                        //只要有一个侧滑菜单处于打开状态, 就不给外层布局上下滑动了
+                        getParent().requestDisallowInterceptTouchEvent(true);
+                    }
+                    //求第一个触点的id, 此时可能有多个触点,但至少一个,计算滑动速率用
+                    mPointerId = ev.getPointerId(0);
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    //add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。滑动也不该出现
+                    if (iosInterceptFlag) {
+                        break;
+                    }
+                    float gap = mLastP.x - ev.getRawX();
+                    //为了在水平滑动中禁止父类ListView等再竖直滑动
+                    if (Math.abs(gap) > 10 || Math.abs(getScrollX()) > 10) {//2016 09 29 修改此处,使屏蔽父布局滑动更加灵敏,
+                        getParent().requestDisallowInterceptTouchEvent(true);
+                    }
+                    //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。begin
+                    if (Math.abs(gap) > mScaleTouchSlop) {
+                        isUnMoved = false;
+                    }
+                    //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。end
+                    //如果scroller还没有滑动结束 停止滑动动画
+/*                    if (!mScroller.isFinished()) {
+                        mScroller.abortAnimation();
+                    }*/
+                    scrollBy((int) (gap), 0);//滑动使用scrollBy
+                    //越界修正
+                    if (isLeftSwipe) {//左滑
+                        if (getScrollX() < 0) {
+                            scrollTo(0, 0);
+                        }
+                        if (getScrollX() > mRightMenuWidths) {
+                            scrollTo(mRightMenuWidths, 0);
+                        }
+                    } else {//右滑
+                        if (getScrollX() < -mRightMenuWidths) {
+                            scrollTo(-mRightMenuWidths, 0);
+                        }
+                        if (getScrollX() > 0) {
+                            scrollTo(0, 0);
+                        }
+                    }
+
+                    mLastP.set(ev.getRawX(), ev.getRawY());
+                    break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    //2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
+                    if (Math.abs(ev.getRawX() - mFirstP.x) > mScaleTouchSlop) {
+                        isUserSwiped = true;
+                    }
+
+                    //add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。滑动也不该出现
+                    if (!iosInterceptFlag) {//且滑动了 才判断是否要收起、展开menu
+                        //求伪瞬时速度
+                        verTracker.computeCurrentVelocity(1000, mMaxVelocity);
+                        final float velocityX = verTracker.getXVelocity(mPointerId);
+                        if (Math.abs(velocityX) > 1000) {//滑动速度超过阈值
+                            if (velocityX < -1000) {
+                                if (isLeftSwipe) {//左滑
+                                    //平滑展开Menu
+                                    smoothExpand();
+
+                                } else {
+                                    //平滑关闭Menu
+                                    smoothClose();
+                                }
+                            } else {
+                                if (isLeftSwipe) {//左滑
+                                    // 平滑关闭Menu
+                                    smoothClose();
+                                } else {
+                                    //平滑展开Menu
+                                    smoothExpand();
+
+                                }
+                            }
+                        } else {
+                            if (Math.abs(getScrollX()) > mLimit) {//否则就判断滑动距离
+                                //平滑展开Menu
+                                smoothExpand();
+                            } else {
+                                // 平滑关闭Menu
+                                smoothClose();
+                            }
+                        }
+                    }
+                    //释放
+                    releaseVelocityTracker();
+                    //LogUtils.i(TAG, "onTouch A ACTION_UP ACTION_CANCEL:velocityY:" + velocityX);
+                    isTouching = false;//没有手指在摸我了
+                    break;
+                default:
+                    break;
+            }
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        //Log.d(TAG, "onInterceptTouchEvent() called with: ev = [" + ev + "]");
+        //add by zhangxutong 2016 12 07 begin:
+        //禁止侧滑时,点击事件不受干扰。
+        if (isSwipeEnable) {
+            switch (ev.getAction()) {
+                //add by zhangxutong 2016 11 04 begin :
+                // fix 长按事件和侧滑的冲突。
+                case MotionEvent.ACTION_MOVE:
+                    //屏蔽滑动时的事件
+                    if (Math.abs(ev.getRawX() - mFirstP.x) > mScaleTouchSlop) {
+                        return true;
+                    }
+                    break;
+                //add by zhangxutong 2016 11 04 end
+                case MotionEvent.ACTION_UP:
+                    //为了在侧滑时,屏蔽子View的点击事件
+                    if (isLeftSwipe) {
+                        if (getScrollX() > mScaleTouchSlop) {
+                            //add by 2016 09 10 解决一个智障问题~ 居然不给点击侧滑菜单 我跪着谢罪
+                            //这里判断落点在内容区域屏蔽点击,内容区域外,允许传递事件继续向下的的。。。
+                            if (ev.getX() < getWidth() - getScrollX()) {
+                                //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。
+                                if (isUnMoved) {
+                                    smoothClose();
+                                }
+                                return true;//true表示拦截
+                            }
+                        }
+                    } else {
+                        if (-getScrollX() > mScaleTouchSlop) {
+                            if (ev.getX() > -getScrollX()) {//点击范围在菜单外 屏蔽
+                                //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。
+                                if (isUnMoved) {
+                                    smoothClose();
+                                }
+                                return true;
+                            }
+                        }
+                    }
+                    //add by zhangxutong 2016 11 03 begin:
+                    // 判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
+                    if (isUserSwiped) {
+                        return true;
+                    }
+                    //add by zhangxutong 2016 11 03 end
+
+                    break;
+            }
+            //模仿IOS 点击其他区域关闭:
+            if (iosInterceptFlag) {
+                //IOS模式开启,且当前有菜单的View,且不是自己的 拦截点击事件给子View
+                return true;
+            }
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    /**
+     * 平滑展开
+     */
+    private ValueAnimator mExpandAnim, mCloseAnim;
+
+    private boolean isExpand;//代表当前是否是展开状态 2016 11 03 add
+
+    public void smoothExpand() {
+        //Log.d(TAG, "smoothExpand() called" + this);
+        /*mScroller.startScroll(getScrollX(), 0, mRightMenuWidths - getScrollX(), 0);
+        invalidate();*/
+        //展开就加入ViewCache:
+        mViewCache = SwipeMenuLayout.this;
+
+        //2016 11 13 add 侧滑菜单展开,屏蔽content长按
+        if (null != mContentView) {
+            mContentView.setLongClickable(false);
+        }
+
+        cancelAnim();
+        mExpandAnim = ValueAnimator.ofInt(getScrollX(), isLeftSwipe ? mRightMenuWidths : -mRightMenuWidths);
+        mExpandAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                scrollTo((Integer) animation.getAnimatedValue(), 0);
+            }
+        });
+        mExpandAnim.setInterpolator(new OvershootInterpolator());
+        mExpandAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                isExpand = true;
+            }
+        });
+        mExpandAnim.setDuration(300).start();
+    }
+
+    /**
+     * 每次执行动画之前都应该先取消之前的动画
+     */
+    private void cancelAnim() {
+        if (mCloseAnim != null && mCloseAnim.isRunning()) {
+            mCloseAnim.cancel();
+        }
+        if (mExpandAnim != null && mExpandAnim.isRunning()) {
+            mExpandAnim.cancel();
+        }
+    }
+
+    /**
+     * 平滑关闭
+     */
+    public void smoothClose() {
+        //Log.d(TAG, "smoothClose() called" + this);
+/*        mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0);
+        invalidate();*/
+        mViewCache = null;
+
+        //2016 11 13 add 侧滑菜单展开,屏蔽content长按
+        if (null != mContentView) {
+            mContentView.setLongClickable(true);
+        }
+
+        cancelAnim();
+        mCloseAnim = ValueAnimator.ofInt(getScrollX(), 0);
+        mCloseAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                scrollTo((Integer) animation.getAnimatedValue(), 0);
+            }
+        });
+        mCloseAnim.setInterpolator(new AccelerateInterpolator());
+        mCloseAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                isExpand = false;
+
+            }
+        });
+        mCloseAnim.setDuration(300).start();
+        //LogUtils.d(TAG, "smoothClose() called with:getScrollX() " + getScrollX());
+    }
+
+
+    /**
+     * @param event 向VelocityTracker添加MotionEvent
+     * @see VelocityTracker#obtain()
+     * @see VelocityTracker#addMovement(MotionEvent)
+     */
+    private void acquireVelocityTracker(final MotionEvent event) {
+        if (null == mVelocityTracker) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(event);
+    }
+
+    /**
+     * * 释放VelocityTracker
+     *
+     * @see VelocityTracker#clear()
+     * @see VelocityTracker#recycle()
+     */
+    private void releaseVelocityTracker() {
+        if (null != mVelocityTracker) {
+            mVelocityTracker.clear();
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    //每次ViewDetach的时候,判断一下 ViewCache是不是自己,如果是自己,关闭侧滑菜单,且ViewCache设置为null,
+    // 理由:1 防止内存泄漏(ViewCache是一个静态变量)
+    // 2 侧滑删除后自己后,这个View被Recycler回收,复用,下一个进入屏幕的View的状态应该是普通状态,而不是展开状态。
+    @Override
+    protected void onDetachedFromWindow() {
+        if (this == mViewCache) {
+            mViewCache.smoothClose();
+            mViewCache = null;
+        }
+        super.onDetachedFromWindow();
+    }
+
+    //展开时,禁止长按
+    @Override
+    public boolean performLongClick() {
+        if (Math.abs(getScrollX()) > mScaleTouchSlop) {
+            return false;
+        }
+        return super.performLongClick();
+    }
+
+    //平滑滚动 弃用 改属性动画实现
+/*    @Override
+    public void computeScroll() {
+        //判断Scroller是否执行完毕:
+        if (mScroller.computeScrollOffset()) {
+            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
+            //通知View重绘-invalidate()->onDraw()->computeScroll()
+            invalidate();
+        }
+    }*/
+
+    /**
+     * 快速关闭。
+     * 用于 点击侧滑菜单上的选项,同时想让它快速关闭(删除 置顶)。
+     * 这个方法在ListView里是必须调用的,
+     * 在RecyclerView里,视情况而定,如果是mAdapter.notifyItemRemoved(pos)方法不用调用。
+     */
+    public void quickClose() {
+        if (this == mViewCache) {
+            //先取消展开动画
+            cancelAnim();
+            mViewCache.scrollTo(0, 0);//关闭
+            mViewCache = null;
+        }
+    }
+
+}

+ 78 - 0
demo/src/main/java/com/sybotan/android/demo/viewmodel/ContainerVM.kt

@@ -0,0 +1,78 @@
+package com.sybotan.android.demo.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.viewModelScope
+import com.framework.mvvm.model.db.entity.task.*
+import com.framework.mvvm.model.repository.AdmRepository
+import com.sybotan.android.demo.activities.BaseActivity
+import com.sybotan.android.demo.bean.ContainerParentBean
+import com.sybotan.android.demo.retrofit.BaseViewModelInterface
+import kotlinx.coroutines.launch
+import org.kodein.di.DI
+import org.kodein.di.DIAware
+import org.kodein.di.android.x.closestDI
+import org.kodein.di.instance
+import java.util.*
+import kotlin.collections.ArrayList
+
+
+/**
+ * Created by lihao.
+ * Date: 2021/6/9
+ */
+class ContainerVM(
+    app: Application,
+    b: BaseViewModelInterface?, activity: BaseActivity?, //建筑id
+    private val buildingId: String, //楼层id
+    private val floorId: String,
+    private val projectId: String
+) : BaseViewModel(app, b, activity), DIAware {
+
+    override val di: DI by closestDI()
+    private val repo: AdmRepository by instance()
+
+    fun getConfig(deviceId: String) {
+        viewModelScope.launch {
+            val equip = repo.getObject(deviceId)
+            val configs = repo.getContainerConfig(equip!!.classCode!!)
+            val parent = ArrayList<ContainerParentBean>()
+            val children = ArrayList<ArrayList<ObjectEntity>>()
+            val conObjs = repo.getContainerObjs(deviceId)
+            for (config in configs) {
+                val child = ArrayList<ObjectEntity>()
+                val p = ContainerParentBean()
+                p.widgetCode = config.widgetCode
+                val equipment = repo.getEquipByCode(config.widgetCode)
+                p.widgetName = equipment?.name
+                parent.add(p)
+                for (obj in conObjs) {
+                    if (obj.classCode.equals(config.widgetCode)) {
+                        child.add(obj)
+                    }
+                }
+                children.add(child)
+            }
+            val hashMap = HashMap<String, Any>()
+            hashMap.put("title", equip.localId!!)
+            hashMap.put("parent", parent)
+            hashMap.put("child", children)
+            mEmitter.SendDircetive(CONTAINER, hashMap)
+        }
+    }
+
+    fun deleteObject(item: ObjectEntity, deviceId: String) {
+        viewModelScope.launch {
+            item.valid = 0
+            item.state = "1"
+            repo.insObject(item)
+            getConfig(deviceId)
+        }
+    }
+
+    companion object {
+
+        //
+        const val CONTAINER = "CONTAINER"
+
+    }
+}

+ 32 - 3
demo/src/main/java/com/sybotan/android/demo/viewmodel/GraphyVM.kt

@@ -187,7 +187,7 @@ class GraphyVM(
      */
     fun getSpaceEq(choseSpace: TunableSpaceItem?) {
         viewModelScope.launch {
-            var equips = repo.getEquipByFloorId(floorId)
+            val equips = repo.getEquipByFloorId(floorId)
 
             var models: ArrayList<SpaceEqModel> = ArrayList()
             for (equip in equips) {
@@ -443,7 +443,8 @@ class GraphyVM(
         localName: String,
         localId: String,
         checked: Int,
-        position: Position
+        position: Position,
+        containerId: String,
     ) {
         viewModelScope.launch {
             val objects = repo.getObjects()
@@ -494,7 +495,9 @@ class GraphyVM(
                     virtualCodes = null,
                     infos = null,
                     strInfos = gson.toJson(hashMap),
-                    floorMap = null, state = "1"
+                    floorMap = null,
+                    state = "1",
+                    containerId = if (TextUtils.isEmpty(containerId)) "" else containerId
                 )
                 repo.insObject(objectEntity)
             } else {
@@ -1036,6 +1039,30 @@ class GraphyVM(
 
     }
 
+    fun getServeArea(deviceId: String) {
+        viewModelScope.launch {
+            val entity = repo.getServeAreaByObjId(deviceId)
+            if (entity == null) {
+                mEmitter.SendDircetive(SERVE_AREA, "无")
+            } else {
+                mEmitter.SendDircetive(SERVE_AREA, "有")
+            }
+        }
+
+    }
+
+    fun getContainer(deviceId: String) {
+        viewModelScope.launch {
+            val objs = repo.getContainerObjs(deviceId)
+            val equip = repo.getObject(deviceId)
+            val configs = repo.getContainerConfig(equip!!.classCode!!)
+            val hashMap = HashMap<String, Any>()
+            hashMap.put("configs",configs)
+            hashMap.put("objs",objs)
+            mEmitter.SendDircetive(CONTAINER_EQUIP, hashMap)
+        }
+    }
+
     companion object {
         const val UPDATE_APP = "UPDATE_APP"
         const val MAP_LOAD = "MAP_LOAD"
@@ -1077,6 +1104,8 @@ class GraphyVM(
         const val PIPE_OBJECT = "PIPE_OBJECT"
         const val PIPE = "PIPE"
         const val UPDATE_LOCATION = "UPDATE_LOCATION"
+        const val SERVE_AREA = "SERVE_AREA"
+        const val CONTAINER_EQUIP = "CONTAINER_EQUIP"
     }
 
 }

+ 61 - 1
demo/src/main/java/com/sybotan/android/demo/viewmodel/SpaceMapVM.kt

@@ -15,6 +15,7 @@ import cn.sagacloud.android.cadengine.types.PointZ
 import com.framework.mvvm.model.db.entity.task.*
 import com.framework.mvvm.model.repository.AdmRepository
 import com.google.gson.Gson
+import com.google.gson.JsonArray
 import com.google.gson.reflect.TypeToken
 import com.sybotan.android.demo.activities.BaseActivity
 import com.sybotan.android.demo.bean.result.*
@@ -28,6 +29,7 @@ import org.kodein.di.DIAware
 import org.kodein.di.android.x.closestDI
 import org.kodein.di.instance
 import java.util.*
+import kotlin.collections.ArrayList
 
 
 /**
@@ -401,8 +403,63 @@ class SpaceMapVM(
         }
     }
 
-    fun insServeArea() {
+    fun insServeArea(deviceId: String, points: ArrayList<PointF>) {
+        viewModelScope.launch {
+            val geom = ArrayList<PointF>()
+            geom.add(points[0])
+            geom.add(PointF(points[0].x, points[1].y))
+            geom.add(points[1])
+            geom.add(PointF(points[1].x, points[0].y))
+            var entity = repo.getServeAreaByObjId(deviceId)
+            if (entity == null) {
+                entity = ServeAreaEntity(
+                    id = UUID.randomUUID().toString(),
+                    projectId = projectId,
+                    buildingId = buildingId,
+                    floorId = floorId,
+                    state = "1",
+                    uploadFlag = null,
+                    valid = 1,
+                    creator = null,
+                    creationTime = null,
+                    modifier = null,
+                    modifiedTime = null,
+                    objId = deviceId,
+                    geomType = "polygon",
+                    geom = gson.toJsonTree(geom),
+                    serveType = null, remark = null
+                )
+            } else {
+                entity.geom = gson.toJsonTree(geom)
+                entity.state = "1"
+            }
+            repo.insServeArea(entity)
+            mActivity.finish()
+        }
+    }
+
+    fun getServeArea(deviceId: String) {
+        viewModelScope.launch {
+            val entity = repo.getServeAreaByObjId(deviceId)
+            if (entity != null) {
+                val ar = gson.fromJson<List<PointF>>(
+                    gson.toJson(entity.geom),
+                    object : TypeToken<List<PointF?>?>() {}.type
+                )
+                mEmitter.SendDircetive(SERVE_AREA, ar)
+            }
+        }
+    }
 
+    fun deleteServeArea(deviceId: String) {
+        viewModelScope.launch {
+            val entity = repo.getServeAreaByObjId(deviceId)
+            if (entity != null) {
+                entity.valid = 0
+                entity.state = "1"
+                repo.insServeArea(entity)
+            }
+        }
     }
 
 
@@ -434,5 +491,8 @@ class SpaceMapVM(
         const val SET_JOB = "SET_JOB"
         const val PIPE = "PIPE"
 
+        //服务区域
+        const val SERVE_AREA = "SERVE_AREA"
+
     }
 }

+ 24 - 0
demo/src/main/res/layout/activity_equip_container.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".activities.EquipContainerActivity">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:padding="10dp"
+        android:text=""
+        android:textColor="@color/black_272727"
+        android:textSize="18sp" />
+
+    <com.sybotan.android.demo.view.SuperExpandableListView
+        android:id="@+id/expandlv"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</LinearLayout>

+ 37 - 13
demo/src/main/res/layout/activity_graphy.xml

@@ -197,21 +197,45 @@
             </LinearLayout>
 
 
-            <TextView
-                android:id="@+id/equipDetail"
-                android:layout_width="wrap_content"
+            <androidx.appcompat.widget.LinearLayoutCompat
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_gravity="center_horizontal"
                 android:layout_marginTop="5dp"
-                android:alpha="0.5"
-                android:background="@drawable/bg_blue_3bacb2"
-                android:paddingLeft="34dp"
-                android:paddingTop="2dp"
-                android:paddingRight="34dp"
-                android:paddingBottom="2dp"
-                android:text="记录设备信息"
-                android:textColor="@color/white_ffffff"
-                android:textSize="14sp" />
+                android:gravity="center_horizontal"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:id="@+id/equipDetail"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal"
+                    android:alpha="0.5"
+                    android:background="@drawable/bg_blue_3bacb2"
+                    android:paddingLeft="34dp"
+                    android:paddingTop="2dp"
+                    android:paddingRight="34dp"
+                    android:paddingBottom="2dp"
+                    android:text="记录设备信息"
+                    android:textColor="@color/white_ffffff"
+                    android:textSize="14sp" />
+
+                <TextView
+                    android:id="@+id/equipContainer"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal"
+                    android:layout_marginLeft="20dp"
+                    android:layout_marginTop="5dp"
+                    android:alpha="0.5"
+                    android:background="@drawable/bg_blue_3bacb2"
+                    android:paddingLeft="34dp"
+                    android:paddingTop="2dp"
+                    android:paddingRight="34dp"
+                    android:paddingBottom="2dp"
+                    android:text="无模型设备"
+                    android:textColor="@color/white_ffffff"
+                    android:textSize="14sp" />
+            </androidx.appcompat.widget.LinearLayoutCompat>
 
             <TextView
                 android:id="@+id/equipDelete"

+ 17 - 1
demo/src/main/res/layout/activity_serve_area.xml

@@ -46,6 +46,7 @@
     <androidx.appcompat.widget.LinearLayoutCompat
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:background="@color/grey_50"
         android:orientation="vertical"
         android:paddingTop="20dp"
         android:paddingBottom="20dp">
@@ -72,7 +73,8 @@
                 android:paddingLeft="10dp"
                 android:text="圆形"
                 android:textColor="@color/black_272727"
-                android:textSize="16sp" />
+                android:textSize="16sp"
+                android:visibility="gone" />
         </RadioGroup>
 
         <androidx.appcompat.widget.LinearLayoutCompat
@@ -97,6 +99,20 @@
                 android:textSize="16sp" />
 
             <TextView
+                android:id="@+id/delete"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="36dp"
+                android:layout_weight="1"
+                android:background="@drawable/bg_grey_e6e6e6"
+                android:gravity="center"
+                android:paddingTop="8dp"
+                android:paddingBottom="8dp"
+                android:text="删除"
+                android:textColor="@color/black_272727"
+                android:textSize="16sp" />
+
+            <TextView
                 android:id="@+id/save"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"

+ 30 - 0
demo/src/main/res/layout/child_expand_container.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.sybotan.android.demo.view.SwipMenuLayout.SwipeMenuLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="62dp"
+    android:background="@drawable/common_line_ffffff_single_all"
+    android:orientation="horizontal">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_vertical"
+        android:paddingLeft="26dp"
+        android:gravity="center_vertical"
+        android:layout_marginBottom="10dp"
+        android:text=""
+        android:textColor="@color/gray_808080"
+        android:textSize="16sp" />
+
+    <TextView
+        android:id="@+id/delete"
+        android:layout_width="82dp"
+        android:layout_height="match_parent"
+        android:background="@color/red_400"
+        android:gravity="center"
+        android:text="删除"
+        android:textColor="@color/white_ffffff"
+        android:textSize="16sp" />
+
+</com.sybotan.android.demo.view.SwipMenuLayout.SwipeMenuLayout>

+ 1 - 0
demo/src/main/res/layout/group_expand_relation.xml

@@ -2,6 +2,7 @@
 <androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:background="@drawable/common_line_ffffff_single_all"
     android:orientation="horizontal">
 
     <TextView

+ 5 - 0
demo/src/main/res/values/attrs.xml

@@ -23,4 +23,9 @@
     <declare-styleable name="MaxHeightRecyclerView">
         <attr name="maxHeight" format="dimension" />
     </declare-styleable>
+    <declare-styleable name="SwipeMenuLayout">
+        <attr name="swipeEnable" format="boolean" />
+        <attr name="ios" format="boolean" />
+        <attr name="leftSwipe" format="boolean" />
+    </declare-styleable>
 </resources>