前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android | 自定义格子拖拽填充效果

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

作者头像
345
发布2022-10-05 16:55:17
4600
发布2022-10-05 16:55:17
举报
文章被收录于专栏:用户4077185的专栏

前言

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

分析

  • 格子的拖动效果
  • 整个 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
    }
}

最后

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 分析
  • 实现
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档