Android | 格子拖拽填充效果

前言

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

device-2022-09-22-161816 (1)

分析

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

实现

@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 && y > gridLayout.y && x < (gridLayout.width + gridLayout.x) && 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 && y > gridLayout.y && x < (gridLayout.width + gridLayout.x) && 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 && y >= 0 && x <= right && y <= bottom) {
            view.x = x
            view.y = y
            return
        }
        //-------------- 拖动边界判断
        if (x < 0 && y < 0) { //左上角
            view.x = 0f
            view.y = 0f
        } else if (x < 0 && y > bottom) { //左下角
            view.x = 0f
            view.y = bottom.toFloat()
        } else if (x > right && y < 0) { //右上角
            view.x = right.toFloat()
            view.y = 0f
        } else if (x > right && y > bottom) { //右下角
            view.x = right.toFloat()
            view.y = bottom.toFloat()
        } else if ((x > 0 && x < right) && y < 0) { // y越上界
            view.x = x
            view.y = 0f
        } else if ((x > 0 && x < right) && y > bottom) { // y越下界
            view.x = x
            view.y = bottom.toFloat()
        } else if ((y > 0 && y < bottom) && x < 0) { // x越左界
            view.x = 0f
            view.y = y
        } else if ((y > 0 && y < bottom) && 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 && 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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tʀᴜsᴛ³⁴⁵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值