Android-仿高德的路线规划搜索框

最近开发车主相关的App功能, 做一个出行功能大致和高德路线规划功能相当。 对高德的路线规划交做了细致的调研。 功能拆分开来,路线规划逻辑主要涉及:POI搜索和 路线规划; UI部分主要任务在搜索输入框开发, 列表开发, 路线图层开发。 路线的搜索和路线规划及其规划线路绘制方面高德开放平台均有文档详细介绍,就不再细谈了;此篇主要介绍仿高德的路线搜索交互界面的开发,本文简称为路线搜索框开发。

此路线搜索框有两种状态,编辑途径点态和无途径点态; 此外 编辑途径点态还包含拖动地址改变顺序功能。 考虑拖动的交互,Android里常选的方案是RecyclerView实现;但是今天我们采用Tween动画实现拖动的交互效果。 Tween Animation 和属性动画不一样,不改变View的真实属性数据(也就不改变子View真实位置),仅动画效果的显示,可利用此特性来展示拖动时的切换效果,和拖动结束时回到真实状态的一个切换。 利用数据交换来刷新 拖动后台的UI。

UI整体布局采用XML硬编码的形式引入,使用include的方式复用每条位置POI搜索框。

ini 复制代码
<LinearLayout
                    android:id="@+id/ll_more_edit_line"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_toLeftOf="@+id/ll_edit_action"
                    android:orientation="vertical">

                    <include
                        android:id="@+id/ll_start_location"
                        layout="@layout/edit_search_key" />

                    <include
                        android:id="@+id/ll_end_location"
                        layout="@layout/edit_search_key" />
                </LinearLayout>

edit_search_key.xml文件, 定义每条搜索POI的子View

ini 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <RelativeLayout
        android:id="@+id/ll_edit_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/iv_edt"
            android:layout_width="@dimen/dp_32"
            android:layout_height="@dimen/dp_32"
            android:scaleType="centerInside"
            android:src="@drawable/start_location_point" />

        <EditText
            android:id="@+id/edt_input_key"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/iv_edt"
            android:background="@color/transparent"
            android:hint=""
            android:minHeight="@dimen/dp_32"
            android:paddingVertical="@dimen/dp_6"
            android:singleLine="true"
            android:text="@string/confirm"
            android:textColor="@color/text_content_first"
            android:textColorHint="@color/text_content_five"
            android:textSize="@dimen/dp_14" />

        <ImageView
            android:id="@+id/iv_edit_move_tag"
            android:layout_width="@dimen/fit_dp_20"
            android:layout_height="@dimen/fit_dp_20"
            android:layout_alignRight="@+id/edt_input_key"
            android:layout_centerVertical="true"
            android:layout_marginRight="@dimen/fit_dp_8"
            android:src="@drawable/icon_edit_move"
            android:visibility="gone" />
    </RelativeLayout>

    <ImageView
        android:id="@+id/iv_del_edit"
        android:layout_width="@dimen/dp_20"
        android:layout_height="@dimen/dp_20"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="@dimen/dp_8"
        android:scaleType="centerInside"
        android:src="@drawable/ic_close"
        android:visibility="gone" />
</LinearLayout>

增删途径点,通过LinearLayout布局动态AddView()和RemoveView()实现; 增加途径点

ini 复制代码
    /**
     * @param middleIndex 从0开始,排除起点和终点之外的列表
     */
    private fun addMiddleLocation(middleIndex: Int = 0): EditText {
        val params = LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
        val top: Int = resources.getDimensionPixelSize(R.dimen.dp_6)
        val bottom = 0
        params.topMargin = top
        params.bottomMargin = bottom
        val middleViewBinding = createMiddleLocationEditView();
        var middlePos: Int = middleIndex
        if (getMiddleCount() > middleIndex) {
            binding.llMoreEditLine.addView(middleViewBinding.root, middleIndex + 1, params)
        } else {
            val index = binding.llMoreEditLine.childCount - 1;
            binding.llMoreEditLine.addView(middleViewBinding.root, index, params)
            middlePos = getMiddleCount() - 1;
        }
        val middleData = EditLocation.createMiddleLocation()
        middleViewBinding.root.setTag(R.id.cb_item_tag, middleData)
        middleViewBinding.edtInputKey.setTag(R.id.cb_item_tag, middleData)
        locationList.add(middlePos + 1, middleData)
        updateAddMoreUIState()
        post {
            middleViewBinding.edtInputKey.requestFocus()
        }
        return middleViewBinding.edtInputKey
    }

删除途径点

kotlin 复制代码
    /**
     * @param middleIndex 从0开始,排除起点和终点之外的列表
     */
    private fun removeMiddleLocation(middleIndex: Int) {
        getMiddleItemViewAt(middleIndex)?.let { child ->
            binding.llMoreEditLine.removeView(child)
            (child.getTag(R.id.cb_item_tag) as? EditLocation).let { data ->
                locationList.remove(data)
            }
        }
    
        val count = getMiddleCount()
        switchUIState(true, count)
        updateAddMoreUIState()
    }

长按事件的触发直接在事件分发的顶层拦截触摸事件判断是否长按:

kotlin 复制代码
val mainHandler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            when (msg.what) {
                MSG_LONG_CLICK -> {
                    val obj = msg.obj as Point
                    val windowX = obj.x;
                    val windowY = obj.y
                    findLongTouchValidView(windowX, windowY)?.let { findDragView ->
                        onDragStart(findDragView, msg.arg1, msg.arg2)
                    }
                }
            }
        }
    }

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        var dy = 0f
        val tempDragView = dragSelectedView;
        when (ev?.action) {
            MotionEvent.ACTION_DOWN -> {
                yPos = ev.y
                mainHandler.removeMessages(MSG_LONG_CLICK)
                val msg = mainHandler.obtainMessage(MSG_LONG_CLICK)
                msg.arg1 = ev.x.toInt()
                msg.arg2 = ev.y.toInt()
                msg.obj = Point(ev.rawX.toInt(), ev.rawY.toInt())
                mainHandler.sendMessageDelayed(msg, LONG_TIME)
            }
            MotionEvent.ACTION_MOVE -> {
                dy = ev.y - yPos;
                yPos = ev.y
                if (tempDragView != null && dy != 0f) {
                    mainHandler.removeMessages(MSG_LONG_CLICK)
                    onDragMove(tempDragView!!, ev.x.toInt(), ev.y.toInt(), dy)
                }
            }
            MotionEvent.ACTION_CANCEL -> {
                mainHandler.removeMessages(MSG_LONG_CLICK)
                if (tempDragView != null) {
                    onDragEnd()
                }
            }
            MotionEvent.ACTION_UP -> {
                mainHandler.removeMessages(MSG_LONG_CLICK)
                if (tempDragView != null) {
                    onDragEnd()
                }
            }
        }
        if (tempDragView != null) {
            return true
        }
        return super.dispatchTouchEvent(ev);
    }

拖动的View是一个可以悬浮在容器之上的空间中, 需要定义一个View来显示拖拽的目标View,这里采用的是ImageView; 把即将拖拽的View生成对应Bitmap来显示到悬浮的ImageView中,并把悬浮的位置设定在拖拽View的位置,实现拖拽重合移动效果。 代码片段如下:

scss 复制代码
/**
     * 开始拖拽,在长按事件触发之后
     */
    private fun onDragStart(dragItemVIew: View, touchX: Int, touchY: Int) {
        try {
            if (itemEditHeight != 0 && isDragEnable()) {
                val itemView = dragItemVIew
                val vImage = vToBitmap(itemView)
                floatView.setImageBitmap(vImage)
                val top = itemView.top + ((itemView.parent as? View)?.top ?: 0)
                Log.w(TAG, "top === $top");
                floatView.tag = top;
                setViewTopMargin(floatView, top)
                floatView.visibility = View.VISIBLE
                itemView.visibility = INVISIBLE
                dragSelectedView = itemView;
                dragInsertViewStack.clear();
                resetItemViewRectInfo()
                ClickVibrator.clickSingle(context, 100L)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 拖拽移动
     */
    private fun onDragMove(dragItemVIew: View, touchX: Int, touchY: Int, dy: Float) {
        val newTop: Int = floatView.tag as Int + dy.toInt()
        val newBottom: Int = newTop + itemEditHeight;
        if (newTop in minDragTop..maxDragTop) {
            floatView.tag = newTop
            setViewTopMargin(floatView, newTop)
            //碰撞检测
            for (rect in itemRectHashMap.keys) {
                val line = rect.top + rect.height() / 2
                val itemView = itemRectHashMap.get(rect)
                if (itemView == dragSelectedView) {
                    continue
                }
                if (line in newTop..newBottom) {
                    //发生碰撞
                    val dTop = line - newTop;
                    val dBottom = newBottom - line;
                    Log.w(TAG, "move === $dTop -- $dBottom")
                    val fromTop = rect.top;
                    val animDistance = rect.height() + resources.getDimensionPixelSize(R.dimen.dp_6)
                    if (dTop < dBottom) {
                        //item down
                        post {
                            val destTop = fromTop + animDistance
                            itemView?.let {
                                itemRectHashMap.remove(rect)
                                val newRect =
                                    Rect(rect.left, destTop, rect.right, destTop + itemEditHeight)
                                itemRectHashMap.put(newRect, itemView)
                                trans(itemView, rect, newRect)
                            }

                        }
                    } else {
                        //item up
                        post {
                            val destTop = fromTop - animDistance
                            itemView?.let {
                                itemRectHashMap.remove(rect)
                                val newRect =
                                    Rect(rect.left, destTop, rect.right, destTop + itemEditHeight)
                                itemRectHashMap.put(newRect, itemView)
                                trans(itemView, rect, newRect)
                            }
                        }
                    }
                    break;
                }
            }
        }
    }
/**
     * 拖拽结束
     */
    private fun onDragEnd() {
        val replaceView = if (dragInsertViewStack.isEmpty()) null else dragInsertViewStack.pop()
        if (dragSelectedView != null && replaceView != null) {
            val dragData = dragSelectedView?.getTag(R.id.cb_item_tag) as? EditLocation
            val replaceData = replaceView.getTag(R.id.cb_item_tag) as? EditLocation
            callEditLocationDataChange(dragData, replaceData)
        }
        clearDragUIState()
    }

平移动画实现,需要注意的是:平移的参数主要是相对View的原始位置 计算上下移动的位置。 所以需要改根据位置计算坐标变换。

kotlin 复制代码
private fun trans(item: View?, fromRect: Rect, toRect: Rect) {
        val divideTop = binding.rlEditLocationContainer.top
        val fromTop = fromRect.top - divideTop
        val destTop = toRect.top - divideTop
        val originTop = (item?.top ?: 0) + ((item?.parent as? View)?.top ?: 0)
        val animationDis = destTop - fromTop
        val moveOut = originTop == fromTop
        if (moveOut) {
            dragInsertViewStack.push(item)
        } else if (!dragInsertViewStack.isEmpty()) {
            dragInsertViewStack.pop();
        }
        val fromY = if (moveOut) 0 else -1 * animationDis
        val toY = if (moveOut) animationDis else 0;
        Log.w(TAG, "trans start == $originTop --- $fromY,$toY")
        val tran = TranslateAnimation(
            Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f,
            Animation.ABSOLUTE, fromY.toFloat(), Animation.ABSOLUTE, toY.toFloat()
        )
        tran.duration = (300L)
        tran.fillAfter = true;
        item?.startAnimation(tran)
    }

在拖动结束时清空所用动画和 计算交换后的数据,重新更新对应位置的显示数据。

ini 复制代码
private fun callEditLocationDataChange(dragData: EditLocation?, replaceData: EditLocation?) {
        if (dragData == null || replaceData == null) {
            return;
        }
        val tempList = ArrayList<EditLocation>();
        val tempPoiList = ArrayList<EditLocation>();
        tempList.addAll(locationList)
        val dragIndex = tempList.indexOf(dragData)
        val replaceIndex = tempList.indexOf(replaceData);
        if (dragIndex != -1 && replaceIndex != -1 && dragIndex != replaceIndex) {
            tempList.removeAt(dragIndex);
            val insertIndex = tempList.indexOf(replaceData);
            if (insertIndex != -1) {
                val addIndex = if (dragIndex > replaceIndex) insertIndex else insertIndex + 1
                tempList.add(addIndex, dragData)
            }
            if (tempList.size == locationList.size) {
                for (item in tempList) {
                    val temp = EditLocation()
                    temp.poi = item.poi;
                    temp.editContent = item.editContent
                    tempPoiList.add(temp)
                }
                for (i in 0 until locationList.size) {
                    val item = locationList[i]
                    val tempData = tempPoiList[i]
                    item.poi = tempData.poi;
                    item.editContent = tempData.editContent
                    val tv = findLocationTextView(i)
                    updateLocationUI(item, tv)
                }
            }
        }
        tempList.clear()
        tempPoiList.clear()
    }

private fun clearDragUIState() {
        dragSelectedView?.visibility = View.VISIBLE
        floatView.visibility = View.GONE
        dragSelectedView = null
        for (i in 0 until binding.llMoreEditLine.childCount) {
            binding.llMoreEditLine.getChildAt(i)?.clearAnimation()
        }
        dragInsertViewStack.clear()
    }

附上完整代码:

kotlin 复制代码
package com.cw.widget.map

import android.content.Context
import android.graphics.*
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.animation.Animation
import android.view.animation.TranslateAnimation
import android.widget.ImageView
import com.cw.widget.R
import com.cw.widget.utils.ClickVibrator
import java.util.*

class DragEditTripLocationView : EditTripLocationListView {
    val TAG = "Car-EditTrip %s"

    constructor(context: Context) : super(context) {}
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
    }

    constructor(
        context: Context,
        attrs: AttributeSet?,
        defStyleAttr: Int,
        defStyleRes: Int
    ) : super(context, attrs, defStyleAttr, defStyleRes) {
    }

    companion object {
        const val LONG_TIME = 1000L;
        const val MSG_LONG_CLICK = 100;

        const val isDebug = false;
    }

    val floatView: ImageView
    var itemEditHeight: Int = 0;
    var itemEditWidth: Int = 0;
    var minDragTop: Int = 0;
    var maxDragTop: Int = 0;
    var testPaint: Paint = Paint()
    var dragSelectedView: View? = null;
    val itemRectHashMap = HashMap<Rect, View>();

    val floatPaint = Paint();

    /**
     * 脱拽过程中插入的View
     */
    private val dragInsertViewStack = Stack<View?>();

    val mainHandler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            when (msg.what) {
                MSG_LONG_CLICK -> {
                    val obj = msg.obj as Point
                    val windowX = obj.x;
                    val windowY = obj.y
                    findLongTouchValidView(windowX, windowY)?.let { findDragView ->
                        onDragStart(findDragView, msg.arg1, msg.arg2)
                    }
                }
            }
        }
    }

    init {
        floatView = ImageView(context)
        binding.rlEditTwoLocation.addView(
            floatView,
            LayoutParams.WRAP_CONTENT,
            LayoutParams.WRAP_CONTENT
        )
        floatView.visibility = View.GONE
        testPaint.color = Color.RED;
        testPaint.style = Paint.Style.STROKE
        floatPaint.color = Color.parseColor("#ffc0df17");
        floatPaint.strokeWidth = 2f;
        floatPaint.style = Paint.Style.STROKE
    }


    private fun setViewTopMargin(v: View, top: Int) {
        (v.layoutParams as? MarginLayoutParams)?.let { p ->
            p.topMargin = top;
            v.layoutParams = p;
        }
    }

    private fun vToBitmap(v: View): Bitmap {
        val w = v.width;
        val h = v.height;
        val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565)
        val canvas = Canvas(bitmap)
        canvas.drawColor(Color.WHITE)
        v.draw(canvas)
        val editWidth = v.findViewById<View>(R.id.ll_edit_content)?.width ?: w
        val corners = resources.getDimensionPixelSize(R.dimen.dp_2)
        canvas.drawRoundRect(
            RectF(0f, 0f, editWidth.toFloat(), h.toFloat()),
            corners.toFloat(),
            corners.toFloat(),
            floatPaint
        )
        canvas.setBitmap(null)
        return bitmap
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        itemEditWidth = getMiddleItemViewAt(0)?.width ?: 0
        itemEditHeight = getMiddleItemViewAt(0)?.height ?: 0
    }

    override fun dispatchDraw(canvas: Canvas?) {
        super.dispatchDraw(canvas)
        if (isDebug) {
            if (itemRectHashMap.isNotEmpty()) {
                for (item in itemRectHashMap.keys) {
                    canvas?.drawRect(item, testPaint)
                }
            }
        }
    }

    private fun resetItemViewRectInfo() {
        minDragTop = 0;
        maxDragTop = binding.rlEditTwoLocation.height - itemEditHeight;
        itemRectHashMap.clear();
        for (i in 0 until binding.llMoreEditLine.childCount) {
            val itemView = binding.llMoreEditLine.getChildAt(i)
            if (itemView != null && itemView != dragSelectedView) {
                itemRectHashMap.put(getViewRect(itemView), itemView)
            }
        }
    }

    /**
     * 获取相对于整个View 的Rect数据
     */
    private fun getViewRect(v: View): Rect {
        val pTop = binding.rlEditLocationContainer.top + ((v.parent as? View)?.top ?: 0)
        val top = v.top + pTop
        val bottom = v.bottom + pTop;
        val left = 0;
        val right = v.width;

        return Rect(left, top, right, bottom)
    }

    private fun findLongTouchValidView(x: Int, y: Int): View? {
        val rect = Rect();
        for (i in 0 until binding.llMoreEditLine.childCount) {
            binding.llMoreEditLine.getChildAt(i)?.let {
                it.getGlobalVisibleRect(rect)
                if (rect.contains(x, y)) {
                    return it
                }
            }
        }
        return null;
    }

    private fun isDragEnable(): Boolean {
        return binding.rlMoreAddLocation.visibility == View.VISIBLE
    }

    /**
     * 开始拖拽,在长按事件触发之后
     */
    private fun onDragStart(dragItemVIew: View, touchX: Int, touchY: Int) {
        try {
            if (itemEditHeight != 0 && isDragEnable()) {
                val itemView = dragItemVIew
                val vImage = vToBitmap(itemView)
                floatView.setImageBitmap(vImage)
                val top = itemView.top + ((itemView.parent as? View)?.top ?: 0)
                Log.w(TAG, "top === $top");
                floatView.tag = top;
                setViewTopMargin(floatView, top)
                floatView.visibility = View.VISIBLE
                itemView.visibility = INVISIBLE
                dragSelectedView = itemView;
                dragInsertViewStack.clear();
                resetItemViewRectInfo()
                ClickVibrator.clickSingle(context, 100L)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 拖拽移动
     */
    private fun onDragMove(dragItemVIew: View, touchX: Int, touchY: Int, dy: Float) {
        val newTop: Int = floatView.tag as Int + dy.toInt()
        val newBottom: Int = newTop + itemEditHeight;
        if (newTop in minDragTop..maxDragTop) {
            floatView.tag = newTop
            setViewTopMargin(floatView, newTop)
            //碰撞检测
            for (rect in itemRectHashMap.keys) {
                val line = rect.top + rect.height() / 2
                val itemView = itemRectHashMap.get(rect)
                if (itemView == dragSelectedView) {
                    continue
                }
                if (line in newTop..newBottom) {
                    //发生碰撞
                    val dTop = line - newTop;
                    val dBottom = newBottom - line;
                    Log.w(TAG, "move === $dTop -- $dBottom")
                    val fromTop = rect.top;
                    val animDistance = rect.height() + resources.getDimensionPixelSize(R.dimen.dp_6)
                    if (dTop < dBottom) {
                        //item down
                        post {
                            val destTop = fromTop + animDistance
                            itemView?.let {
                                itemRectHashMap.remove(rect)
                                val newRect =
                                    Rect(rect.left, destTop, rect.right, destTop + itemEditHeight)
                                itemRectHashMap.put(newRect, itemView)
                                trans(itemView, rect, newRect)
                            }

                        }
                    } else {
                        //item up
                        post {
                            val destTop = fromTop - animDistance
                            itemView?.let {
                                itemRectHashMap.remove(rect)
                                val newRect =
                                    Rect(rect.left, destTop, rect.right, destTop + itemEditHeight)
                                itemRectHashMap.put(newRect, itemView)
                                trans(itemView, rect, newRect)
                            }
                        }
                    }
                    break;
                }
            }
        }
    }

    private fun trans(item: View?, fromRect: Rect, toRect: Rect) {
        val divideTop = binding.rlEditLocationContainer.top
        val fromTop = fromRect.top - divideTop
        val destTop = toRect.top - divideTop
        val originTop = (item?.top ?: 0) + ((item?.parent as? View)?.top ?: 0)
        val animationDis = destTop - fromTop
        val moveOut = originTop == fromTop
        if (moveOut) {
            dragInsertViewStack.push(item)
        } else if (!dragInsertViewStack.isEmpty()) {
            dragInsertViewStack.pop();
        }
        val fromY = if (moveOut) 0 else -1 * animationDis
        val toY = if (moveOut) animationDis else 0;
        Log.w(TAG, "trans start == $originTop --- $fromY,$toY")
        val tran = TranslateAnimation(
            Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f,
            Animation.ABSOLUTE, fromY.toFloat(), Animation.ABSOLUTE, toY.toFloat()
        )
        tran.duration = (300L)
        tran.fillAfter = true;
        item?.startAnimation(tran)
    }

    /**
     * 拖拽结束
     */
    private fun onDragEnd() {
        val replaceView = if (dragInsertViewStack.isEmpty()) null else dragInsertViewStack.pop()
        if (dragSelectedView != null && replaceView != null) {
            val dragData = dragSelectedView?.getTag(R.id.cb_item_tag) as? EditLocation
            val replaceData = replaceView.getTag(R.id.cb_item_tag) as? EditLocation
            callEditLocationDataChange(dragData, replaceData)
        }
        clearDragUIState()
    }

    private fun callEditLocationDataChange(dragData: EditLocation?, replaceData: EditLocation?) {
        if (dragData == null || replaceData == null) {
            return;
        }
        val tempList = ArrayList<EditLocation>();
        val tempPoiList = ArrayList<EditLocation>();
        tempList.addAll(locationList)
        val dragIndex = tempList.indexOf(dragData)
        val replaceIndex = tempList.indexOf(replaceData);
        if (dragIndex != -1 && replaceIndex != -1 && dragIndex != replaceIndex) {
            tempList.removeAt(dragIndex);
            val insertIndex = tempList.indexOf(replaceData);
            if (insertIndex != -1) {
                val addIndex = if (dragIndex > replaceIndex) insertIndex else insertIndex + 1
                tempList.add(addIndex, dragData)
            }
            if (tempList.size == locationList.size) {
                for (item in tempList) {
                    val temp = EditLocation()
                    temp.poi = item.poi;
                    temp.editContent = item.editContent
                    tempPoiList.add(temp)
                }
                for (i in 0 until locationList.size) {
                    val item = locationList[i]
                    val tempData = tempPoiList[i]
                    item.poi = tempData.poi;
                    item.editContent = tempData.editContent
                    val tv = findLocationTextView(i)
                    updateLocationUI(item, tv)
                }
            }
        }
        tempList.clear()
        tempPoiList.clear()
    }

    private fun clearDragUIState() {
        dragSelectedView?.visibility = View.VISIBLE
        floatView.visibility = View.GONE
        dragSelectedView = null
        for (i in 0 until binding.llMoreEditLine.childCount) {
            binding.llMoreEditLine.getChildAt(i)?.clearAnimation()
        }
        dragInsertViewStack.clear()
    }

    private var yPos = 0f;

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        var dy = 0f
        val tempDragView = dragSelectedView;
        when (ev?.action) {
            MotionEvent.ACTION_DOWN -> {
                yPos = ev.y
                mainHandler.removeMessages(MSG_LONG_CLICK)
                val msg = mainHandler.obtainMessage(MSG_LONG_CLICK)
                msg.arg1 = ev.x.toInt()
                msg.arg2 = ev.y.toInt()
                msg.obj = Point(ev.rawX.toInt(), ev.rawY.toInt())
                mainHandler.sendMessageDelayed(msg, LONG_TIME)
            }
            MotionEvent.ACTION_MOVE -> {
                dy = ev.y - yPos;
                yPos = ev.y
                if (tempDragView != null && dy != 0f) {
                    mainHandler.removeMessages(MSG_LONG_CLICK)
                    onDragMove(tempDragView!!, ev.x.toInt(), ev.y.toInt(), dy)
                }
            }
            MotionEvent.ACTION_CANCEL -> {
                mainHandler.removeMessages(MSG_LONG_CLICK)
                if (tempDragView != null) {
                    onDragEnd()
                }
            }
            MotionEvent.ACTION_UP -> {
                mainHandler.removeMessages(MSG_LONG_CLICK)
                if (tempDragView != null) {
                    onDragEnd()
                }
            }
        }
        if (tempDragView != null) {
            return true
        }
        return super.dispatchTouchEvent(ev);
    }
}
kotlin 复制代码
package com.cw.widget.map

import android.app.Activity
import android.content.Context
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.text.Editable
import android.text.TextWatcher
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import com.blankj.utilcode.util.KeyboardUtils
import com.blankj.utilcode.util.ToastUtils
import com.cw.widget.R
import com.cw.widget.bean.POIType
import com.cw.widget.bean.SearchPOI
import com.cw.widget.databinding.EditSearchKeyBinding
import com.cw.widget.databinding.EditTripLocationListBinding

/**
 * 新建,编辑行程的编辑View
 */
open class EditTripLocationListView : FrameLayout {
    constructor(context: Context) : super(context) {}
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
    }

    constructor(
        context: Context,
        attrs: AttributeSet?,
        defStyleAttr: Int,
        defStyleRes: Int
    ) : super(context, attrs, defStyleAttr, defStyleRes) {
    }

    val binding: EditTripLocationListBinding

    val MAX_MIDDLE_NUM = 3;
    val locationList: ArrayList<EditLocation>
    private val startLocation: EditLocation
    private val endLocation: EditLocation

    var onEventListener: OnEventListener? = null

    /**
     * 文字变化忽略改动
     */
    var textChangeIgnoreTask: TextChangeIgnore? = null

    init {
        startLocation = EditLocation.createStartLocation(context)
        endLocation = EditLocation.createEndLocation()
        locationList = ArrayList<EditLocation>()
        locationList.add(startLocation)
        locationList.add(endLocation)
        binding = EditTripLocationListBinding.inflate(LayoutInflater.from(context))
        addView(binding.root, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
        binding.llStartLocation.ivEdt.setImageResource(R.drawable.start_location_point)
        binding.llStartLocation.edtInputKey.setText("")
        binding.llStartLocation.edtInputKey.hint = "请输入起点"
        binding.llStartLocation.edtInputKey.contentDescription = "起点"
        setUpEditViewKeyboard(binding.llStartLocation.edtInputKey)
        val startWatcher = EditTextWatcher(this, binding.llStartLocation.edtInputKey)
        binding.llStartLocation.edtInputKey.addTextChangedListener(startWatcher)
        binding.llStartLocation.root.setTag(R.id.cb_item_tag, startLocation)
        binding.llStartLocation.edtInputKey.setTag(R.id.cb_item_tag, startLocation)
        updateLocationUI(startLocation)
        binding.llEndLocation.ivEdt.setImageResource(R.drawable.end_location_point)
        binding.llEndLocation.root.setTag(R.id.cb_item_tag, endLocation)
        binding.llEndLocation.edtInputKey.setTag(R.id.cb_item_tag, endLocation)
        binding.llEndLocation.edtInputKey.contentDescription = "终点"
        setUpEditViewKeyboard(binding.llEndLocation.edtInputKey)
        val endWatcher = EditTextWatcher(this, binding.llEndLocation.edtInputKey)
        binding.llEndLocation.edtInputKey.addTextChangedListener(endWatcher)
        binding.llEndLocation.edtInputKey.setText("")
        binding.llEndLocation.edtInputKey.hint = "请输入终点"
        switchUIState(false)
        post {
            if (binding.llStartLocation.edtInputKey.text.isNullOrEmpty()) {
                binding.llStartLocation.edtInputKey.requestFocus()
            } else {
                binding.llEndLocation.edtInputKey.requestFocus()
            }
        }
        binding.ivAddLocation.setOnClickListener {
            val index = getMiddleCount()
            if (index < MAX_MIDDLE_NUM) {
                var addIndex = index
                (findFocusEditTextView()?.getTag(R.id.cb_item_tag) as? EditLocation)?.let {
                    val findIndex = locationList.indexOf(it)
                    if (findIndex != -1) {
                        addIndex = findIndex;
                    }
                }
                addMiddleLocation(addIndex)
                switchUIState(true)
            } else {
                ToastUtils.showShort("最多可添加${MAX_MIDDLE_NUM}个途经点")
            }
        }
        binding.rlMoreAdd.setOnClickListener {
            binding.ivAddLocation.performClick()
        }
        binding.llStartLocation.ivDelEdit.setOnClickListener {
            if (locationList.size > 2 &&
                locationList[1].locationType == EditLocation.TYPE_MIDDLE
            ) {
                startLocation.poi = locationList[1].poi
                removeMiddleLocation(0)
            } else {
                startLocation.poi = null
            }
            updateLocationUI(startLocation)
        }
        binding.llEndLocation.ivDelEdit.setOnClickListener {
            val index = locationList.size - 2
            if (locationList.size > 2 &&
                locationList[index].locationType == EditLocation.TYPE_MIDDLE
            ) {
                endLocation.poi = locationList[index].poi
                val middleIndex = getMiddleCount() - 1
                removeMiddleLocation(middleIndex)
            } else {
                endLocation.poi = null
            }
            updateLocationUI(endLocation)
        }
        binding.ivBackFinish.setOnClickListener { v ->
            onEventListener?.onCloseClick(v)
        }
        binding.ivLocationChangeMore.setOnClickListener {
            binding.ivSwitchLocation.performClick();
        }
        binding.ivSwitchLocation.setOnClickListener { v ->
            val temp = startLocation.poi
            val tempEditContent = startLocation.editContent
            startLocation.poi = endLocation.poi
            startLocation.editContent = endLocation.editContent
            endLocation.poi = temp
            endLocation.editContent = tempEditContent
            updateLocationUI(startLocation)
            updateLocationUI(endLocation)
            val start = 1;
            val end = locationList.lastIndex - 1
            //途经点交换次数
            val switchCount = locationList.size / 2 - 1
            for (i in 0 until switchCount) {
                val index = start + i;
                val switchIndex = end - i;
                if (switchIndex != index && switchIndex >= 0) {
                    val temp = locationList.getOrNull(index)?.poi
                    locationList.getOrNull(index)?.poi = locationList.getOrNull(switchIndex)?.poi
                    locationList.getOrNull(switchIndex)?.poi = temp;
                    locationList.getOrNull(index)?.let {
                        updateLocationUI(it, findMidLocationTextView(index))
                    }
                    locationList.getOrNull(switchIndex)?.let {
                        updateLocationUI(it, findMidLocationTextView(switchIndex))
                    }
                }
            }
            findFirstNoneEditTextView()?.let {
                if (!it.text.isNullOrEmpty()) {
                    callOnTextChanged(it, it.text)
                }
                requestFocus(it, true)
            }
        }
        binding.btnEditLocationComplete.setOnClickListener {
            val dataList = ArrayList<EditLocation>()
            for (item in locationList) {
                if (item.poi != null) {
                    dataList.add(item)
                }
            }
            if (startLocation.poi == null && !startLocation.editContent.isNullOrEmpty()) {
                ToastUtils.showShort("请选择起点")
                requestFocus(binding.llStartLocation.edtInputKey)
                return@setOnClickListener
            }
            if (endLocation.poi == null && !endLocation.editContent.isNullOrEmpty()) {
                ToastUtils.showShort("请选择终点")
                requestFocus(binding.llEndLocation.edtInputKey)
                return@setOnClickListener
            }
            if (dataList.size < 2) {
                if (startLocation?.poi == null) {
                    ToastUtils.showShort("请选择起点")
                    requestFocus(binding.llStartLocation.edtInputKey)
                } else if (endLocation?.poi == null) {
                    ToastUtils.showShort("请选择终点")
                    requestFocus(binding.llEndLocation.edtInputKey)
                }
                return@setOnClickListener
            }

            if (getMiddleCount() + 2 != dataList.size) {
                for (i in 0 until getMiddleCount()) {
                    val editInText = !findMidLocationTextView(i + 1)?.text.isNullOrEmpty()
                    val poiNoSelected = locationList.getOrNull(i + 1)?.poi == null
                    if (editInText && poiNoSelected) {
                        ToastUtils.showShort("请选择第${i + 1}个途经点")
                        return@setOnClickListener
                    }
                }
            }
            val resultList = ArrayList<EditLocation>()
            var latestData: EditLocation? = null
            for (data in dataList) {
                if (data.poi?.poiName != latestData?.poi?.poiName ||
                    data.poi?.poiType != latestData?.poi?.poiType
                ) {
                    resultList.add(data)
                }
                latestData = data
            }
            if (resultList.size == 2) {
                if (isSamePOI(resultList[0].poi, resultList[1].poi)) {
                    ToastUtils.showShort("起点与终点不能相同")
                    return@setOnClickListener
                }
            } else if (resultList.size < 2) {
                ToastUtils.showShort("起点与终点不能相同")
                return@setOnClickListener
            }
            hideSoftInput()
            onEventListener?.onLocationEditComplete(resultList)
        }
    }

    /**
     * 获取途经点个数
     */
    protected fun getMiddleCount(): Int {
        val allCount = binding.llMoreEditLine.childCount - 2
        if (allCount >= 0) {
            return allCount;
        }
        return 0;
    }

    private fun isSamePOI(poi1: SearchPOI?, poi2: SearchPOI?): Boolean {
        return poi1 == poi2 || poi1?.poiName == poi2?.poiName ||
                (poi1?.poiType == POIType.userPosition && POIType.userPosition == poi2?.poiType)
                || (poi1?.poiType == POIType.carPosition && POIType.carPosition == poi2?.poiType)
    }

    private fun requestFocus(view: TextView, isSelectedText: Boolean = false) {
        post {
            view.requestFocus()
            if (view is EditText && isSelectedText) {
                val length = view.text.length;
                (view as EditText).setSelection(0, length)
                KeyboardUtils.showSoftInput(view)
            }
        }
    }

    fun setLocationList(list: List<EditLocation>) {
        list.getOrNull(0)?.let {
            startLocation.poi = it.poi
            updateLocationUI(startLocation)
        }
        list.getOrNull(list.lastIndex)?.let {
            endLocation.poi = it.poi
            updateLocationUI(endLocation)
        }
        locationList.clear()
        locationList.add(startLocation)
        locationList.add(endLocation)
        removeAllMiddleView()
        switchUIState(list.size > 2)
        for (i in 1 until list.lastIndex) {
            val v = addMiddleLocation(i - 1)
            val item = list[i]
            locationList[i].poi = item.poi
            updateLocationUI(locationList[i], v)
        }
    }

    private fun removeAllMiddleView() {
        var hasCount = binding.llMoreEditLine.childCount
        while (hasCount > 2) {
            val index = hasCount - 1 - 1;
            binding.llMoreEditLine.getChildAt(index)?.let { v ->
                binding.llMoreEditLine.removeView(v)
            }
            hasCount = binding.llMoreEditLine.childCount
        }

    }

    fun setIndexFocus(focusIndex: Int) {
        locationList.getOrNull(focusIndex)?.let {
            if (it.locationType == EditLocation.TYPE_START) {
                requestFocus(binding.llStartLocation.edtInputKey, true)
            } else if (it.locationType == EditLocation.TYPE_END) {
                requestFocus(binding.llEndLocation.edtInputKey, true)
            } else {
                getMiddleItemViewAt(focusIndex - 1)
                    ?.findViewById<EditText>(R.id.edt_input_key)
                    ?.let { v ->
                        requestFocus(v, true)
                    }
            }
        }
    }

    /**
     * @param middleIndex 从0开始,排除起点和终点之外的列表
     */
    protected fun getMiddleItemViewAt(middleIndex: Int): View? {
        return binding.llMoreEditLine.getChildAt(middleIndex + 1)
    }

    fun showSoftInput() {
        if (context is Activity) {
            KeyboardUtils.showSoftInput(context as Activity)
        } else {
            KeyboardUtils.showSoftInput();
        }
    }

    fun showSoftInput(editText: View) {
        KeyboardUtils.showSoftInput(editText)
    }

    fun hideSoftInput() {
        if (context is Activity) {
            KeyboardUtils.hideSoftInput(context as Activity)
        } else {
            KeyboardUtils.hideSoftInput(this)
        }
    }

    /**
     * 设置POI ; 有光标时就填充对应的项;没有光标时优先填充未填写项,顺序:终点、起点、途经点
     */
    fun setPoi(poi: SearchPOI) {
        val v = findFocusEditTextView()
        if (v != null) {
            val tagData = v.getTag(R.id.cb_item_tag)
            if (tagData is EditLocation) {
                tagData.poi = poi
                updateLocationUI(tagData, v)
            }
        } else if (endLocation.poi == null) {
            endLocation.poi = poi
            updateLocationUI(endLocation)
        } else if (startLocation.poi == null) {
            startLocation.poi = poi
            updateLocationUI(startLocation)
        } else {
            val index = findNonePoiMiddleLocation()
            if (index <= 0) {
                endLocation.poi = poi
                updateLocationUI(endLocation)
            } else {
                locationList[index].poi = poi
                getMiddleItemViewAt(index - 1)
                    ?.findViewById<EditText>(R.id.edt_input_key)
                    ?.let { v ->
                        updateLocationUI(locationList[index], v)
                    }
            }
        }
        //光标自动聚焦到下一个空的输入框
        findFirstNoneEditTextView()?.let {
            requestFocus(it)
            callOnTextChanged(it, it.text)
        } ?: let {
            callOnTextChanged(null, "")
            hideSoftInput()

        }
        //无途经点
        if (getMiddleCount() <= 0) {
            if (startLocation.poi != null && endLocation.poi != null) {
                if (isSamePOI(startLocation.poi, endLocation.poi)) {
                    ToastUtils.showShort("起点与终点不能相同")
                } else {
                    val resultList = ArrayList<EditLocation>();
                    resultList.add(startLocation)
                    resultList.add(endLocation)
                    hideSoftInput()
                    onEventListener?.onLocationEditComplete(resultList)
                }

            }

        }
    }

    private fun setUpEditViewKeyboard(editText: EditText?) {
        editText?.imeOptions = EditorInfo.IME_ACTION_SEARCH
        editText?.setOnEditorActionListener { v, actionId, event ->
            val isClick = event?.keyCode == KeyEvent.KEYCODE_ENTER;
            if (actionId == EditorInfo.IME_ACTION_SEARCH || isClick) {
                onEventListener?.onEditTextChange(v as? EditText, v.text)
                return@setOnEditorActionListener true;
            }
            return@setOnEditorActionListener false;
        }
        editText?.isLongClickable = false;
        editText?.setOnFocusChangeListener { v, hasFocus ->
            if (hasFocus) {
                (v as? EditText)?.let { edit ->
                    edit.postDelayed({
                        edit.setSelection(0, edit.text?.length ?: 0)
                    }, 100);
                }
            }
        }
        editText?.setOnClickListener { v ->
            (v as? EditText)?.let { edit ->
                edit.setSelection(0, edit.text?.length ?: 0)
            }
        }
    }


    private fun findNonePoiMiddleLocation(): Int {
        for (index in 1..locationList.size - 2) {
            val item = locationList[index]
            if (item.poi == null) {
                return index
            }
        }
        return -1;
    }

    private fun findFirstNoneEditTextView(): EditText? {
        if (startLocation.poi == null) {
            return binding.llStartLocation.edtInputKey
        }
        if (endLocation.poi == null) {
            return binding.llEndLocation.edtInputKey
        }
        for (i in 0 until getMiddleCount()) {
            val itemView = getMiddleItemViewAt(i)
            val editText = itemView?.findViewById<EditText>(R.id.edt_input_key)
            if (editText != null && (editText.getTag(R.id.cb_item_tag) as? EditLocation)?.poi == null) {
                return editText
            }
        }

        return null
    }

    private fun findFocusEditTextView(): EditText? {
        if (binding.llStartLocation.edtInputKey.hasFocus()) {
            return binding.llStartLocation.edtInputKey
        }
        if (binding.llEndLocation.edtInputKey.hasFocus()) {
            return binding.llEndLocation.edtInputKey
        }
        for (i in 0 until getMiddleCount()) {
            val itemView = getMiddleItemViewAt(i)
            val editText = itemView?.findViewById<EditText>(R.id.edt_input_key)
            if (editText != null && editText.hasFocus()) {
                return editText
            }
        }
        return null
    }

    private fun findMidLocationTextView(index: Int): EditText? {
        val middleIndex = index - 1;
        val itemView = getMiddleItemViewAt(middleIndex)
        return itemView?.findViewById<EditText>(R.id.edt_input_key)
    }

    protected fun findLocationTextView(index: Int): EditText? {
        val itemView = binding.llMoreEditLine.getChildAt(index)
        return itemView?.findViewById<EditText>(R.id.edt_input_key)
    }

    protected fun updateLocationUI(location: EditLocation, textView: TextView? = null) {
        var addressName: String = location.poi?.poiName ?: ""
        if (location.poi?.poiType == POIType.userPosition) {
            addressName = "我的位置"
        } else if (location.poi?.poiType == POIType.carPosition) {
            addressName = "车辆位置"
        }
        var editView: TextView? = null
        if (location.locationType == EditLocation.TYPE_START) {
            editView = binding.llStartLocation.edtInputKey;
        } else if (location.locationType == EditLocation.TYPE_END) {
            editView = binding.llEndLocation.edtInputKey
        } else {
            editView = textView
        }
        if (editView != null && !addressName.isNullOrEmpty()) {
            textChangeIgnoreTask = TextChangeIgnore(editView, addressName)
        }
        val showText = addressName.ifEmpty { (location.editContent ?: "") }
        editView?.text = showText
        editView?.text?.length?.let { l ->
            (editView as? EditText)?.setSelection(l)
        }
    }

    private fun setViewMargin(v: View, left: Int, top: Int, right: Int, bottom: Int) {
        v.layoutParams?.let {
            val p = it as MarginLayoutParams
            if (p.leftMargin != left ||
                p.topMargin != top ||
                p.rightMargin != right ||
                p.bottomMargin != bottom
            ) {
                p.setMargins(left, top, right, bottom)
                v.layoutParams = p
            }
        }
    }

    private fun switchUIState(isHasMiddleLocation: Boolean, middleCount: Int = 1) {
        if (isHasMiddleLocation) {
            binding.rlEditLocationContainer.setBackgroundResource(R.drawable.bg_transparent)
            binding.llStartLocation.llEditContent.setBackgroundResource(R.drawable.bg_edit_box)
            binding.llEndLocation.llEditContent.setBackgroundResource(R.drawable.bg_edit_box)
            binding.llStartLocation.ivDelEdit.visibility =
                if (middleCount == 0) View.GONE else View.VISIBLE
            binding.llEndLocation.ivDelEdit.visibility =
                if (middleCount == 0) View.GONE else View.VISIBLE
            binding.llEditAction.visibility = View.GONE
            binding.llStartLocation.ivEditMoveTag.visibility = View.VISIBLE
            binding.llEndLocation.ivEditMoveTag.visibility = View.VISIBLE
            binding.ivLocationChangeMore.visibility = View.VISIBLE
            binding.rlMoreAddLocation.visibility = View.VISIBLE
            binding.twoLineSplit.visibility = View.GONE
            setViewMargin(
                binding.llEndLocation.root, 0, resources.getDimensionPixelSize(R.dimen.dp_6),
                0, 0
            )
        } else {
            binding.rlEditLocationContainer.setBackgroundResource(R.drawable.bg_edit_box)
            binding.llStartLocation.llEditContent.setBackgroundResource(R.drawable.bg_transparent)
            binding.llEndLocation.llEditContent.setBackgroundResource(R.drawable.bg_transparent)
            binding.llStartLocation.ivDelEdit.visibility = View.GONE
            binding.llEndLocation.ivDelEdit.visibility = View.GONE
            binding.llStartLocation.ivEditMoveTag.visibility = View.GONE
            binding.llEndLocation.ivEditMoveTag.visibility = View.GONE
            binding.llEditAction.visibility = View.VISIBLE
            binding.ivLocationChangeMore.visibility = View.GONE
            binding.rlMoreAddLocation.visibility = View.GONE
            removeAllMiddleView()
            binding.twoLineSplit.visibility = View.VISIBLE
            setViewMargin(
                binding.llEndLocation.root, 0, 0,
                0, 0
            )
        }
    }

    /**
     * @param middleIndex 从0开始,排除起点和终点之外的列表
     */
    private fun addMiddleLocation(middleIndex: Int = 0): EditText {
        val params = LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
        val top: Int = resources.getDimensionPixelSize(R.dimen.dp_6)
        val bottom = 0
        params.topMargin = top
        params.bottomMargin = bottom
        val middleViewBinding = createMiddleLocationEditView();
        var middlePos: Int = middleIndex
        if (getMiddleCount() > middleIndex) {
            binding.llMoreEditLine.addView(middleViewBinding.root, middleIndex + 1, params)
        } else {
            val index = binding.llMoreEditLine.childCount - 1;
            binding.llMoreEditLine.addView(middleViewBinding.root, index, params)
            middlePos = getMiddleCount() - 1;
        }
        val middleData = EditLocation.createMiddleLocation()
        middleViewBinding.root.setTag(R.id.cb_item_tag, middleData)
        middleViewBinding.edtInputKey.setTag(R.id.cb_item_tag, middleData)
        locationList.add(middlePos + 1, middleData)
        updateAddMoreUIState()
        post {
            middleViewBinding.edtInputKey.requestFocus()
        }
        return middleViewBinding.edtInputKey
    }

    private fun updateAddMoreUIState() {
        val couldAddSize = MAX_MIDDLE_NUM - getMiddleCount()
        binding.tvAddNum.text = if (couldAddSize <= 0) "已到达上限" else "还可添加${couldAddSize}个"
        if (couldAddSize <= 0) {
            binding.rlMoreAdd.tag = false
            setViewColorFilter(binding.rlMoreAdd, 0.5f)
        } else {
            binding.rlMoreAdd.tag = true;
            setViewColorFilter(binding.rlMoreAdd, 1f)
        }
    }

    private fun setViewColorFilter(view: View, alphaScale: Float) {
        val paint = Paint()
        val cm = ColorMatrix()
        cm.setScale(1f, 1f, 1f, alphaScale)
        paint.colorFilter = ColorMatrixColorFilter(cm)
        view.setLayerType(View.LAYER_TYPE_HARDWARE, paint)
    }

    /**
     * @param middleIndex 从0开始,排除起点和终点之外的列表
     */
    private fun removeMiddleLocation(middleIndex: Int) {
        getMiddleItemViewAt(middleIndex)?.let { child ->
            binding.llMoreEditLine.removeView(child)
            (child.getTag(R.id.cb_item_tag) as? EditLocation).let { data ->
                locationList.remove(data)
            }
        }
        /*val index = middleIndex + 1
        val location = locationList.getOrNull(index)
        if (location != null && location.locationType == EditLocation.TYPE_MIDDLE) {
            locationList.removeAt(index)
        }*/
        val count = getMiddleCount()
        switchUIState(true, count)
        updateAddMoreUIState()
    }

    private fun createMiddleLocationEditView(): EditSearchKeyBinding {
        val tempBinding = EditSearchKeyBinding.inflate(LayoutInflater.from(context))
        val itemView: View = tempBinding.root
        tempBinding.ivEdt.setImageResource(R.drawable.middle_location_point)
        tempBinding.llEditContent.setBackgroundResource(R.drawable.bg_edit_box)
        tempBinding.edtInputKey.setText("")
        tempBinding.edtInputKey.hint = "请输入途经点"
        setUpEditViewKeyboard(tempBinding.edtInputKey)
        val middleWatcher = EditTextWatcher(this, tempBinding.edtInputKey)
        tempBinding.edtInputKey.addTextChangedListener(middleWatcher)
        tempBinding.ivEditMoveTag.visibility = View.VISIBLE
        tempBinding.ivDelEdit.visibility = View.VISIBLE
        tempBinding.ivDelEdit.setTag(itemView)
        tempBinding.ivDelEdit.setOnClickListener { v ->
            if (v.tag != null && v.tag is View) {
                val index = binding.llMoreEditLine.indexOfChild(v.tag as View) - 1
                removeMiddleLocation(index)
            }
        }
        return tempBinding
    }

    private fun callOnTextChanged(editText: EditText?, text: CharSequence?) {
        if (textChangeIgnoreTask?.isIgnore(editText, text?.toString() ?: "") != true) {
            (editText?.getTag(R.id.cb_item_tag) as? EditLocation)?.let {
                it.editContent = text?.toString();
                it.poi = null;
            }
            onEventListener?.onEditTextChange(editText, text)
        }
        textChangeIgnoreTask = null
    }

    interface OnEventListener {
        fun onEditTextChange(editText: EditText?, text: CharSequence?)

        fun onCloseClick(v: View)

        fun onLocationEditComplete(locationList: List<EditLocation>)
    }

    class TextChangeIgnore(val tagView: View, val ignoreText: String) {
        fun isIgnore(changeView: View?, changeText: String): Boolean {
            return tagView == changeView && changeText == ignoreText;
        }
    }

    class EditTextWatcher(val v: EditTripLocationListView, val editText: EditText) : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        }

        override fun afterTextChanged(s: Editable?) {
            var inputText = s?.toString() ?: ""
            if (inputText.length > 200) {
                inputText = inputText.substring(0, 200)
                editText.setText(inputText)
                val selection = editText.text.length
                editText.setSelection(selection)
            }
            v.callOnTextChanged(editText, inputText)
        }

    }
}

class EditLocation {
    companion object {
        const val TYPE_START = 0;
        const val TYPE_MIDDLE = 1;
        const val TYPE_END = 2;

        fun createStartLocation(context: Context): EditLocation {
            val location = EditLocation()
            location.locationType = TYPE_START
            val userPoi = SearchPOI()
            userPoi.poiType = POIType.userPosition;
            location.poi = userPoi;
            return location
        }

        fun createMiddleLocation(): EditLocation {
            val location = EditLocation()
            location.locationType = TYPE_MIDDLE
            return location
        }

        fun createEndLocation(): EditLocation {
            val location = EditLocation()
            location.locationType = TYPE_END
            return location
        }
    }

    var locationType: Int = TYPE_START

    var poi: SearchPOI? = null

    /**
     * 编辑的文字
     */
    var editContent: String? = null;
}

最终的效果:

相关推荐
枯骨成佛1 小时前
Android中Crash Debug技巧
android
kim56596 小时前
android studio 更改gradle版本方法(备忘)
android·ide·gradle·android studio
咸芝麻鱼6 小时前
Android Studio | 最新版本配置要求高,JDK运行环境不适配,导致无法启动App
android·ide·android studio
无所谓จุ๊บ6 小时前
Android Studio使用c++编写
android·c++
csucoderlee7 小时前
Android Studio的新界面New UI,怎么切换回老界面
android·ui·android studio
kim56597 小时前
各版本android studio下载地址
android·ide·android studio
饮啦冰美式7 小时前
Android Studio 将项目打包成apk文件
android·ide·android studio
夜色。7 小时前
Unity6 + Android Studio 开发环境搭建【备忘】
android·unity·android studio
ROCKY_8178 小时前
AndroidStudio-滚动视图ScrollView
android
趴菜小玩家9 小时前
使用 Gradle 插件优化 Flutter Android 插件开发中的 Flutter 依赖缺失问题
android·flutter·gradle