1.优化订阅列表显示(Renew at $29.97 per month thereafter)

2.历史轨迹地图增加罗盘和当前手机位置显示
3.历史轨迹点模式增加点击可显示宠物信息
4.修复雷达停止退出还会有声音的bug
This commit is contained in:
yezhiqiu
2026-03-05 14:43:08 +08:00
parent 1aa6f7a247
commit 8b128e58cb
15 changed files with 846 additions and 71 deletions

View File

@@ -28,9 +28,9 @@ android {
applicationId "com.abbidot.tracker" applicationId "com.abbidot.tracker"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 35 targetSdkVersion 35
versionCode 2109 versionCode 2110
// versionName "2.1.9" // versionName "2.1.10"
versionName "2.1.9-Beta1" versionName "2.1.10-Beta1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -47,19 +47,34 @@ class MySubscriptionAdapter(
holder.setText(R.id.tv_my_subscription_plan_name, item.mealName) holder.setText(R.id.tv_my_subscription_plan_name, item.mealName)
holder.getTextView(R.id.tv_my_subscription_auto_subscription_tips).apply { holder.getTextView(R.id.tv_my_subscription_auto_subscription_tips).apply {
visibility = if (item.subscriptionStatus == ConstantInt.Open) { visibility = if (item.subscriptionStatus == ConstantInt.Open) {
text = if (item.mealUnit == ConstantString.PackageUnitYear) String.format( text = when (item.mealUnit) {
mContext.getString(R.string.txt_auto_subscription_year), ConstantString.PackageUnitYear -> if (item.mealPeriod > 1) String.format(
item.autoRenewPrice.toString() mContext.getString(R.string.txt_auto_subscription_years),
)
else if (item.mealUnit == ConstantString.PackageUnitDay) String.format(
mContext.getString(R.string.txt_auto_subscription_day),
"${item.autoRenewPrice}", "${item.autoRenewPrice}",
"${item.mealPeriod}" "${item.mealPeriod}"
) else String.format(
mContext.getString(R.string.txt_auto_subscription_year),
"${item.autoRenewPrice}"
) )
else String.format(
ConstantString.PackageUnitDay -> if (item.mealPeriod > 1) String.format(
mContext.getString(R.string.txt_auto_subscription_days),
"${item.autoRenewPrice}",
"${item.mealPeriod}"
) else String.format(
mContext.getString(R.string.txt_auto_subscription_day),
"${item.autoRenewPrice}"
)
else -> if (item.mealPeriod > 1) String.format(
mContext.getString(R.string.txt_auto_subscription_months),
"${item.autoRenewPrice}",
"${item.mealPeriod}"
) else String.format(
mContext.getString(R.string.txt_auto_subscription_month), mContext.getString(R.string.txt_auto_subscription_month),
item.autoRenewPrice.toString() "${item.autoRenewPrice}"
) )
}
View.VISIBLE View.VISIBLE
} else View.GONE } else View.GONE
} }

View File

@@ -53,7 +53,7 @@ class HistoryDataActivity :
mHistoryDataMapCommon = HistoryDataMapCommon() mHistoryDataMapCommon = HistoryDataMapCommon()
val fragment = mHistoryDataMapCommon.getMapFragment( val fragment = mHistoryDataMapCommon.getMapFragment(
this, MapMarkerInfoView(mContext), mGeoCoderViewModel this, MapMarkerInfoView(mContext), mGeoCoderViewModel,VerticalTopToBottomSeekBar(mContext)
) {} ) {}
supportFragmentManager.commit { supportFragmentManager.commit {
add(R.id.history_data_map_view, fragment) add(R.id.history_data_map_view, fragment)

View File

@@ -1088,6 +1088,7 @@ class LiveActivityV3 : BaseActivity<ActivityLiveV3Binding>(ActivityLiveV3Binding
mFindBleDeviceViewModel.stopPlay() mFindBleDeviceViewModel.stopPlay()
showAndHideFindLayout() showAndHideFindLayout()
} else { } else {
mFindBleDeviceViewModel.stopPlay()
mFindBleDeviceViewModel.stopFindDevice() mFindBleDeviceViewModel.stopFindDevice()
finishActivity() finishActivity()
} }

View File

@@ -10,6 +10,7 @@ import com.abbidot.tracker.ui.fragment.map.baidumap.HistoryDataBaiduMapFragment
import com.abbidot.tracker.ui.fragment.map.googlemap.HistoryDataGoogleMapFragment import com.abbidot.tracker.ui.fragment.map.googlemap.HistoryDataGoogleMapFragment
import com.abbidot.tracker.vm.GeoCoderViewModel import com.abbidot.tracker.vm.GeoCoderViewModel
import com.abbidot.tracker.widget.MapMarkerInfoView import com.abbidot.tracker.widget.MapMarkerInfoView
import com.abbidot.tracker.widget.VerticalTopToBottomSeekBar
/** /**
*Created by .yzq on 2022/7/5/005. *Created by .yzq on 2022/7/5/005.
@@ -25,6 +26,7 @@ class HistoryDataMapCommon : BaseMapCommon() {
context: Context, context: Context,
markerInfoView: MapMarkerInfoView, markerInfoView: MapMarkerInfoView,
geoCoderViewModel: GeoCoderViewModel, geoCoderViewModel: GeoCoderViewModel,
verticalTopToBottomSeekBar: VerticalTopToBottomSeekBar,
mapLoadOk: () -> Unit mapLoadOk: () -> Unit
): Fragment { ): Fragment {
return if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { return if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) {
@@ -33,7 +35,7 @@ class HistoryDataMapCommon : BaseMapCommon() {
mHistoryDataBaiduMapFragment!! mHistoryDataBaiduMapFragment!!
} else { } else {
mHistoryDataGoogleMapFragment = HistoryDataGoogleMapFragment.newInstance( mHistoryDataGoogleMapFragment = HistoryDataGoogleMapFragment.newInstance(
context, markerInfoView, geoCoderViewModel, mapLoadOk context, markerInfoView, geoCoderViewModel, verticalTopToBottomSeekBar, mapLoadOk
) )
mHistoryDataGoogleMapFragment!! mHistoryDataGoogleMapFragment!!
} }

View File

@@ -85,7 +85,7 @@ class RouteV2Fragment : BaseFragment<FragmentRouteV2Binding>(FragmentRouteV2Bind
root, WindowInsetsCompat.Type.statusBars() root, WindowInsetsCompat.Type.statusBars()
) )
mFragment = mHistoryDataMapCommon.getMapFragment( mFragment = mHistoryDataMapCommon.getMapFragment(
mContext!!, miHomeRouteAddressView, mGeoCoderViewModel mContext!!, miHomeRouteAddressView, mGeoCoderViewModel, vsbMapRouteLine
) { ) {
mapLoadOk() mapLoadOk()
} }
@@ -188,7 +188,7 @@ class RouteV2Fragment : BaseFragment<FragmentRouteV2Binding>(FragmentRouteV2Bind
when (checkedId) { when (checkedId) {
mViewBinding.rbHistoryRouteMapLine.id -> { mViewBinding.rbHistoryRouteMapLine.id -> {
MMKVUtil.putInt(MMKVKey.LineSelectPosition, ConstantInt.Line) MMKVUtil.putInt(MMKVKey.LineSelectPosition, ConstantInt.Line)
mHistoryDataMapCommon.addSetLine(false) mHistoryDataMapCommon.addSetLine()
if (mHistoryDataList.size > 0) { if (mHistoryDataList.size > 0) {
// mHistoryDataMapCommon.slideStopChanged(mHistoryDataList.size - 1) // mHistoryDataMapCommon.slideStopChanged(mHistoryDataList.size - 1)
} else { } else {
@@ -198,7 +198,7 @@ class RouteV2Fragment : BaseFragment<FragmentRouteV2Binding>(FragmentRouteV2Bind
mViewBinding.rbHistoryRouteMapNumber.id -> { mViewBinding.rbHistoryRouteMapNumber.id -> {
MMKVUtil.putInt(MMKVKey.LineSelectPosition, ConstantInt.Point) MMKVUtil.putInt(MMKVKey.LineSelectPosition, ConstantInt.Point)
mHistoryDataMapCommon.addSetNumber(false) mHistoryDataMapCommon.addSetNumber()
if (mHistoryDataList.size > 0) { if (mHistoryDataList.size > 0) {
// mHistoryDataMapCommon.slideStopChanged(mHistoryDataList.size - 1) // mHistoryDataMapCommon.slideStopChanged(mHistoryDataList.size - 1)
} else { } else {

View File

@@ -301,9 +301,11 @@ class HomeTrackFragment :
// mTrackerDFUStateDialog?.addData(this) // mTrackerDFUStateDialog?.addData(this)
if (progress == 100) { if (progress == 100) {
LogUtil.e("固件下载完成") LogUtil.e("固件下载完成")
mDeviceDFUViewModel.startDFU( mBleTrackDeviceBean?.let { b ->
mContext!!, mBleTrackDeviceBean!!.bleDevice!!, filePath b.bleDevice?.let { ble ->
) mDeviceDFUViewModel.startDFU(mContext!!, ble, filePath)
}
}
} }
} }
} }
@@ -830,7 +832,7 @@ class HomeTrackFragment :
mBleTrackDeviceBean?.apply { mBleTrackDeviceBean?.apply {
SRBleUtil.instance.disconnectToMac(mac) SRBleUtil.instance.disconnectToMac(mac)
} }
mMapDeviceBean?.let {ble-> mMapDeviceBean?.let { ble ->
mTrackerSetViewModel.turnOff(ble.deviceServerId) mTrackerSetViewModel.turnOff(ble.deviceServerId)
} }
mTrackMenuList[1].menuValue = "" mTrackMenuList[1].menuValue = ""

View File

@@ -212,7 +212,6 @@ class FencesAddEditBaiduMapFragment : BaseBaiduMapFragment() {
*/ */
fun resetFencesViewCentre(centreLatLng: LatLng) { fun resetFencesViewCentre(centreLatLng: LatLng) {
mBaiduMap?.projection?.toScreenLocation(centreLatLng)?.apply { mBaiduMap?.projection?.toScreenLocation(centreLatLng)?.apply {
LogUtil.e("ddddssddfsdf$this")
if (mFencesCircleView.isVisible) { if (mFencesCircleView.isVisible) {
mFencesCircleView.setCentreXY(this) mFencesCircleView.setCentreXY(this)
} else if (mFencesRectView.isVisible) { } else if (mFencesRectView.isVisible) {

View File

@@ -868,6 +868,7 @@ abstract class BaseGoogleMapFragment :
) { ) {
//启用“我的位置”图层。 //启用“我的位置”图层。
isMyLocationEnabled = true isMyLocationEnabled = true
}
uiSettings.let { uiSettings.let {
//禁止显示“我的位置”按钮。 //禁止显示“我的位置”按钮。
it.isMyLocationButtonEnabled = false it.isMyLocationButtonEnabled = false
@@ -878,7 +879,6 @@ abstract class BaseGoogleMapFragment :
it.isTiltGesturesEnabled = false it.isTiltGesturesEnabled = false
it.isCompassEnabled = false it.isCompassEnabled = false
} }
}
onMapLoadOk(googleMap) onMapLoadOk(googleMap)
} }
@@ -961,9 +961,9 @@ abstract class BaseGoogleMapFragment :
* 由 points 组成的区域 距离手机屏幕距离screenPaddingPx 像素单位 * 由 points 组成的区域 距离手机屏幕距离screenPaddingPx 像素单位
*/ */
fun setLatLngZoom(context: Context, screenPaddingPx: Int, vararg points: LatLng) { fun setLatLngZoom(context: Context, screenPaddingPx: Int, vararg points: LatLng) {
val width = context.resources.displayMetrics.widthPixels // val width = context.resources.displayMetrics.widthPixels
val height = context.resources.displayMetrics.heightPixels // val height = context.resources.displayMetrics.heightPixels
val padding = width / 8 // val padding = width / 8
val builder = LatLngBounds.Builder() val builder = LatLngBounds.Builder()
for (item in points) { for (item in points) {
builder.include(item) builder.include(item)

View File

@@ -1,11 +1,8 @@
package com.abbidot.tracker.ui.fragment.map.googlemap package com.abbidot.tracker.ui.fragment.map.googlemap
import android.Manifest
import android.content.Context import android.content.Context
import android.content.pm.PackageManager
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Typeface import android.graphics.Typeface
import androidx.core.app.ActivityCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.abbidot.baselibrary.constant.MMKVKey import com.abbidot.baselibrary.constant.MMKVKey
import com.abbidot.baselibrary.util.AppUtils import com.abbidot.baselibrary.util.AppUtils
@@ -18,6 +15,7 @@ import com.abbidot.tracker.constant.ConstantInt
import com.abbidot.tracker.util.ViewUtil import com.abbidot.tracker.util.ViewUtil
import com.abbidot.tracker.vm.GeoCoderViewModel import com.abbidot.tracker.vm.GeoCoderViewModel
import com.abbidot.tracker.widget.MapMarkerInfoView import com.abbidot.tracker.widget.MapMarkerInfoView
import com.abbidot.tracker.widget.VerticalTopToBottomSeekBar
import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.BitmapDescriptor import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.BitmapDescriptorFactory import com.google.android.gms.maps.model.BitmapDescriptorFactory
@@ -34,6 +32,7 @@ class HistoryDataGoogleMapFragment : BaseGoogleMapFragment() {
// private val mScreenshotsViewModel: ScreenshotsViewModel by viewModels() // private val mScreenshotsViewModel: ScreenshotsViewModel by viewModels()
private lateinit var mGeoCoderViewModel: GeoCoderViewModel private lateinit var mGeoCoderViewModel: GeoCoderViewModel
private lateinit var mMarkerInfoView: MapMarkerInfoView private lateinit var mMarkerInfoView: MapMarkerInfoView
private lateinit var mVerticalTopToBottomSeekBar: VerticalTopToBottomSeekBar
private val mMarkerOptions = MarkerOptions() private val mMarkerOptions = MarkerOptions()
private lateinit var mPetIconDescriptor: BitmapDescriptor private lateinit var mPetIconDescriptor: BitmapDescriptor
@@ -56,10 +55,12 @@ class HistoryDataGoogleMapFragment : BaseGoogleMapFragment() {
context: Context, context: Context,
markerInfoView: MapMarkerInfoView, markerInfoView: MapMarkerInfoView,
geoCoderViewModel: GeoCoderViewModel, geoCoderViewModel: GeoCoderViewModel,
verticalTopToBottomSeekBar: VerticalTopToBottomSeekBar,
mapLoadOk: () -> Unit mapLoadOk: () -> Unit
) = HistoryDataGoogleMapFragment().apply { ) = HistoryDataGoogleMapFragment().apply {
mContext = context mContext = context
mGeoCoderViewModel = geoCoderViewModel mGeoCoderViewModel = geoCoderViewModel
mVerticalTopToBottomSeekBar = verticalTopToBottomSeekBar
mMarkerInfoView = markerInfoView mMarkerInfoView = markerInfoView
mMapLoadOk = mapLoadOk mMapLoadOk = mapLoadOk
} }
@@ -100,17 +101,22 @@ class HistoryDataGoogleMapFragment : BaseGoogleMapFragment() {
if (mMarkerInfoView.isVisible) setMarkerInfoViewOffset(mMarkerInfoView, it) if (mMarkerInfoView.isVisible) setMarkerInfoViewOffset(mMarkerInfoView, it)
} }
} }
setOnMarkerClickListener { m ->
//检测权限开启用户的当前位置https://developers.google.cn/maps/documentation/android-sdk/location?hl=zh-cn //点击数字大头针回到该位置
if (ActivityCompat.checkSelfPermission( if (m != mMarker) {
mContext!!, Manifest.permission.ACCESS_FINE_LOCATION val index = m.tag
) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( if (index is Int) {
mContext!!, Manifest.permission.ACCESS_COARSE_LOCATION mVerticalTopToBottomSeekBar.progress = index
) == PackageManager.PERMISSION_GRANTED mMarker?.let {
) { it.position = m.position
//启用“我的位置”图层。
isMyLocationEnabled = false
} }
slideStopChanged(index)
}
true
} else false
}
uiSettings.isCompassEnabled = true
} }
getLastLocation() getLastLocation()
@@ -267,7 +273,9 @@ class HistoryDataGoogleMapFragment : BaseGoogleMapFragment() {
) )
) )
) )
it.addMarker(numberMarker) it.addMarker(numberMarker)?.apply {
tag = i
}
} }
//设置宠物头像和经纬度 //设置宠物头像和经纬度

View File

@@ -0,0 +1,757 @@
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 FencesRectView2 : 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 mZoomBitmap: 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)
mZoomBitmap =
ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fence_rect_zoom_svg)
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=$mWidth,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()
// val a = getRectABCDXYPoint(FencesRectView.POINT_A)
// drawCircle(a[0], a[1], 50f, mTextPaint)
// val b = getRectABCDXYPoint(FencesRectView.POINT_B)
// drawCircle(b[0], b[1], 50f, mTextPaint)
// val c = getRectABCDXYPoint(FencesRectView.POINT_C)
// drawCircle(c[0], c[1], 50f, mTextPaint)
// val d = getRectABCDXYPoint(FencesRectView.POINT_D)
// drawCircle(d[0], d[1], 50f, mTextPaint)
}
}
private fun startDraw(canvas: Canvas) {
LogUtil.e("startDrawmRotateAngle=$mRotateAngle")
if (mRectWidth == 0f && mRectHeight == 0f) return
mRectLeft = -mRectWidth / 2
mRectTop = -mRectHeight / 2
mRectRight = mRectLeft + mRectWidth
mRectBottom = mRectTop + mRectHeight
mLocationBitmap?.apply {
val locationImageHalfWidth = width / 2f
val locationImageHalfHeight = height / 2f
val left = -locationImageHalfWidth
val top = -locationImageHalfHeight
canvas.drawBitmap(this, left, top, null)
}
mRectF.apply {
left = mRectLeft
top = mRectTop
right = mRectRight
bottom = mRectBottom
}
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
}
mZoomBitmap?.apply {
canvas.drawBitmap(
this, mRectRight - 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
val y = if (mHeight / 2 - mRectHeight / 2 > textHeight) {
mRectHeight / 2 + textHeight
} else {
mHeight / 2 - textHeight / 6
}
canvas.drawText(
mRectWidthDistanceText, -(textWidth / 2), y, mTextPaint
)
}
if (!TextUtils.isEmpty(mRectHeightDistanceText)) {
//文字宽度
val textWidth = mTextPaint.measureText(mRectHeightDistanceText)
//文字高度
val textHeight = fontMetrics.bottom - fontMetrics.top
val x = if (mWidth / 2 - mRectWidth / 2 > textWidth) {
mRectWidth / 2 + (textHeight / 6)
} else {
mWidth / 2 - textWidth - textHeight / 6
}
canvas.drawText(
mRectHeightDistanceText, x, (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) {
mZoomBitmap = 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
)
}
@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
}
}
//是否按下了放大缩小按钮
mZoomBitmap?.apply {
if (!isRotate && isClickScale(x, y, mRotateAngle)) {
isScale = true
LogUtil.e("点击了放大缩小")
mOldX = x
mOldY = y
return true
}
}
//因为有旋转角度,需要减去中心
if (mRectF.contains(x - mCentreX, y - mCentreY)) {
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) {
//计算手指按下画布移动后的xy角度
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
invalidate()
return true
} else if (isDrag) {
val transformX = x - mOldX
val transformY = y - mOldY
mCentreX += transformX
mCentreY += 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 是坐标轴移动以mCentreXmCentreY原点的坐标
* @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())
}
/**
* 是否点击按下了旋转按照移动了坐标轴原点mCentreXmCentreY计算初始坐标
*/
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
}
/**
* 是否点击按下了放大缩小,按照移动了坐标轴原点mCentreXmCentreY计算初始坐标
*/
private fun isClickScale(clickX: Float, clickY: Float, rotate: Double): Boolean {
var x1 = (mRectWidth / 2 - mZoomBitmap!!.width)
var x2 = (mRectWidth / 2 + mZoomBitmap!!.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
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 != mZoomBitmap) {
mZoomBitmap?.recycle()
mZoomBitmap = null
}
}
}

View File

@@ -172,12 +172,12 @@ class FencesRectView3 : View {
super.onDraw(canvas) super.onDraw(canvas)
canvas.apply { canvas.apply {
save() // save()
//只为旋转才移动坐标轴 // //只为旋转才移动坐标轴
translate(mCentreX, mCentreY) // translate(mCentreX, mCentreY)
rotate(mRotateAngle.toFloat()) // rotate(mRotateAngle.toFloat())
startDraw(this) startDraw(this)
restore() // restore()
} }
} }

View File

@@ -867,11 +867,7 @@
<string name="txt_residual_value">Restwert</string> <string name="txt_residual_value">Restwert</string>
<string name="txt_month_unit">/Monat x%s</string> <string name="txt_month_unit">/Monat x%s</string>
<string name="txt_auto_subscription_year">(Verlängerung zu $%s jährlich danach)</string> <string name="txt_auto_subscription_year">(Verlängerung zu $%s jährlich danach)</string>
<string name="txt_auto_subscription_year1">(Verlängerung zu</string>
<string name="txt_auto_subscription_year2">jährlich danach)</string>
<string name="txt_auto_subscription_month">(Verlängerung zu $%s monatlich danach)</string> <string name="txt_auto_subscription_month">(Verlängerung zu $%s monatlich danach)</string>
<string name="txt_auto_subscription_month1">(Verlängerung zu</string>
<string name="txt_auto_subscription_month2">monatlich danach)</string>
<string name="txt_about_dec">Haustiere orten und trainieren, um Verlust/Gefahren vorzubeugen.</string> <string name="txt_about_dec">Haustiere orten und trainieren, um Verlust/Gefahren vorzubeugen.</string>
<string name="txt_year_unit">/%s Jahr</string> <string name="txt_year_unit">/%s Jahr</string>
<string name="txt_advanced_set">Erweiterte Einstellungen</string> <string name="txt_advanced_set">Erweiterte Einstellungen</string>

View File

@@ -910,11 +910,7 @@
<string name="txt_residual_value">剩余价值</string> <string name="txt_residual_value">剩余价值</string>
<string name="txt_month_unit">/月 x%s</string> <string name="txt_month_unit">/月 x%s</string>
<string name="txt_auto_subscription_year">(此后按每年 $%s 续订)</string> <string name="txt_auto_subscription_year">(此后按每年 $%s 续订)</string>
<string name="txt_auto_subscription_year1">(此后按</string>
<string name="txt_auto_subscription_year2">/年续费)</string>
<string name="txt_auto_subscription_month">(此后按每月 $%s 续订)</string> <string name="txt_auto_subscription_month">(此后按每月 $%s 续订)</string>
<string name="txt_auto_subscription_month1">(此后按</string>
<string name="txt_auto_subscription_month2">/月续费)</string>
<string name="txt_about_dec">追踪并训练宠物,以防丢失或危险。</string> <string name="txt_about_dec">追踪并训练宠物,以防丢失或危险。</string>
<string name="txt_year_unit">/%s 年</string> <string name="txt_year_unit">/%s 年</string>
<string name="txt_advanced_set">高级设置</string> <string name="txt_advanced_set">高级设置</string>

View File

@@ -968,11 +968,7 @@
<string name="txt_residual_value">Residual Value</string> <string name="txt_residual_value">Residual Value</string>
<string name="txt_month_unit">/month x%s</string> <string name="txt_month_unit">/month x%s</string>
<string name="txt_auto_subscription_year">(Renew at $%s per year thereafter)</string> <string name="txt_auto_subscription_year">(Renew at $%s per year thereafter)</string>
<string name="txt_auto_subscription_year1">(Renew at </string>
<string name="txt_auto_subscription_year2">per year thereafter)</string>
<string name="txt_auto_subscription_month">(Renew at $%s per month thereafter)</string> <string name="txt_auto_subscription_month">(Renew at $%s per month thereafter)</string>
<string name="txt_auto_subscription_month1">(Renew at </string>
<string name="txt_auto_subscription_month2">per month thereafter)</string>
<string name="txt_about_dec">Track and train pets to prevent loss or danger.</string> <string name="txt_about_dec">Track and train pets to prevent loss or danger.</string>
<string name="txt_year_unit">/%s year</string> <string name="txt_year_unit">/%s year</string>
<string name="txt_advanced_set">Advanced Setting</string> <string name="txt_advanced_set">Advanced Setting</string>
@@ -1067,7 +1063,10 @@
<string name="txt_renewal_day">Renewal: $%s/%s day on %s</string> <string name="txt_renewal_day">Renewal: $%s/%s day on %s</string>
<string name="txt_day_unit">/day x%s</string> <string name="txt_day_unit">/day x%s</string>
<string name="txt_location_tip">"ABBIDOT APP collects location data,The route and distance between the current location and the device can be calculated."</string> <string name="txt_location_tip">"ABBIDOT APP collects location data,The route and distance between the current location and the device can be calculated."</string>
<string name="txt_auto_subscription_day">(Renew at $%s per %s day thereafter)</string> <string name="txt_auto_subscription_day">(Renew at $%s per day thereafter)</string>
<string name="txt_auto_subscription_days">(Renew at $%s/%s days thereafter)</string>
<string name="txt_locating">Locating&#8230;</string> <string name="txt_locating">Locating&#8230;</string>
<string name="txt_auto_subscription_years">(Renew at $%s/%s years thereafter)</string>
<string name="txt_auto_subscription_months">(Renew at $%s/%s months thereafter)</string>
</resources> </resources>