Android | 自定义格子拖拽填充效果

2022-10-05 16:55:17 浏览数 (1)

前言

最近遇到一个需求,需要实现一个格子填充的效果,具体效果如下所示:

分析

  • 格子的拖动效果
  • 整个 View 的边界判断
  • 二维网格边界的判断
  • 拖动后格子填充时的位置判断
  • 网格的绘制
  • 填充后进行复位

实现

代码语言:javascript复制
@SuppressLint("ClickableViewAccessibility")
class DragGridGroupView : FrameLayout {

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context, attrs, defStyleAttr
    ) {
        init()
    }

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

    //二维格子
    lateinit var array: Array<Array<View>>
    private val gridRowCount = 15 //行数量
    private val gridColumnCount = 10 //列数量

    //上一个  iew
    private var preView: View? = null

    //初始位置
    private val initPoint: Point = Point(dpToPx(30f), dpToPx(50f))

    //格子大小
    private val sizePoint: Point = Point(dpToPx(30f), dpToPx(30f))

    //按下位置
    private val downPoint: PointF = PointF(0f, 0f)

    //View 位置
    private val viewPoint: PointF = PointF(0f, 0f)

    //是否为按下状态
    private var isDown = false

    //默认格子颜色
    private val defaultGridColor = Color.parseColor("#D8D5D7")

    //滑动时选中的颜色
    private val scrollSelectColor = Color.parseColor("#A6D8A9")

    //选中的颜色
    private val selectColor = Color.parseColor("#4FD855")

    private val paint by lazy {
        Paint().apply {
            color = Color.GRAY
            strokeWidth = dpToPx(1f).toFloat()
            style = Paint.Style.STROKE
            pathEffect = dashPathEffect
        }
    }
    private val path = Path()

    private val dashLength = dpToPx(3f).toFloat()
    private val dashPathEffect by lazy {
        DashPathEffect(
            floatArrayOf(dashLength, dashLength), dashLength
        )
    }

    val view by lazy {
        View(context).apply {
            val layoutParams = LayoutParams(sizePoint.x, sizePoint.y)
            layoutParams.marginStart = initPoint.x
            layoutParams.topMargin = initPoint.y
            this.layoutParams = layoutParams
            setBackgroundColor(selectColor)
        }
    }

    private val gridLayout by lazy {
        NotTouchGridLayout(context).apply {
            val layoutParams =
                LayoutParams(sizePoint.x * gridColumnCount, sizePoint.y * gridRowCount)
            layoutParams.marginStart = initPoint.x
            layoutParams.topMargin = initPoint.y   (sizePoint.y * 2)

            this.layoutParams = layoutParams
            setBackgroundColor(Color.BLUE)
            orientation = GridLayout.HORIZONTAL
            columnCount = gridColumnCount
            rowCount = gridRowCount
        }
    }

    private fun init() {
        addView(gridLayout)
        addView(view)
        initData()
        initDrag()
        setWillNotDraw(false)
    }

    private fun initData() {
        array = Array(gridRowCount, init = {
            Array(gridColumnCount, init = {
                View(context).apply {
                    val layoutParams = LayoutParams(dpToPx(30f), dpToPx(30f))
                    this.layoutParams = layoutParams
                    setBackgroundColor(defaultGridColor)
                }
            })
        })
        for (i in array.indices) {
            for (j in 0 until array[i].size) {
                val v = array[i][j]
                gridLayout.addView(v)
            }
        }
    }


    private fun initDrag() {

    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                isDown = true
                invalidate()
                downPoint.x = event.x
                downPoint.y = event.y
                viewPoint.x = view.x
                viewPoint.y = view.y
            }
            MotionEvent.ACTION_MOVE -> {
                val x = (event.x - downPoint.x)   viewPoint.x
                val y = (event.y - downPoint.y)   viewPoint.y
                move(x, y)
                if (x > gridLayout.x &amp;&amp; y > gridLayout.y &amp;&amp; x < (gridLayout.width   gridLayout.x) &amp;&amp; y < (gridLayout.height   gridLayout.y)) {
                    fillingGrid(x, y)
                }
            }
            MotionEvent.ACTION_UP -> {
                isDown = false
                invalidate()
                val x = (event.x - downPoint.x)   viewPoint.x
                val y = (event.y - downPoint.y)   viewPoint.y
                preView?.let {
                    if (x > gridLayout.x &amp;&amp; y > gridLayout.y &amp;&amp; x < (gridLayout.width   gridLayout.x) &amp;&amp; y < (gridLayout.height   gridLayout.y)) {
                        it.tag = 1
                        setBgColor(it, scrollSelectColor, selectColor)
                    } else {
                        it.tag = 0
                        setBgColor(it, scrollSelectColor, defaultGridColor)
                    }
                }
                val animate = view.animate()
                animate.x(initPoint.x.toFloat())
                animate.y(initPoint.y.toFloat())
                animate.start()
                preView = null
            }

        }

        return true;
    }

    private fun setBgColor(view: View, vararg color: Int) {
        val colorAnim = ObjectAnimator.ofInt(view, "backgroundColor", *color)
        colorAnim.duration = 500
        colorAnim.setEvaluator(ArgbEvaluator())
        colorAnim.start()
    }

    private fun move(x: Float, y: Float) {
        //计算右边和下边边界
        val right = (width - sizePoint.x)
        val bottom = (height - sizePoint.y)
        //内部直接滑动
        if (x >= 0 &amp;&amp; y >= 0 &amp;&amp; x <= right &amp;&amp; y <= bottom) {
            view.x = x
            view.y = y
            return
        }
        //-------------- 拖动边界判断
        if (x < 0 &amp;&amp; y < 0) { //左上角
            view.x = 0f
            view.y = 0f
        } else if (x < 0 &amp;&amp; y > bottom) { //左下角
            view.x = 0f
            view.y = bottom.toFloat()
        } else if (x > right &amp;&amp; y < 0) { //右上角
            view.x = right.toFloat()
            view.y = 0f
        } else if (x > right &amp;&amp; y > bottom) { //右下角
            view.x = right.toFloat()
            view.y = bottom.toFloat()
        } else if ((x > 0 &amp;&amp; x < right) &amp;&amp; y < 0) { // y越上界
            view.x = x
            view.y = 0f
        } else if ((x > 0 &amp;&amp; x < right) &amp;&amp; y > bottom) { // y越下界
            view.x = x
            view.y = bottom.toFloat()
        } else if ((y > 0 &amp;&amp; y < bottom) &amp;&amp; x < 0) { // x越左界
            view.x = 0f
            view.y = y
        } else if ((y > 0 &amp;&amp; y < bottom) &amp;&amp; x > right) { // x越右界
            view.x = right.toFloat()
            view.y = y
        }
    }

    private fun fillingGrid(x: Float, y: Float) {
        //计算框内位置,  格子的一半,等于中心点距离边上的位置
        val nx = x - gridLayout.x   (sizePoint.x / 2)
        val ny = y - gridLayout.y   (sizePoint.y / 2)

        //计算索引位置
        val i = (nx / sizePoint.x).toInt()
        val j = (ny / sizePoint.y).toInt()

        if (j >= gridRowCount || i >= gridColumnCount) return

        val v = array[j][i]
        if (preView != null &amp;&amp; preView == v) {
            return
        }
        if (v.tag == 1) {
            return
        }
        preView?.run {
            tag = 0
            setBgColor(this, scrollSelectColor, defaultGridColor)
        }
        preView = v
        v.tag = 1
        setBgColor(v, defaultGridColor, scrollSelectColor)
    }

    /** 等别的 view 绘制完成后,在进行绘制,否则会被覆盖 */
    override fun dispatchDraw(canvas: Canvas?) {
        super.dispatchDraw(canvas)
        if (!isDown) return
        val initX = initPoint.x.toFloat()
        val initY = initPoint.y   (sizePoint.y * 2).toFloat()
        val gridWidth = gridLayout.width
        val gridHeight = gridLayout.height
        for (i in 0..gridRowCount) {
            val y = (sizePoint.y * i)   initY
            path.moveTo(initX, y)
            path.lineTo(initX   gridWidth, y)
            canvas?.drawPath(path, paint)
            path.reset()
        }
        for (i in 0..gridColumnCount) {
            val x = (sizePoint.x * i)   initX
            path.moveTo(x, initY)
            path.lineTo(x, initY   gridHeight)
            canvas?.drawPath(path, paint)
            path.reset()
        }

    }

    private fun dpToPx(dpValue: Float): Int {
        val scale: Float = resources.displayMetrics.density
        return (dpValue * scale   0.5f).toInt()
    }

}

class NotTouchGridLayout : GridLayout {

    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)

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        return true
    }
}

最后

具体的注释代码中都有,最后贴一下源码地址

0 人点赞