高德地图平滑移动(跟踪进度)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
package com.zx.xiaohui.helper

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.TypeEvaluator
import android.animation.ValueAnimator
import android.content.Context
import android.view.View
import android.view.animation.LinearInterpolator
import android.widget.LinearLayout
import com.amap.api.maps.AMap
import com.amap.api.maps.AMapUtils
import com.amap.api.maps.model.*
import com.zx.xiaohui.R
import kotlin.collections.ArrayList
import kotlin.math.atan2

class SmoothMarker(private val context:Context,private val mAMap: AMap) : AMap.InfoWindowAdapter {
    class LngLat(val lng: Double, val lat: Double) {
        override fun toString(): String {
            return "LngLat{" +
                    "lng=" + lng +
                    ", lat=" + lat +
                    '}'
        }
    }

    class LngLatEvaluator : TypeEvaluator<Any?> {
        override fun evaluate(fraction: Float, startValue: Any?, endValue: Any?): Any? {
            val startLnglat = startValue as LngLat?
            val endLnglat = endValue as LngLat?
            val lng = startLnglat!!.lng + fraction * (endLnglat!!.lng - startLnglat.lng)
            val lat = startLnglat.lat + fraction * (endLnglat.lat - startLnglat.lat)
            return LngLat(lng, lat)
        }
    }
    /**
     * 实现此接口来监听平滑移动的过程
     */
    interface SmoothMarkerMoveListener {
        fun move(curIndex:Int)
        fun stop()
    }


    private var moveListener: SmoothMarkerMoveListener? = null

    //每段点的队列,第一个点为起点
    private val points: ArrayList<LatLng?> = arrayListOf()

    //每段距离 队列  大小为points.size() - 1
    //使用ArrayList而不是LinkedList, 方可实现拖拽进度条来从任意位置继续平滑移动
    private val eachDistance : ArrayList<Double> = arrayListOf()

    private var totalDistance = 0.0 //总距离
    private var remainDistance = 0.0 // 剩余距离
    private var endPoint: LatLng? = null
    private var lastEndPoint: LatLng? = null

    //移动的marker
    var marker: Marker? = null
        private set
    /**
     * 平滑移动的速度,单位km/h
     */
    var speed = 0f

    //marker动画
    private var markerAnimator: ValueAnimator? = null

    //是否暂停动画
    private var animStop = true
    //当前位置所对应的经纬度
    private var currentLatLng: LatLng? = null
    //下一个位置
    private var nextPos: LatLng? = null
    //一段动画被中断
    private var animCancel = false
    //本次动画移动的距离
    private var currentPlayDistance = 0.0
    //本次动画剩余距离
    private var currentRemainDistance = 0.0
    //悬浮跟随的窗口(移动的头像)
    private var infoWindowLayout: LinearLayout? = null
    //当前进行动画的那一段的索引
    private var curIndex:Int = 0

    /**
     * 总段数, 用于进度条显示
     */
    var totalCount :Int = 0

    /**
     * 设置平滑移动的经纬度数组
     *
     * 将同时创建marker和跟随悬浮的图标
     * @param points
     */
    fun setPoints(points: List<LatLng>) {
        this.points.clear()
        for (latLng in points) {
            this.points.add(latLng)
        }
        if (points.size > 1) {
            endPoint = points[points.size - 1]
            lastEndPoint = points[points.size - 2]
        }
        eachDistance.clear()
        totalDistance = 0.0
        curIndex = 0
        totalCount = 0

        //逐段计算距离
        for (i in 0 until points.size - 1) {
            val distance = AMapUtils.calculateLineDistance(points[i], points[i + 1]).toDouble()
            eachDistance.add(distance)
            totalDistance += distance
            totalCount ++
        }
        remainDistance = totalDistance

        val markerPoint: LatLng? = this.points.first()
        if (marker != null) {
            marker!!.position = markerPoint
        } else {
            marker = mAMap.addMarker(
                MarkerOptions().anchor(
                    0.5f,
                    0.5f
                ).icon(BitmapDescriptorFactory.fromResource(R.mipmap.baby))
                    .alpha(0f)
                    .setInfoWindowOffset(0,130)
            )
            infoWindowLayout = LinearLayout(context)
            infoWindowLayout!!.orientation = LinearLayout.VERTICAL
            infoWindowLayout!!.setBackgroundResource(R.mipmap.baby)
            marker!!.position = markerPoint
        }
    }


    /**
     * 开始平滑移动
     */
    fun startSmoothMove() {
        if (points.size < 1) {
            return
        }
        animStop = false

        mAMap.setInfoWindowAdapter(this)

        marker!!.showInfoWindow()
        startRun()
    }

    /**
     * 开始动画
     *
     * 递归调用
     */
    private fun startRun() {
        if (curIndex>=totalCount-1) {
            setEndRotate()
            return
        }
        val dis = eachDistance[curIndex]
        val time = (dis / speed * 60 * 60).toLong()
        //计算旋转
        val curPos: LatLng = marker!!.position
        nextPos = points[curIndex+1]

        val rotate = getRotate(curPos, nextPos)
        marker!!.rotateAngle = 360 - rotate + mAMap.cameraPosition.bearing
        val curLngLat = LngLat(curPos.longitude, curPos.latitude)
        val nextLngLat = LngLat(nextPos!!.longitude, nextPos!!.latitude)

        markerAnimator = ValueAnimator.ofObject(LngLatEvaluator(), curLngLat, nextLngLat)
        markerAnimator!!.duration = time //本段动画总时间
        markerAnimator!!.interpolator = LinearInterpolator() //线性的动画

        //动画更新marker的位置
        markerAnimator!!.addUpdateListener { animation ->
            val lngLat: LngLat = animation.animatedValue as LngLat
            currentLatLng = LatLng(lngLat.lat, lngLat.lng)

            marker!!.position = currentLatLng
            //当前段运动的距离
            currentPlayDistance = dis * animation.currentPlayTime / time
            //当前段剩下的距离
            currentRemainDistance = remainDistance - currentPlayDistance
            if (currentRemainDistance < 0)
                currentRemainDistance = 0.0

        }
        markerAnimator!!.addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator?) {
                 if (!animCancel) {
                     if (dis.isNaN()) {
                         remainDistance -= 0
                     } else {
                         remainDistance -= dis
                     }

                } else {
                     if (currentPlayDistance.isNaN()) {
                         remainDistance -= 0
                     } else {
                         remainDistance -= currentPlayDistance
                     }
                }
                //每一段动画结束后 回调索引
                if (moveListener != null) {
                    moveListener!!.move(curIndex)
                }
                //增加索引, 开始下一段
                curIndex++

                //如果不是最后一段
                if (!animStop) {
                    startRun()
                    animCancel = false
                }
            }

            override fun onAnimationCancel(animation: Animator?) {
                animCancel = true
            }
        })
        markerAnimator!!.start()
    }

    /**
     * 设置运行时间过短导致的 终点及角度问题
     */
    private fun setEndRotate() {
        val rotate = getRotate(lastEndPoint, endPoint)
        marker!!.rotateAngle = 360 - rotate + mAMap.cameraPosition.bearing
        marker!!.position = endPoint

        marker!!.hideInfoWindow()
        if (moveListener != null) {
            moveListener!!.stop()
        }
    }

    /**
     * 根据经纬度计算需要偏转的角度
     */
    private fun getRotate(curPos: LatLng?, nextPos: LatLng?): Float {
        val x1: Double = curPos!!.latitude
        val x2: Double = nextPos!!.latitude
        val y1: Double = curPos.longitude
        val y2: Double = nextPos.longitude
        return (atan2(y2 - y1, x2 - x1) / Math.PI * 180).toFloat()
    }

    /**
     * 停止平滑移动
     */
    fun stopMove() {
        if (animStop) {
            return
        }
        animStop = true
        markerAnimator!!.cancel()
    }

    /**
     * 暂停动画
     */
    fun pauseMove() {
        if (animStop) {
            return
        }
        animStop = true
        markerAnimator!!.cancel()
    }

    /**
     * 即时修改移动速度
     * @param speed Float 单位 km/h
     */
    fun changeSpeed(speed: Float) {
        pauseMove()
        this.speed = speed
        resumeMove()
    }

    /**
     * 恢复动画
     */
    fun resumeMove() {
        if (!animStop) {
            return
        }
        animStop = false
        startRun()
    }

    /**
     * 获取当前marker位置
     */
    val position: LatLng?
        get() = if (marker == null) null else marker!!.position

    /**
     * 销毁
     */
    fun destroy() {
        stopMove()
        if (marker != null) {
            marker!!.destroy()
            marker = null
        }
        points.clear()
        eachDistance.clear()
    }

    /**
     * 开始进度拖动时调用(按下的时候), 用于暂停动画
     */
    fun startChangePositionIndex() {
        pauseMove()
    }

    /**
     * 结束禁毒拖动时调用(抬起的时候), 继续动画
     */
    fun endChangePositionIndex() {
        resumeMove()
    }

    /**
     * 进度拖动过程中调用, 用于实时地显示位置
     * @param index Int
     */
    fun setPositionIndex(index:Int) {
        curIndex = index
        marker!!.position = points[curIndex]
    }

    fun setMoveListener(moveListener: SmoothMarkerMoveListener?) {
        this.moveListener = moveListener
    }


    //region <重载代码>
    override fun getInfoContents(p0: Marker?): View {
        return infoWindowLayout!!
    }

    override fun getInfoWindow(p0: Marker?): View {
        return infoWindowLayout!!
    }
    //endregion

}
Licensed under CC BY-NC-SA 4.0
记录平时瞎折腾遇到的各种问题, 方便查找
使用 Hugo 构建
主题 Stack 3.29.0Jimmy 设计