最近遇到一个需求,需要实现一个格子填充的效果,具体效果如下所示:
@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
}
}
具体的注释代码中都有,最后贴一下源码地址