Linx's Solution

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

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

}