From 1aa6f7a247e7186292439b67219bf228359dc393 Mon Sep 17 00:00:00 2001 From: yezhiqiu <983577727@qq.com> Date: Mon, 2 Mar 2026 15:33:05 +0800 Subject: [PATCH] =?UTF-8?q?1.=E5=AE=8C=E6=88=90Nearby=20=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E5=AE=9A=E4=BD=8D=E7=AD=96=E7=95=A5=202.=E5=A4=9A=E8=BE=B9?= =?UTF-8?q?=E5=BD=A2=E5=9B=B4=E6=A0=8F=E8=B7=9D=E7=A6=BB=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 6 +- .../tracker/ui/fragment/map/MapV3Fragment.kt | 19 +- .../FencesAddEditGoogleMapFragment.kt | 1 + .../tracker/util/bluetooth/SRBleCmdUtil.kt | 7 + .../tracker/util/bluetooth/SRBleUtil.kt | 3 + .../tracker/widget/FencesPolygonView.kt | 97 ++- .../abbidot/tracker/widget/FencesRectView3.kt | 818 ++++++++++++++++++ .../drawable-xhdpi/icon_fence_rect_zoom.png | Bin 0 -> 531 bytes .../drawable-xxhdpi/icon_fence_rect_zoom.png | Bin 0 -> 778 bytes .../drawable-xxxhdpi/icon_fence_rect_zoom.png | Bin 0 -> 1041 bytes .../main/res/layout/activity_fences_add.xml | 2 +- .../abbidot/baselibrary/constant/EventName.kt | 3 + 12 files changed, 948 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/abbidot/tracker/widget/FencesRectView3.kt create mode 100644 app/src/main/res/drawable-xhdpi/icon_fence_rect_zoom.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_fence_rect_zoom.png create mode 100644 app/src/main/res/drawable-xxxhdpi/icon_fence_rect_zoom.png diff --git a/app/build.gradle b/app/build.gradle index 7cf685c..c4e780b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,9 +28,9 @@ android { applicationId "com.abbidot.tracker" minSdkVersion 23 targetSdkVersion 35 - versionCode 2108 -// versionName "2.1.8" - versionName "2.1.8-Beta1" + versionCode 2109 +// versionName "2.1.9" + versionName "2.1.9-Beta1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/MapV3Fragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/MapV3Fragment.kt index 056a7f5..d5ac0c4 100644 --- a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/MapV3Fragment.kt +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/MapV3Fragment.kt @@ -43,11 +43,13 @@ import com.abbidot.tracker.ui.activity.map.LiveActivityV3 import com.abbidot.tracker.ui.common.map.HomeMapCommonV3 import com.abbidot.tracker.util.Util import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil import com.abbidot.tracker.util.bluetooth.SRBleUtil import com.abbidot.tracker.vm.CountDownTimerViewModel import com.abbidot.tracker.vm.FencesMapViewModel import com.abbidot.tracker.vm.MapViewModel import com.clj.fastble.BleManager +import com.google.android.gms.maps.model.LatLng import com.hjq.permissions.XXPermissions import com.hjq.permissions.permission.PermissionLists import dagger.hilt.android.AndroidEntryPoint @@ -243,7 +245,6 @@ class MapV3Fragment : BaseFragment(FragmentMapV3Binding::i mCountDownTimerViewModel.mCountDownEndLiveData.observe(this) { mCountDownTimerViewModel.isStartCountDown = true - LogUtil.e("22222222222") mViewBinding.llHomeMapTopPet.homeDataPetNameSmall.text = getHomeV2Activity()?.getPet()?.petName } @@ -291,6 +292,22 @@ class MapV3Fragment : BaseFragment(FragmentMapV3Binding::i if (macID == reportData.mac) updateBleReportData(reportData) } } + //设备获取手机位置 + XEventBus.observe(this, EventName.BleGetLocation) { mac: String -> + getHomeV2Activity()?.getPet(false)?.apply { + if (macID == mac) { + mHomeMapCommon.getUserGoogleLatLng()?.let { latLng: LatLng -> + SRBleUtil.instance.getConnectMacDevice(mac)?.let { ble -> + SRBleUtil.instance.writeData( + ble.bleDevice, SRBleCmdUtil.instance.getPhoneLocation( + latLng.latitude, latLng.longitude + ) + ) + } + } + } + } + } //接收蓝牙连接状态 XEventBus.observe(this, EventName.ConnectDeviceState) { trackBle: BleTrackDeviceBean -> getHomeV2Activity()?.getPet(false)?.apply { diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/FencesAddEditGoogleMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/FencesAddEditGoogleMapFragment.kt index db9ef17..5138766 100644 --- a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/FencesAddEditGoogleMapFragment.kt +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/FencesAddEditGoogleMapFragment.kt @@ -193,6 +193,7 @@ class FencesAddEditGoogleMapFragment : BaseGoogleMapFragment() { LogUtil.e("setScaleLimitDistance,$stepDistance,$limitRadius,$limitSide,${cameraPosition.zoom}") mFencesCircleView.mLimitRadius = limitRadius.toFloat() mFencesRectView.mLimitSide = limitSide.toFloat() + mFencesPolygonView.mLimitSide = limitSide.toFloat() } } diff --git a/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleCmdUtil.kt b/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleCmdUtil.kt index e3ee45d..785117f 100644 --- a/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleCmdUtil.kt +++ b/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleCmdUtil.kt @@ -367,5 +367,12 @@ class SRBleCmdUtil private constructor() { byteArray = byteArray.plus(longTo2ByteArray(duration)) return getCrc8Cmd(byteArray) } + + fun getPhoneLocation(lat: Double, lon: Double): ByteArray { + var byteArray = byteArrayOf(0x23, CMD_READ.toByte(), 0) + byteArray = byteArray.plus(longTo4ByteArray((lon * 1000000).toLong())) + byteArray = byteArray.plus(longTo4ByteArray((lat * 1000000).toLong())) + return getCrc8Cmd(byteArray) + } } diff --git a/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleUtil.kt b/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleUtil.kt index e2ae1c8..9404b90 100644 --- a/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleUtil.kt +++ b/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleUtil.kt @@ -226,6 +226,9 @@ class SRBleUtil private constructor() { openBleReportNotify( bleDevice, notifyServiceUUID, bleNotifyCharacteristicUUID ) + } else if (data0 == 0x23 && data1 == 0x00) { + LogUtil.e("${bleDevice.mac},获取手机定位") + XEventBus.post(EventName.BleGetLocation, bleDevice.mac) } else { val deviceData = ReceiveDeviceData(bleDevice, data, bleDevice.mac) XEventBus.post(EventName.DeviceReceiveData, deviceData) diff --git a/app/src/main/java/com/abbidot/tracker/widget/FencesPolygonView.kt b/app/src/main/java/com/abbidot/tracker/widget/FencesPolygonView.kt index 13fb7b0..0737f85 100644 --- a/app/src/main/java/com/abbidot/tracker/widget/FencesPolygonView.kt +++ b/app/src/main/java/com/abbidot/tracker/widget/FencesPolygonView.kt @@ -60,6 +60,9 @@ class FencesPolygonView : View { private var mOutHideRectWidth = 0f private var mOutHideRectHeight = 0f + //限制边长 + var mLimitSide = 0f + //定位图标 private var mLocationBitmap: Bitmap? = null private var mDotBitmap: Bitmap? = null @@ -334,7 +337,12 @@ class FencesPolygonView : View { if (intersectTFEF) { return false } - + val tempRect = getTempRect(mTouchPointTag, mTempPoint) + if (mLimitSide > 0 && (tempRect.width() < mLimitSide || tempRect.height() < mLimitSide)) { + return false + } + mOutHideRectWidth = tempRect.width() + mOutHideRectHeight = tempRect.height() mAPoint.x = x mAPoint.y = y isInvalidate = true @@ -391,6 +399,12 @@ class FencesPolygonView : View { if (intersectTAFA) { return false } + val tempRect = getTempRect(mTouchPointTag, mTempPoint) + if (mLimitSide > 0 && (tempRect.width() < mLimitSide || tempRect.height() < mLimitSide)) { + return false + } + mOutHideRectWidth = tempRect.width() + mOutHideRectHeight = tempRect.height() mFPoint.x = x mFPoint.y = y isInvalidate = true @@ -446,6 +460,12 @@ class FencesPolygonView : View { if (intersectTDED) { return false } + val tempRect = getTempRect(mTouchPointTag, mTempPoint) + if (mLimitSide > 0 && (tempRect.width() < mLimitSide || tempRect.height() < mLimitSide)) { + return false + } + mOutHideRectWidth = tempRect.width() + mOutHideRectHeight = tempRect.height() mEPoint.x = x mEPoint.y = y isInvalidate = true @@ -501,6 +521,12 @@ class FencesPolygonView : View { if (intersectTEFE) { return false } + val tempRect = getTempRect(mTouchPointTag, mTempPoint) + if (mLimitSide > 0 && (tempRect.width() < mLimitSide || tempRect.height() < mLimitSide)) { + return false + } + mOutHideRectWidth = tempRect.width() + mOutHideRectHeight = tempRect.height() mDPoint.x = x mDPoint.y = y isInvalidate = true @@ -556,6 +582,12 @@ class FencesPolygonView : View { if (intersectTFAF) { return false } + val tempRect = getTempRect(mTouchPointTag, mTempPoint) + if (mLimitSide > 0 && (tempRect.width() < mLimitSide || tempRect.height() < mLimitSide)) { + return false + } + mOutHideRectWidth = tempRect.width() + mOutHideRectHeight = tempRect.height() mCPoint.x = x mCPoint.y = y isInvalidate = true @@ -611,6 +643,12 @@ class FencesPolygonView : View { if (intersectTAAB) { return false } + val tempRect = getTempRect(mTouchPointTag, mTempPoint) + if (mLimitSide > 0 && (tempRect.width() < mLimitSide || tempRect.height() < mLimitSide)) { + return false + } + mOutHideRectWidth = tempRect.width() + mOutHideRectHeight = tempRect.height() mBPoint.x = x mBPoint.y = y isInvalidate = true @@ -657,12 +695,65 @@ class FencesPolygonView : View { return super.onTouchEvent(event) } - private fun getTempRect(touchPointTag: String, tempPoint: Point) { + private fun getTempRect(touchPointTag: String, tempPoint: PointBean): RectF { when (touchPointTag) { - mTageA->{ + mTageA -> { + val listX = + listOf(tempPoint.x, mBPoint.x, mCPoint.x, mDPoint.x, mEPoint.x, mFPoint.x) + val listY = + listOf(tempPoint.y, mBPoint.y, mCPoint.y, mDPoint.y, mEPoint.y, mFPoint.y) + return getTRect(listX, listY) + } + mTageB -> { + val listX = + listOf(mAPoint.x, tempPoint.x, mCPoint.x, mDPoint.x, mEPoint.x, mFPoint.x) + val listY = + listOf(mAPoint.y, tempPoint.y, mCPoint.y, mDPoint.y, mEPoint.y, mFPoint.y) + return getTRect(listX, listY) + } + + mTageC -> { + val listX = + listOf(mAPoint.x, mBPoint.x, tempPoint.x, mDPoint.x, mEPoint.x, mFPoint.x) + val listY = + listOf(mAPoint.y, mBPoint.y, tempPoint.y, mDPoint.y, mEPoint.y, mFPoint.y) + return getTRect(listX, listY) + } + + mTageD -> { + val listX = + listOf(mAPoint.x, mBPoint.x, mCPoint.x, tempPoint.x, mEPoint.x, mFPoint.x) + val listY = + listOf(mAPoint.y, mBPoint.y, mCPoint.y, tempPoint.y, mEPoint.y, mFPoint.y) + return getTRect(listX, listY) + } + + mTageE -> { + val listX = + listOf(mAPoint.x, mBPoint.x, mCPoint.x, mDPoint.x, tempPoint.x, mFPoint.x) + val listY = + listOf(mAPoint.y, mBPoint.y, mCPoint.y, mDPoint.y, tempPoint.y, mFPoint.y) + return getTRect(listX, listY) + } + + mTageF -> { + val listX = + listOf(mAPoint.x, mBPoint.x, mCPoint.x, mDPoint.x, mEPoint.x, tempPoint.x) + val listY = + listOf(mAPoint.y, mBPoint.y, mCPoint.y, mDPoint.y, mEPoint.y, tempPoint.y) + return getTRect(listX, listY) } } + return RectF() + } + + private fun getTRect(listX: List, listY: List): RectF { + val minX = listX.min() + val minY = listY.min() + val maxX = listX.max() + val maxY = listY.max() + return RectF(minX, minY, maxX, maxY) } private fun crossProduct(p1: PointBean, p2: PointBean, p3: PointBean): Float { diff --git a/app/src/main/java/com/abbidot/tracker/widget/FencesRectView3.kt b/app/src/main/java/com/abbidot/tracker/widget/FencesRectView3.kt new file mode 100644 index 0000000..896b7a7 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/FencesRectView3.kt @@ -0,0 +1,818 @@ +package com.abbidot.tracker.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.DashPathEffect +import android.graphics.Paint +import android.graphics.Point +import android.graphics.RectF +import android.text.TextUtils +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ImageUtil +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.widget.FencesPolygonView.Companion.POINT_A +import com.abbidot.tracker.widget.FencesPolygonView.Companion.POINT_B +import com.abbidot.tracker.widget.FencesPolygonView.Companion.POINT_C +import com.abbidot.tracker.widget.FencesPolygonView.Companion.POINT_D +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.sin +import kotlin.math.sqrt + +/** + *Created by .yzq on 2022/1/22/022. + * @link + * @description:添加围栏矩形,只针对围栏 + * + * 按以下点组成的矩形 + * A B + * D C + */ +class FencesRectView3 : View { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + + private lateinit var mStrokePaint: Paint + private lateinit var mFillPaint: Paint + + private lateinit var mRectF: RectF + + //矩形的宽高 + private var mRectWidth = 0f + private var mRectHeight = 0f + private var mRectLeft = 0f + private var mRectRight = 0f + private var mRectTop = 0f + private var mRectBottom = 0f + + //中心坐标点 + private var mCentreX = 0f + private var mCentreY = 0f + + //定位图标 + private var mLocationBitmap: Bitmap? = null + private var mDotBitmap: Bitmap? = null + private var mZoomBitmap1: Bitmap? = null + private var mZoomBitmap2: Bitmap? = null + private var mRotateBitmap: Bitmap? = null + + private var mDashedColor = 0 + private var mFillBgColor = 0 + + private var isRotate = false + private var isScale = false + private var isDrag = false + private var mOldX = 0f + private var mOldY = 0f + + //是否显示距离 + private var isShowDistance = true + private var mRectWidthDistanceText = "" + private var mRectHeightDistanceText = "" + private lateinit var mTextPaint: Paint + + //旋转的角度(真实的角度单位) + private var mRotateAngle = 0.0 + + //计算画布移动后,点击旋转图片按钮一开始的坐标角度,经过平移坐标轴算出 + private var mStartRotateAngle = 0.0 + + //是否需要测量,防止百度地图使用会执行onMeasure/地图页面上下滑动重新布局执行onMeasure + private var isNeedMeasure = true + + //限制边长 + var mLimitSide = 0f + + private var mRotateScaleClickListener: OnRectViewRotateScaleClickListener? = null + + constructor(context: Context) : super(context) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context) + } + + private fun init(context: Context) { + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + mContext = context +// mRectWidth = QMUIDisplayHelper.dpToPx(100).toFloat() +// mRectHeight = QMUIDisplayHelper.dpToPx(100).toFloat() + + mStrokePaint = Paint() + LogUtil.e("${javaClass.canonicalName}------------------->>>init") + mStrokePaint.style = Paint.Style.STROKE + mStrokePaint.strokeWidth = QMUIDisplayHelper.dpToPx(1).toFloat() + //虚线长度 + val dashPathWidth = QMUIDisplayHelper.dpToPx(6).toFloat() + mStrokePaint.pathEffect = DashPathEffect(floatArrayOf(dashPathWidth, dashPathWidth), 0f) + mStrokePaint.color = ContextCompat.getColor(mContext, R.color.select_color) + + mFillPaint = Paint() + mFillPaint.style = Paint.Style.FILL + mFillPaint.color = ContextCompat.getColor(mContext, R.color.select_color4) + + mTextPaint = Paint() + val tf = + ViewUtil.instance.setTypeface(context, context.getString(R.string.number_din_cond_font)) + mTextPaint.typeface = tf + mTextPaint.textSize = QMUIDisplayHelper.sp2px(context, 14).toFloat() + mTextPaint.color = ContextCompat.getColor(mContext, R.color.black) + + mDotBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fence_dot_svg) + mLocationBitmap = + ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fences_zone_gps_svg) + mZoomBitmap1 = + ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fence_rect_zoom_svg) + mZoomBitmap2 = + ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fence_rect_zoom) + mRotateBitmap = + ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fence_rect_rotate_svg) + + mRectF = RectF() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mWidth = measuredWidth + mHeight = measuredHeight + LogUtil.e("${javaClass.canonicalName}:onMeasure---mWidth=$isNeedMeasure,mHeight=$mHeight") + if (isNeedMeasure) { + mCentreX = mWidth / 2f + mCentreY = mHeight / 2f + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + LogUtil.e("onAttachedToWindow,${javaClass.canonicalName}视图显示") + } + + override fun onDraw(canvas: Canvas) { + LogUtil.e("${javaClass.canonicalName}------>onDraw") + super.onDraw(canvas) + + canvas.apply { + save() + //只为旋转才移动坐标轴 + translate(mCentreX, mCentreY) + rotate(mRotateAngle.toFloat()) + startDraw(this) + restore() + + } + } + + private fun startDraw(canvas: Canvas) { + +// if (mRectWidth == 0f && mRectHeight == 0f) return + + + mRectF.apply { + left = mRectLeft + top = mRectTop + right = mRectRight + bottom = mRectBottom + } + + mLocationBitmap?.apply { + canvas.drawBitmap( + this, mRectF.centerX() - width / 2, mRectF.centerY() - height / 2, null + ) + } + + canvas.drawRect(mRectF, mFillPaint) + canvas.drawRect(mRectF, mStrokePaint) + +// mDotBitmap?.apply { +// val dotImageHalfWidth = width / 2f +// val dotImageHalfHeight = height / 2f +// canvas.drawBitmap( +// this, mRectLeft - dotImageHalfWidth, mRectTop - dotImageHalfHeight, null +// ) +// canvas.drawBitmap( +// this, mRectRight - dotImageHalfWidth, mRectTop - dotImageHalfHeight, null +// ) +// canvas.drawBitmap( +// this, mRectLeft - dotImageHalfWidth, mRectBottom - dotImageHalfHeight, null +// ) +// if (!isShowDistance) { +// canvas.drawBitmap( +// this, mRectRight - dotImageHalfWidth, mRectBottom - dotImageHalfHeight, null +// ) +// } +// } + + + if (isShowDistance) { + + mRotateBitmap?.apply { + val startX = mRectLeft + mRectWidth / 2 + canvas.drawLine( + startX, + mRectTop, + startX, + mRectTop - QMUIDisplayHelper.dpToPx(3).toFloat(), + mStrokePaint + ) + canvas.drawBitmap( + this, + startX - width / 2, + mRectTop - height - QMUIDisplayHelper.dpToPx(2).toFloat(), + null + ) + mStartRotateAngle = atan2(mRectTop - height, startX - width / 2) / Math.PI * 180 + } + mZoomBitmap1?.apply { + canvas.drawBitmap( + this, mRectRight - width / 2, mRectBottom - height / 2, null + ) + canvas.drawBitmap( + this, mRectLeft - width / 2, mRectTop - height / 2, null + ) + } + mZoomBitmap2?.apply { + canvas.drawBitmap( + this, mRectRight - width / 2, mRectTop - height / 2, null + ) + canvas.drawBitmap( + this, mRectLeft - width / 2, mRectBottom - height / 2, null + ) + } + + canvasDistanceText(canvas) + } + } + + private fun canvasDistanceText(canvas: Canvas) { + val fontMetrics = mTextPaint.fontMetrics + if (!TextUtils.isEmpty(mRectWidthDistanceText)) { + //文字宽度 + val textWidth = mTextPaint.measureText(mRectWidthDistanceText) + //文字高度 + val textHeight = fontMetrics.bottom - fontMetrics.top + + canvas.drawText( + mRectWidthDistanceText, + mRectF.centerX() - textWidth / 2, + mRectBottom + textHeight, + mTextPaint + ) + } + + if (!TextUtils.isEmpty(mRectHeightDistanceText)) { + //文字宽度 + val textWidth = mTextPaint.measureText(mRectHeightDistanceText) + //文字高度 + val textHeight = fontMetrics.bottom - fontMetrics.top + + canvas.drawText( + mRectHeightDistanceText, + mRectRight + textWidth / 10, + mRectF.centerY() + textHeight / 2, + mTextPaint + ) + } + } + + fun setOnRectViewRotateScaleClickListener(onRectViewRotateScaleClickListener: OnRectViewRotateScaleClickListener) { + mRotateScaleClickListener = onRectViewRotateScaleClickListener + } + + /** + * 设置默认长宽大小,初始化使用 + */ + fun initDefaultWidthHeight(width: Int = 150, height: Int = 150) { + if (width <= 0 && height <= 0) return + mRectWidth = QMUIDisplayHelper.dpToPx(width).toFloat() + mRectHeight = QMUIDisplayHelper.dpToPx(height).toFloat() + mRotateAngle = 0.0 + postInvalidate() + } + + /** + * 设置显示的距离 + */ + fun setShowDisDistance(widthDistance: String, heightDistance: String) { + mRectWidthDistanceText = widthDistance + mRectHeightDistanceText = heightDistance + postInvalidate() + } + + /** + * 设置隐藏显示距离文字 + */ + fun setShowHideDisDistance(show: Boolean) { + if (isShowDistance != show) { + isShowDistance = show + postInvalidate() + } + } + + /** + * 设置边框颜色和图片 + */ + fun setColorAndImage( + context: Context, + dashedColorRes: Int = 0, + fillBgColorRes: Int = 0, + locationDrawableRes: Int = 0, + zoomDrawableRes: Int = 0, + dotDrawableRes: Int = 0, + rotateDrawableRes: Int = 0, + disTextColorRes: Int = 0 + ) { + var isRefresh = false + if (dashedColorRes > 0) { + mDashedColor = ContextCompat.getColor(context, dashedColorRes) + mStrokePaint.color = mDashedColor + isRefresh = true + } + if (fillBgColorRes > 0) { + mFillBgColor = ContextCompat.getColor(context, fillBgColorRes) + mFillPaint.color = mFillBgColor + isRefresh = true + } + + if (locationDrawableRes > 0) { + mLocationBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, locationDrawableRes) + isRefresh = true + } + if (zoomDrawableRes > 0) { + mZoomBitmap1 = ImageUtil.getBitmapFromDrawableAndSvg(context, zoomDrawableRes) + isRefresh = true + } + if (rotateDrawableRes > 0) { + mRotateBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, rotateDrawableRes) + isRefresh = true + } + if (dotDrawableRes > 0) { + mDotBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, dotDrawableRes) + isRefresh = true + } + if (disTextColorRes > 0) { + mTextPaint.color = ContextCompat.getColor(context, disTextColorRes) + isRefresh = true + } + if (isRefresh) invalidate() + } + + private fun getCentreX() = mCentreX + private fun getCentreY() = mCentreY + + /** + * 获取中心点 + */ + fun getRectCentrePoint() = Point(mCentreX.toInt(), mCentreY.toInt()) + + /** + * 获取矩形4个点的某一个点 + */ + fun getRectABCDPoint(wherePoint: Int): Point { + val points = getRectABCDXYPoint(wherePoint) + return Point(points[0].toInt(), points[1].toInt()) + } + + /** + * 返回矩形第哪个点(A,B,C,D)的坐标 + * A **** B + * D **** C + */ + private fun getRectABCDXYPoint(wherePoint: Int): FloatArray { + when (wherePoint) { + POINT_A -> { + val leftTop = getRotateXY(mRectLeft, mRectTop, mRotateAngle) + return floatArrayOf( + leftTop[0] + mCentreX, leftTop[1] + mCentreY + ) + } + + POINT_B -> { + val rightTop = getRotateXY(mRectRight, mRectTop, mRotateAngle) + return floatArrayOf( + rightTop[0] + mCentreX, rightTop[1] + mCentreY + ) + } + + POINT_D -> { + val leftBottom = getRotateXY(mRectLeft, mRectBottom, mRotateAngle) + return floatArrayOf( + leftBottom[0] + mCentreX, leftBottom[1] + mCentreY + ) + } + + POINT_C -> { + val rightBottom = getRotateXY(mRectRight, mRectBottom, mRotateAngle) + return floatArrayOf( + rightBottom[0] + mCentreX, rightBottom[1] + mCentreY + ) + } + } + return floatArrayOf( + 0f, 0f + ) + } + + private var mClickTag = 0 + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent?): Boolean { + + when (event?.action) { + MotionEvent.ACTION_DOWN -> { + val x = event.x + val y = event.y + LogUtil.e("action_down,x=$x,y=$y") + mRotateBitmap?.apply { + LogUtil.e("x2=${width / 2f + mCentreX},x1=${-width / 2f + mCentreX}") + LogUtil.e("y2=${-(mRectHeight / 2 + height) + mCentreY},aaa=${-mRectHeight / 2 + mCentreY}") + + if (isClickRotate(x, y, mRotateAngle)) { + LogUtil.e("点击了旋转") + isRotate = true + return true + } + } + //是否按下了放大缩小按钮 + mZoomBitmap1?.apply { +// if (!isRotate && isClickScale(x, y, mRotateAngle)) { + if (!isRotate) { + if (x > mRectF.left - width / 2 && x < mRectF.left + width / 2 && y > mRectF.top - height / 2 && y < mRectF.top + height / 2) { + isScale = true + mClickTag = 1 + LogUtil.e("点击了放大缩小") + mOldX = x + mOldY = y + return true + } else if (isClickScale(x, y, mRotateAngle)) { + isScale = true + mClickTag = 3 + LogUtil.e("点击了放大缩小") + mOldX = x + mOldY = y + return true + } + } + } + mZoomBitmap2?.apply { + if (!isRotate) { + if (x > mRectF.right - width / 2 && x < mRectF.right + width / 2 && y > mRectF.top - height / 2 && y < mRectF.top + height / 2) { + isScale = true + mClickTag = 2 + LogUtil.e("点击了放大缩小") + mOldX = x + mOldY = y + return true + } else if (x > mRectF.left - width / 2 && x < mRectF.left + width / 2 && y > mRectF.bottom - height / 2 && y < mRectF.bottom + height / 2) { + isScale = true + mClickTag = 4 + LogUtil.e("点击了放大缩小") + mOldX = x + mOldY = y + return true + } + } + } + + //因为有旋转角度,需要减去中心 + if (mRectF.contains(x, y)) { + isDrag = true + mOldX = x + mOldY = y + mStrokePaint.strokeWidth = QMUIDisplayHelper.dpToPx(3).toFloat() + mRotateScaleClickListener?.rectStartDragClick() + invalidate() + return true + } + } + + MotionEvent.ACTION_MOVE -> { + val x = event.x + val y = event.y + LogUtil.e("action_move,x=$x,y=$y,oldX=$mOldX,oldY=$mOldY") + if (isRotate) { + //计算手指按下画布移动后的x,y角度 + val transformAngle = atan2(y - mCentreY, x - mCentreX) / Math.PI * 180 + //计算需要旋转的角度 + mRotateAngle = transformAngle - mStartRotateAngle + LogUtil.e("mRotateAngle=$mRotateAngle") + invalidate() + return true + } else if (isScale) { + LogUtil.e("mRectWidth=$mRectWidth,mRectHeight=$mRectHeight,x - oldX=${x - mOldX},y - oldY=${y - mOldY}") +// val transformX = x - mOldX +// val transformY = y - mOldY +// val newRectWidth = mRectWidth + transformX +// val newRectHeight = mRectHeight + transformY +// //限制缩放不能太小 +// if (mLimitSide == 0f) { +// if (newRectWidth < mLocationBitmap!!.height || newRectHeight < mLocationBitmap!!.height) { +// return true +// } +// } else if (newRectWidth < mLimitSide || newRectHeight < mLimitSide) { +// return true +// } +// mRotateScaleClickListener?.rectScalingClick() +// mRectWidth = newRectWidth +// mRectHeight = newRectHeight +// mOldX = x +// mOldY = y + when (mClickTag) { + 1 -> { + mRectLeft = x + mRectTop = y + } + + 2 -> { + mRectRight = x + mRectTop = y + } + + 3 -> { + mRectRight = x + mRectBottom = y + } + + 4 -> { + mRectLeft = x + mRectBottom = y + } + } + invalidate() + return true + } else if (isDrag) { + val transformX = x - mOldX + val transformY = y - mOldY + mRectLeft += transformX + mRectTop += transformY + mRectRight += transformX + mRectBottom += transformY + mOldX = x + mOldY = y + invalidate() + return true + } + } + + MotionEvent.ACTION_UP -> { + if (isRotate) { + mRotateScaleClickListener?.rectRotateClick() + isRotate = false + } + if (isScale) { + mRotateScaleClickListener?.rectScaleEndClick() + isScale = false + } + if (isDrag) { + mRotateScaleClickListener?.rectEndDragClick() + isDrag = false + mStrokePaint.strokeWidth = QMUIDisplayHelper.dpToPx(1).toFloat() + invalidate() + } + } + } + return super.onTouchEvent(event) + } + + /** + * @param x 是坐标轴移动以(mCentreX,mCentreY)原点的坐标 + * @param y + * 获取以mCentreX、mCentreY为原点的坐标轴进行旋转后的XY坐标 + */ + private fun getRotateXY(x: Float, y: Float, rotate: Double): FloatArray { + //Math.toRadians(double angdeg) 角度转化为弧度 + //Math.toDegrees(Math.PI/2);弧度转化为角度 (π/2的角度值) + val curAngle = atan2(y, x) / Math.PI * 180 + val sumAngle = curAngle + rotate + //将角度化为弧度 +// val angle = Math.PI / 180 * sumAngle + val angle = Math.toRadians(sumAngle) + //初始坐标与中点形成的直线长度不管怎么旋转都是不会变的,用勾股定理求出然后将其作为斜边 + val c = sqrt(x.pow(2) + y.pow(2)) + //斜边乘sin值等于即可求出y坐标 + val a = sin(angle) * c + //斜边乘cos值等于即可求出x坐标 + val b = cos(angle) * c + //目前的xy坐标是相对于图片中点为原点的坐标轴 + return floatArrayOf((b).toFloat(), (a).toFloat()) + } + + /** + * 是否点击按下了旋转,按照移动了坐标轴原点(mCentreX,mCentreY)计算初始坐标 + */ + private fun isClickRotate(clickX: Float, clickY: Float, rotate: Double): Boolean { + var x1 = -(mRectWidth / 2 - mRotateBitmap!!.width / 2) + var x2 = (mRectWidth / 2 - mRotateBitmap!!.width / 2) + var y1 = -(mRectHeight / 2 + mRotateBitmap!!.height + mRotateBitmap!!.width) + var y2 = -(mRectHeight / 2 - mRotateBitmap!!.height) + val minX = getRotateXY(x1, y1, rotate) + val maxX = getRotateXY(x2, y2, rotate) + x1 = minX[0] + x2 = maxX[0] + if (minX[0] > maxX[0]) { + x1 = maxX[0] + x2 = minX[0] + } + y1 = minX[1] + y2 = maxX[1] + if (minX[1] > maxX[1]) { + y1 = maxX[1] + y2 = minX[1] + } + + //处理旋转到某个角度时,旋转图片按钮会超出坐标范围内,不在可点击坐标范围内,把可点击的坐标范围扩大 + if (abs(x2 - x1) < mRotateBitmap!!.height) { + if (x2 > 0) { + x2 += mRotateBitmap!!.height + } else { + x1 -= mRotateBitmap!!.height + } + } + if (abs(y2 - y1) < mRotateBitmap!!.height) { + if (y2 > 0) { + y2 += mRotateBitmap!!.height + } else { + y1 -= mRotateBitmap!!.height + } + } + + val transformX = clickX - mCentreX + val transformY = clickY - mCentreY + + if (transformX in x1..x2 && transformY in y1..y2) { + return true + } + return false + } + + /** + * 是否点击按下了放大缩小,按照移动了坐标轴原点(mCentreX,mCentreY)计算初始坐标 + */ + private fun isClickScale(clickX: Float, clickY: Float, rotate: Double): Boolean { + var x1 = (mRectWidth / 2 - mZoomBitmap1!!.width) + var x2 = (mRectWidth / 2 + mZoomBitmap1!!.width) + var y1 = (mRectHeight / 2 - mRotateBitmap!!.height) + var y2 = (mRectHeight / 2 + mRotateBitmap!!.height) + val minX = getRotateXY(x1, y1, rotate) + val maxX = getRotateXY(x2, y2, rotate) + x1 = minX[0] + x2 = maxX[0] + if (minX[0] > maxX[0]) { + x1 = maxX[0] + x2 = minX[0] + } + y1 = minX[1] + y2 = maxX[1] + if (minX[1] > maxX[1]) { + y1 = maxX[1] + y2 = minX[1] + } + + val transformX = clickX - mCentreX + val transformY = clickY - mCentreY + + if (transformX in x1..x2 && transformY in y1..y2) { + return true + } + return false + } + + /** + * 设置显示编辑的矩形(利用假设方式) + * 错误方式 + fun setRectABCDXYPoint(left: Float, top: Float, right: Float, bottom: Float) { + //假设A没有旋转的,原点在矩形中心,正常与x轴的角度135℃ + val noRotateAngle = 135.0 + val rotateAX = left - mCentreX + val rotateAY = top - mCentreY + //计算现在编辑后的矩形A点的角度 + val curAngle = atan2(rotateAY, rotateAX) / Math.PI * 180 + //计算旋转了多少度 + mRotateAngle = noRotateAngle - curAngle + //初始坐标与矩形中点形成的直线长度不管怎么旋转都是不会变的,用勾股定理求出然后将其作为斜边 + val c = sqrt(rotateAX.pow(2) + rotateAY.pow(2)) + //将角度化为弧度,计算旋转后的与x轴的角度,然后算出长和宽 + val angle = Math.toRadians(curAngle) + //斜边乘sin值等于即可求出y坐标 + val a = sin(angle) * c + //斜边乘cos值等于即可求出x坐标 + val b = cos(angle) * c + mRectLeft = (-abs(b)).toFloat() + mRectTop = (-abs(a)).toFloat() + + // mRectWidth = abs(b * 2).toFloat() + mRectWidth = abs(right) - abs(left) + // mRectHeight = abs(a * 2).toFloat() + mRectHeight = abs(bottom) - abs(top) + + mRectRight = mRectWidth / 2 + mRectBottom = mRectHeight / 2 + + isEdit = true + + LogUtil.e("编辑的坐标($mRectLeft,$mRectTop),($mRectRight,$mRectBottom),($mCentreX,$mCentreY)") + postInvalidate() + } */ + + fun setRectABCDXYPoint( + pointCentre: Point, pointA: Point, pointB: Point, pointC: Point, pointD: Point + ) { + mCentreX = pointCentre.x.toFloat() + mCentreY = pointCentre.y.toFloat() + //利用屏幕的坐标系根据勾股定理算出矩形的边长,A点和D点与坐标轴组成一个三角形 + mRectHeight = sqrt( + (abs(pointA.x - pointD.x).toFloat()).pow(2) + (abs(pointD.y - pointA.y).toFloat()).pow(2) + ) + //A点和B点与坐标轴组成一个三角形 + mRectWidth = sqrt( + (abs(pointB.x - pointA.x).toFloat()).pow(2) + (abs(pointB.y - pointA.y).toFloat()).pow(2) + ) + + val rotateAX = pointA.x - mCentreX + val rotateAY = pointA.y - mCentreY + //计算现在编辑后的矩形A点与x轴形成的角度 + val curAngle = atan2(rotateAY, rotateAX) / Math.PI * 180 + + //初始坐标与矩形中点形成的直线长度不管怎么旋转都是不会变的,用勾股定理求出然后将其作为斜边 +// val c = sqrt(rotateAX.pow(2) + rotateAY.pow(2)) + //将角度化为弧度,计算旋转后的与x轴的角度,然后算出长和宽 +// val angle = Math.toRadians(curAngle) + //斜边乘sin值等于即可求出y坐标 +// val a = sin(angle) * c + //斜边乘cos值等于即可求出x坐标 +// val b = cos(angle) * c + + //根据矩形长宽算出没有旋转的矩形坐标 + val rectLeft = -mRectWidth / 2 + val rectTop = -mRectHeight / 2 + val rectRight = rectLeft + mRectWidth + val rectBottom = rectTop + mRectHeight + //计算A没有旋转的,原点在矩形中心,正常与x轴的角度 + val noRotateAngle = atan2(rectTop, rectLeft) / Math.PI * 180 + //计算旋转了多少度 + mRotateAngle = curAngle - noRotateAngle + LogUtil.e("没有旋转时初始角度=$noRotateAngle,当前旋转后的角度curAngle=$curAngle,旋转了多少度mRotateAngle=$mRotateAngle") + LogUtil.e("编辑的坐标($rectLeft,$rectTop),($rectRight,$rectBottom),($mCentreX,$mCentreY)") + postInvalidate() + } + + fun setCentreXY(point: Point) { + point.apply { + if (mCentreX != x.toFloat() || mCentreY != y.toFloat()) { + mCentreX = x.toFloat() + mCentreY = y.toFloat() + isNeedMeasure = false + mRectLeft = - mRectWidth / 2 + mRectTop = - mRectHeight / 2 + mRectRight = mRectWidth / 2 + mRectBottom = mRectHeight / 2 + invalidate() + } + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + LogUtil.e("onDetachedFromWindow,${javaClass.canonicalName}视图销毁") + if (null != mDotBitmap) { + mDotBitmap?.recycle() + mDotBitmap = null + } + if (null != mLocationBitmap) { + mLocationBitmap?.recycle() + mLocationBitmap = null + } + if (null != mRotateBitmap) { + mRotateBitmap?.recycle() + mRotateBitmap = null + } + if (null != mZoomBitmap1) { + mZoomBitmap1?.recycle() + mZoomBitmap1 = null + } + if (null != mZoomBitmap2) { + mZoomBitmap2?.recycle() + mZoomBitmap2 = null + } + } +} + diff --git a/app/src/main/res/drawable-xhdpi/icon_fence_rect_zoom.png b/app/src/main/res/drawable-xhdpi/icon_fence_rect_zoom.png new file mode 100644 index 0000000000000000000000000000000000000000..0d1996df91e378a170592ab6673fbe857d1fbcc6 GIT binary patch literal 531 zcmV+u0_^>XP)Tv z=$d>2zB2<21$J_K^Q%cr)z$RYl;%V0dhMGr1nuBJ zCb|pigKYBK7?(+7LTqLMl3*tjh1iZUt(9q7eja4u>!=KLQlEygFp6WOZpM9JRZ9?t z-(Eqe)_$^8;5$5yO>VhgyA#``c--CBu~CFb>lV(bv7BeLWV3xay!>={`dIk-{12HM V8J9IcjTry{002ovPDHLkV1jZ~748sGnkH-adjg0_3HGn=b++I->X zY(n_g*L8g>|2og}U9R&yj?Z{kWf1HA-e-*{$lGGgF@RcDcr-03rFh?Wj4>sqa0%5g z>Aqk5im|d5tRha5kOGWkmrE+48e=kztg=bu*9XuLk9-A@#67>*!AMTATRFnceq9802pKi>?vJU zdYA+%2hh_~gIcwUXMs3rl4B4&rd;f4cW;vIX}EzdZWEZq1Lt{&H8tS?cmmf3AhY71 zK1np*sI)dOWV{i*IY?>qlnIUiDQ&JYDlIU8TcqNa`;c87m`D-s6U{uMTTZ&5!ogfN zK%TFOgCG%I>1_v8IWLeVXPrn%8`L<^_GM)QnsWQJw>uK|-jS!=pY z>(>@72%J|Cedio(?Z38+hAk2Xb#fqCUi{59JBwW=IY$UAId2W1%8;v?c6vFkEgiG$ zw*hp3oT6xPGmmT8zIqfLqU!$V9z`4J$xb!qD`oY_E{^NZNj;y}pL2U_bqnSc=Q9-V zmH7GGdFOIWQCk?WYsA{8pv2?guYEqHv|RoEMby%}lt25YA4mQ;wQc2De*gdg07*qo IM6N<$f>QchMF0Q* literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/icon_fence_rect_zoom.png b/app/src/main/res/drawable-xxxhdpi/icon_fence_rect_zoom.png new file mode 100644 index 0000000000000000000000000000000000000000..38ad2a309a90557c884889c5000acf353b3d0f55 GIT binary patch literal 1041 zcmV+s1n&EZP)1Z|(TyzDt$ona;c z0ys^-_HzIoSe9j;*L8ir*L6;9Sw3mal7X8xe~4qTz>#g9pX#_m2a-n|efX2&wu%dH zWI*mw%o5}{wr$_{*K6Om)Q+bVivTzhux{HX9vM`@yKkWbo8TB?VE9|xcm4~LF~C64 zJ+9$T8P%yZm`-HBZ;s$4iqP05dyL51f`}ntQ|*7i9ippQ+t*MW?F6?;+VWm64kf;W z_!R3-q)Pz8_LGtlh|4e({Nes*p1sWBzDtkr$t6F~*o9mD=VQ*3fC7f{Q)Uxps}}|o z(4QUrClJDxfC6qC!~$*SDzHDof4$HoE6ymOZ_s|Ec6KJMa{6)1RYOA9Ai0n=id_j| z0lEOwBH;h6glGiHcqJa?5-QY*6c$jzNCdV79cCbiq);P44@`)H_9*Jmne4efqF8GJ z?1A(M*Rg(%mM3^F>9?S>(xbRTSP}7D(gc=ABMymDCBJA7HW7qDhl)S=w=uT$2neF{ zbj5!GQbPb9@gNL^fbcP2K1@LPxC*zz;Ss*<3@BjuUm@W5E&*7_(+J9~Krtlnad~Oy zm-_??|CC_lUQ6Jln^%CcKHI7RkIhCG1Pnp%#8yRRsFoJ)FU zm=1X!sl7m~@BlNKjUZ;E@<5~!47y%(%J&HmDePjOczW<9#O+QN^GIiEXT#L;;S0uA z86`9xY<0(XL{7k*2&7TxNa%Yap70`&hqWhRgu*u{Ghir30PVd8uP6A7^!3A#A|X6& zdUca~B2`WtZ!!c9)gBz#QQh&aL%YC{9n00wmfRWu#vz4@$nXNG_O-o(*)pJGGjr82 zaI`$p;dnTi==dUVI9?~EbDfN|oz}hn(Kt9r7Zd>sf{$KMB!+Kau?U9u=E`8^4Bywu zTQrM9pzG6{Zn8?7&Z;k8&%s4qx~aoeuCCRg>}golU0_pT)E@?-t4Kl=Ycx46-{vmG zBlwf@isLyts9G!?>!;9>BYiWFzZ%FN4WzFI(l-Q=O#D6bKOFc2b5&#mH+| + tools:context=".deprecated.ui.activity.profile.FencesAddActivity">