|
|
@@ -0,0 +1,221 @@
|
|
|
+package com.grkj.iscs.view.widget
|
|
|
+
|
|
|
+import android.graphics.Bitmap
|
|
|
+import android.graphics.Canvas
|
|
|
+import android.graphics.Color
|
|
|
+import android.graphics.Matrix
|
|
|
+import android.graphics.Paint
|
|
|
+import android.graphics.PointF
|
|
|
+import android.os.SystemClock
|
|
|
+import android.util.Pair
|
|
|
+import android.view.MotionEvent
|
|
|
+import androidx.core.content.ContextCompat
|
|
|
+import com.grkj.iscs.MyApplication
|
|
|
+import com.grkj.iscs.R
|
|
|
+import com.grkj.iscs.modbus.ModBusController
|
|
|
+import com.grkj.iscs.util.BitmapUtil
|
|
|
+import com.onlylemi.mapview.library.MapView
|
|
|
+import com.onlylemi.mapview.library.layer.MapBaseLayer
|
|
|
+import kotlin.math.cos
|
|
|
+import kotlin.math.sin
|
|
|
+
|
|
|
+class CustomSwitchStationLayer @JvmOverloads constructor(
|
|
|
+ mapView: MapView?, private var pointList: List<IsolationPoint> = mutableListOf()
|
|
|
+) : MapBaseLayer(mapView) {
|
|
|
+ // 呼吸灯周期(毫秒)
|
|
|
+ private val breathePeriod = 1200f
|
|
|
+ private val FRAME_INTERVAL = 32L // 约 30fps
|
|
|
+ private var alpha = 255
|
|
|
+ private val refreshRunnable = Runnable {
|
|
|
+ // 2. uptimeMillis 保证单调递增
|
|
|
+ val now = SystemClock.uptimeMillis()
|
|
|
+ // 3. 先在 Long 上做模,再转 Float 计算 phase
|
|
|
+ val phase = ((now % breathePeriod).toFloat()) / breathePeriod
|
|
|
+ val normalized = ((sin(phase * 2 * Math.PI) + 1) / 2).toFloat()
|
|
|
+ alpha = (normalized * (255 - 50) + 50).toInt()
|
|
|
+ mapView?.refresh()
|
|
|
+ mapView?.post {
|
|
|
+ startAnimation()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private var listener: MarkIsClickListener? = null
|
|
|
+ private var radiusMark = 0f
|
|
|
+ private var isClickMark: Boolean = false
|
|
|
+ private var num: Int = -1
|
|
|
+ private lateinit var paint: Paint
|
|
|
+ private var btnIndex: Int = -1
|
|
|
+ private var currentZoom = 0f
|
|
|
+ private var currentDegree = 0f
|
|
|
+ private var bgBitmap: Bitmap? = null
|
|
|
+ private var coverBitmap: Bitmap? = null
|
|
|
+ private var ratio: Float = 1f
|
|
|
+ private var switchSize: Float = 1f
|
|
|
+
|
|
|
+ init {
|
|
|
+ initLayer()
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun initLayer() {
|
|
|
+ radiusMark = setValue(6.0f)
|
|
|
+ paint = Paint()
|
|
|
+ paint.isAntiAlias = true
|
|
|
+ paint.style = Paint.Style.FILL_AND_STROKE
|
|
|
+ }
|
|
|
+
|
|
|
+ fun setRatio(ratio: Float) {
|
|
|
+ this.ratio = ratio
|
|
|
+ bgBitmap = BitmapUtil.getResizedBitmapFromDrawable(
|
|
|
+ mapView.context,
|
|
|
+ R.drawable.red_stroke_bg,
|
|
|
+ (50 * ratio).toInt(),
|
|
|
+ (78 * ratio).toInt()
|
|
|
+ )!!
|
|
|
+ coverBitmap = BitmapUtil.getResizedBitmapFromDrawable(
|
|
|
+ mapView.context,
|
|
|
+ R.drawable.map_item_cover_bg,
|
|
|
+ (50 * ratio).toInt(),
|
|
|
+ (78 * ratio).toInt()
|
|
|
+ )!!
|
|
|
+ switchSize = setValue(4 * ratio)
|
|
|
+ }
|
|
|
+
|
|
|
+ fun startAnimation() {
|
|
|
+ // 先干掉前一次没执行的
|
|
|
+ mapView.removeCallbacks(refreshRunnable)
|
|
|
+ // 延后 16ms 再刷新,自动合并一堆连续的调用
|
|
|
+ mapView.postDelayed(refreshRunnable, FRAME_INTERVAL)
|
|
|
+ }
|
|
|
+
|
|
|
+ fun stopAnimation() {
|
|
|
+ // 先干掉前一次没执行的
|
|
|
+ mapView.removeCallbacks(refreshRunnable)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onTouch(event: MotionEvent) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun draw(
|
|
|
+ canvas: Canvas, currentMatrix: Matrix, currentZoom: Float, currentRotateDegrees: Float
|
|
|
+ ) {
|
|
|
+ this.currentZoom = currentZoom
|
|
|
+ currentDegree = 360 - currentRotateDegrees
|
|
|
+
|
|
|
+ if (!isVisible) return
|
|
|
+
|
|
|
+ canvas.save()
|
|
|
+ // 把 mapView 本身的缩放/平移/旋转一次性 concat 到 Canvas
|
|
|
+ canvas.concat(currentMatrix)
|
|
|
+ val switchData = ModBusController.getSwitchData()
|
|
|
+ pointList.forEach { point ->
|
|
|
+ val switchStatus = switchData
|
|
|
+ .find { it.idx == point.pointSerialNumber?.toInt() }?.enabled
|
|
|
+ // point.pos.x/y 已经是「图内像素坐标」
|
|
|
+ val x = point.pos.x
|
|
|
+ val y = point.pos.y
|
|
|
+ // 先画背景(它会被 currentMatrix 自动缩放)
|
|
|
+ bgBitmap?.let {
|
|
|
+ canvas.drawBitmap(
|
|
|
+ it, x, y, paint
|
|
|
+ )
|
|
|
+ paint.alpha = 255
|
|
|
+ if (switchStatus != null) {
|
|
|
+ // 再画 icon
|
|
|
+ if (switchStatus) {
|
|
|
+ paint.color = ContextCompat.getColor(
|
|
|
+ MyApplication.instance?.applicationContext!!,
|
|
|
+ R.color.common_switch_enable
|
|
|
+ )
|
|
|
+ paint.alpha = alpha
|
|
|
+ canvas.drawCircle(
|
|
|
+ x + (it.width - switchSize) / 2 + switchSize / 2,
|
|
|
+ y + (it.width - switchSize) / 2 + switchSize / 2,
|
|
|
+ switchSize, paint
|
|
|
+ )
|
|
|
+ paint.alpha = 255
|
|
|
+ } else {
|
|
|
+ paint.color = ContextCompat.getColor(
|
|
|
+ MyApplication.instance?.applicationContext!!,
|
|
|
+ R.color.common_switch_disable
|
|
|
+ )
|
|
|
+ canvas.drawCircle(
|
|
|
+ x + (it.width - switchSize) / 2 + switchSize / 2,
|
|
|
+ y + (it.width - switchSize) / 2 + switchSize / 2,
|
|
|
+ switchSize, paint
|
|
|
+ )
|
|
|
+ }
|
|
|
+// point.icon?.let { icon ->
|
|
|
+// canvas.drawBitmap(
|
|
|
+// icon,
|
|
|
+// x + (it.width - icon.width) / 2,
|
|
|
+// y + (it.width - icon.width) / 2,
|
|
|
+// paint
|
|
|
+// )
|
|
|
+// }
|
|
|
+ }
|
|
|
+ // 然后画文字
|
|
|
+ paint.style = Paint.Style.FILL
|
|
|
+ paint.strokeWidth = 1f
|
|
|
+ paint.color = Color.RED
|
|
|
+ paint.textSize = radiusMark * ratio // 这里是「图内」的文字大小,后面会跟着缩放
|
|
|
+ val textW = paint.measureText(point.entityName)
|
|
|
+ canvas.drawText(
|
|
|
+ point.entityName,
|
|
|
+ x + (it.width - textW) / 2,
|
|
|
+ y + (it.height - radiusMark / 2 - it.height / 10),
|
|
|
+ paint
|
|
|
+ )
|
|
|
+
|
|
|
+ // 如果选中,再叠加一个标记
|
|
|
+ if (point.isSelected) {
|
|
|
+ coverBitmap?.let {
|
|
|
+ canvas.drawBitmap(
|
|
|
+ it, x, y, paint
|
|
|
+ )
|
|
|
+ }
|
|
|
+ val checkW = paint.measureText("√")
|
|
|
+ paint.color = Color.WHITE
|
|
|
+ canvas.drawText(
|
|
|
+ "√",
|
|
|
+ x + (it.width - checkW) / 2,
|
|
|
+ y + (it.height / 2 + radiusMark / 2),
|
|
|
+ paint
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ canvas.restore()
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun rotatePoint(
|
|
|
+ oriX: Float, oriY: Float, desX: Float, desY: Float, rotateDegrees: Float
|
|
|
+ ): Pair<Float, Float> {
|
|
|
+ // 将度数转换为弧度
|
|
|
+ val theta = Math.toRadians(rotateDegrees.toDouble())
|
|
|
+
|
|
|
+ // 计算旋转后的坐标
|
|
|
+ val newX = (oriX - desX) * cos(theta) - (oriY - desY) * sin(theta) + desX
|
|
|
+ val newY = (oriX - desX) * sin(theta) + ((oriY - desY) * cos(theta)) + desY
|
|
|
+
|
|
|
+ return Pair(newX.toFloat(), newY.toFloat())
|
|
|
+ }
|
|
|
+
|
|
|
+ fun setMarkIsClickListener(listener: MarkIsClickListener?) {
|
|
|
+ this.listener = listener
|
|
|
+ }
|
|
|
+
|
|
|
+ interface MarkIsClickListener {
|
|
|
+ fun markIsClick(index: Int, btnIndex: Int)
|
|
|
+ }
|
|
|
+
|
|
|
+ data class IsolationPoint(
|
|
|
+ val pos: PointF,
|
|
|
+ val entityName: String,
|
|
|
+ val icon: Bitmap?,
|
|
|
+ val entityId: Long,
|
|
|
+ val pointSerialNumber: String?,
|
|
|
+ val isSelected: Boolean,
|
|
|
+ )
|
|
|
+}
|