Android绘图工具Kotlin版Flood Fill(泛洪填充)实现求助
解决Android绘图工具的漫水填充(Flood Fill)问题
我明白你现在的困境——Path.FillType确实不是用来实现「油漆桶式」漫水填充的,它只负责闭合路径内部的填充规则,和你需要的基于像素颜色的区域填充完全是两回事。下面我给你提供Kotlin版本的Flood Fill实现,直接适配你的DrawingView代码。
第一步:修改DrawingView的成员变量
首先添加填充模式的标识和填充颜色的存储:
class DrawingView(context: Context, attrs: AttributeSet) : View(context, attrs) { // ... 原有成员变量 ... private var isFillMode = false // 标记是否处于填充模式 private var fillColor = Color.BLACK // 用户选择的填充颜色 // ... 原有代码 ... }
第二步:实现核心漫水填充算法
这里用迭代式队列实现(避免递归导致的栈溢出问题),处理Bitmap的像素替换:
// 漫水填充核心方法 private fun floodFill(x: Int, y: Int, targetColor: Int, replacementColor: Int) { val bitmap = mCanvasBitMap ?: return // 确保Bitmap是可修改的,不可修改则复制一份 val mutableBitmap = if (bitmap.isMutable) bitmap else bitmap.copy(Bitmap.Config.ARGB_8888, true) val width = mutableBitmap.width val height = mutableBitmap.height // 边界检查:点击点超出Bitmap范围,或目标颜色和填充色一致则直接返回 if (x < 0 || x >= width || y < 0 || y >= height) return if (targetColor == replacementColor) return // 用队列存储待处理的像素点 val pixelQueue = LinkedList<Pair<Int, Int>>() pixelQueue.add(Pair(x, y)) mutableBitmap.setPixel(x, y, replacementColor) // 遍历队列,处理每个像素的上下左右四个方向 while (pixelQueue.isNotEmpty()) { val (currentX, currentY) = pixelQueue.removeFirst() // 上方像素 if (currentY > 0 && mutableBitmap.getPixel(currentX, currentY - 1) == targetColor) { mutableBitmap.setPixel(currentX, currentY - 1, replacementColor) pixelQueue.add(Pair(currentX, currentY - 1)) } // 下方像素 if (currentY < height - 1 && mutableBitmap.getPixel(currentX, currentY + 1) == targetColor) { mutableBitmap.setPixel(currentX, currentY + 1, replacementColor) pixelQueue.add(Pair(currentX, currentY + 1)) } // 左方像素 if (currentX > 0 && mutableBitmap.getPixel(currentX - 1, currentY) == targetColor) { mutableBitmap.setPixel(currentX - 1, currentY, replacementColor) pixelQueue.add(Pair(currentX - 1, currentY)) } // 右方像素 if (currentX < width - 1 && mutableBitmap.getPixel(currentX + 1, currentY) == targetColor) { mutableBitmap.setPixel(currentX + 1, currentY, replacementColor) pixelQueue.add(Pair(currentX + 1, currentY)) } } // 更新画布Bitmap并刷新视图 mCanvasBitMap = mutableBitmap invalidate() }
第三步:修改触摸事件逻辑
在触摸事件中判断是否处于填充模式,点击时执行填充:
override fun onTouchEvent(event: MotionEvent?): Boolean { val touchX = event?.x ?: return false val touchY = event?.y ?: return false // 如果是填充模式,处理点击填充 if (isFillMode) { if (event.action == MotionEvent.ACTION_UP) { // 将View触摸坐标转换为Bitmap像素坐标(适配View和Bitmap尺寸差异) val scaleX = mCanvasBitMap?.width?.toFloat() ?: width.toFloat() / width.toFloat() val scaleY = mCanvasBitMap?.height?.toFloat() ?: height.toFloat() / height.toFloat() val bitmapX = (touchX * scaleX).toInt() val bitmapY = (touchY * scaleY).toInt() // 获取点击点的原始颜色,执行填充 val targetColor = mCanvasBitMap?.getPixel(bitmapX, bitmapY) ?: Color.TRANSPARENT floodFill(bitmapX, bitmapY, targetColor, fillColor) } return true } // 原有绘图逻辑保持不变 when(event.action){ MotionEvent.ACTION_DOWN -> { mDrawPath!!.color = color mDrawPath!!.brushThickness = mBrushSize mDrawPath!!.reset() mDrawPath!!.moveTo(touchX, touchY) } MotionEvent.ACTION_MOVE -> { mDrawPath!!.lineTo(touchX, touchY) } MotionEvent.ACTION_UP -> { mPaths.add(mDrawPath!!) mDrawPath = CustomPath(color, mBrushSize) } else -> return false } invalidate() return true }
第四步:添加模式切换和初始化优化
添加切换填充模式的方法,同时确保初始化时Bitmap是可修改的:
// 外部调用:切换填充模式,传入是否启用和填充颜色 fun toggleFillMode(enable: Boolean, selectedColor: Int) { isFillMode = enable fillColor = selectedColor } // 重写onSizeChanged,初始化可修改的画布Bitmap override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) mCanvasBitMap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) canvas = Canvas(mCanvasBitMap!!) }
注意事项
- 坐标适配:如果你的View存在缩放、裁剪等情况,需要调整坐标转换逻辑,确保触摸点准确对应Bitmap的像素位置。
- 颜色精度:如果Bitmap使用带alpha通道的格式(如ARGB_8888),
getPixel会返回包含alpha的完整颜色值,判断时要完整比较ARGB,避免半透明颜色的误判。 - 性能优化:大尺寸Bitmap的填充可能耗时,建议在子线程执行填充逻辑,完成后回到主线程刷新视图(注意Bitmap的线程安全)。
内容的提问来源于stack exchange,提问作者Zomb991
相关产品推荐
相关产品推荐

