• Skip to primary navigation
  • Skip to main content
  • Skip to primary sidebar

陈文管的博客

分享有价值的内容

  • Android
  • Affiliate
  • SEO
  • 前后端
  • 网站建设
  • 自动化
  • 开发资源
  • 关于

Android不规则图形高斯模糊控件实现

2025年10月12日发布 | 最近更新于 2025年10月12日

用于不规则按钮区域增加高斯模糊处理,实现原理就是截取控件在整个背景图像位置的图像,做高斯模糊处理之后,使用自定义的Path区域去裁剪绘制不规则区域的高斯模糊效果。

一、控件Kotlin实现代码

import android.content.Context
import android.graphics.*
import android.renderscript.Allocation
import android.renderscript.Element
import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean

/**
 * 航前检查区域高斯模糊处理View
 */
class IrregularBlurView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {

    private val blurPath = Path()
    private var blurredBackground: Bitmap? = null
    private var triangleCutSize: Float = 32f
    private var blurRadius: Float = 15f
    private var lastSourceBitmap: Bitmap? = null

    // 性能优化相关
    private val blurExecutor = Executors.newSingleThreadExecutor()
    private val isBlurInProgress = AtomicBoolean(false)
    private var rs: RenderScript? = null
    private var blurScript: ScriptIntrinsicBlur? = null
    private var allocationIn: Allocation? = null
    private var allocationOut: Allocation? = null

    // 防抖参数
    private var lastUpdateTime: Long = 0
    private val UPDATE_THRESHOLD = 33L // 30fps,约33ms

    // 缩放参数
    private val SCALE_FACTOR = 0.25f // 缩小4倍进行模糊,大幅提升性能

    init {
        setBackgroundColor(Color.TRANSPARENT)

        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.IrregularBlurView)
        triangleCutSize = typedArray.getDimension(
            R.styleable.IrregularBlurView_triangleCutSize, 32f
        )
        blurRadius = typedArray.getFloat(
            R.styleable.IrregularBlurView_blurRadius, 15f
        )
        typedArray.recycle()

        // 预初始化RenderScript资源
        initializeRenderScript()
    }

    private fun initializeRenderScript() {
        try {
            rs = RenderScript.create(context)
            blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
        } catch (e: Exception) {

        }
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        cleanup()
    }

    private fun cleanup() {
        // 取消正在进行的模糊任务
        isBlurInProgress.set(false)

        // 释放RenderScript资源
        blurScript?.destroy()
        allocationIn?.destroy()
        allocationOut?.destroy()
        rs?.destroy()

        blurredBackground?.recycle()
        blurredBackground = null
    }

    fun setSourceBitmap(bitmap: Bitmap) {
        if (bitmap.isRecycled) {
            return
        }

        // 防抖检查
        val currentTime = System.currentTimeMillis()
        if (currentTime - lastUpdateTime < UPDATE_THRESHOLD) {
            return
        }
        lastUpdateTime = currentTime

        lastSourceBitmap = bitmap

        if (width == 0 || height == 0) {
            return
        }

        // 使用后台线程进行模糊处理
        processBlurAsync(bitmap)
    }

    private fun getLocationOnScreen(): IntArray {
        val location = IntArray(2)
        getLocationOnScreen(location)
        return location
    }

    private fun processBlurAsync(bitmap: Bitmap) {
        if (isBlurInProgress.getAndSet(true)) {
            return // 已有任务在进行中
        }

        blurExecutor.execute {
            try {
                val result = performBlurOperation(bitmap)
                post {
                    isBlurInProgress.set(false)
                    result?.let {
                        updateBlurredBackground(it)
                    }
                }
            } catch (e: Exception) {
                isBlurInProgress.set(false)
            }
        }
    }

    private fun performBlurOperation(bitmap: Bitmap): Bitmap? {
        return try {
            val location = getLocationOnScreen()
            val x = location[0]
            val y = location[1]

            // 安全的裁剪区域计算
            val safeLeft = x.coerceIn(0, bitmap.width - 1)
            val safeTop = y.coerceIn(0, bitmap.height - 1)
            val safeWidth = width.coerceAtMost(bitmap.width - safeLeft)
            val safeHeight = height.coerceAtMost(bitmap.height - safeTop)

            if (safeWidth <= 0 || safeHeight <= 0) {
                return null
            }

            // 裁剪源区域
            val croppedBitmap = Bitmap.createBitmap(
                bitmap, safeLeft, safeTop, safeWidth, safeHeight
            )

            // 优化后的模糊处理
            val blurred = optimizedSafeBlur(croppedBitmap)
            croppedBitmap.recycle()
            blurred
        } catch (e: Exception) {
            null
        }
    }

    private fun updateBlurredBackground(bitmap: Bitmap) {
        blurredBackground?.recycle()
        blurredBackground = bitmap
        invalidate()
    }

    /**
     * 优化后的安全模糊方法
     */
    private fun optimizedSafeBlur(image: Bitmap): Bitmap {
        // 1. 大幅缩小图片尺寸进行模糊
        val scaledWidth = (image.width * SCALE_FACTOR).toInt().coerceAtLeast(1)
        val scaledHeight = (image.height * SCALE_FACTOR).toInt().coerceAtLeast(1)

        val inputBitmap = Bitmap.createScaledBitmap(image, scaledWidth, scaledHeight, true)

        // 2. 使用预分配的RenderScript资源
        val result = try {
            renderScriptBlur(inputBitmap)
        } catch (e: Exception) {
            fastJavaBlur(inputBitmap)
        }

        // 3. 模糊后放大到原始尺寸
        val finalResult = Bitmap.createScaledBitmap(result, image.width, image.height, true)

        // 回收临时bitmap
        inputBitmap.recycle()
        if (result != finalResult) {
            result.recycle()
        }

        return finalResult
    }

    /**
     * 使用预分配资源的RenderScript模糊
     */
    private fun renderScriptBlur(input: Bitmap): Bitmap {
        val rs = this.rs ?: throw IllegalStateException("RenderScript not initialized")
        val blurScript = this.blurScript ?: throw IllegalStateException("Blur script not initialized")

        // 重用或重新创建Allocation
        if (allocationIn == null || allocationIn!!.type.element != Element.U8_4(rs)) {
            allocationIn?.destroy()
            allocationIn = Allocation.createFromBitmap(rs, input)
        } else {
            allocationIn!!.copyFrom(input)
        }

        if (allocationOut == null || allocationOut!!.type.element != Element.U8_4(rs)) {
            allocationOut?.destroy()
            allocationOut = Allocation.createTyped(rs, allocationIn!!.type)
        }

        val outputBitmap = Bitmap.createBitmap(input.width, input.height, Bitmap.Config.ARGB_8888)

        blurScript.setRadius(blurRadius.coerceIn(0.1f, 25f))
        blurScript.setInput(allocationIn)
        blurScript.forEach(allocationOut)
        allocationOut!!.copyTo(outputBitmap)

        return outputBitmap
    }

    /**
     * 快速Java模糊实现(备选方案)
     */
    private fun fastJavaBlur(src: Bitmap): Bitmap {
        val radius = 5 // 固定小半径,保证性能

        val w = src.width
        val h = src.height
        val output = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)

        val srcPixels = IntArray(w * h)
        val dstPixels = IntArray(w * h)
        src.getPixels(srcPixels, 0, w, 0, 0, w, h)

        // 水平模糊
        for (y in 0 until h) {
            for (x in 0 until w) {
                var a = 0
                var r = 0
                var g = 0
                var b = 0
                var count = 0

                for (dx in -radius..radius) {
                    val nx = (x + dx).coerceIn(0, w - 1)
                    val pixel = srcPixels[y * w + nx]
                    a += Color.alpha(pixel)
                    r += Color.red(pixel)
                    g += Color.green(pixel)
                    b += Color.blue(pixel)
                    count++
                }

                dstPixels[y * w + x] = Color.argb(
                    a / count, r / count, g / count, b / count
                )
            }
        }

        // 垂直模糊
        for (x in 0 until w) {
            for (y in 0 until h) {
                var a = 0
                var r = 0
                var g = 0
                var b = 0
                var count = 0

                for (dy in -radius..radius) {
                    val ny = (y + dy).coerceIn(0, h - 1)
                    val pixel = dstPixels[ny * w + x]
                    a += Color.alpha(pixel)
                    r += Color.red(pixel)
                    g += Color.green(pixel)
                    b += Color.blue(pixel)
                    count++
                }

                srcPixels[y * w + x] = Color.argb(
                    a / count, r / count, g / count, b / count
                )
            }
        }

        output.setPixels(srcPixels, 0, w, 0, 0, w, h)
        return output
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        updateBlurPath()
        lastSourceBitmap?.let { setSourceBitmap(it) }
    }

    private fun updateBlurPath() {
        blurPath.reset()
        blurPath.apply {
            moveTo(triangleCutSize, 0f)
            lineTo(width.toFloat(), 0f)
            lineTo(width.toFloat(), height.toFloat())
            lineTo(0f, height.toFloat())
            lineTo(0f, triangleCutSize)
            close()
        }
    }

    override fun onDraw(canvas: Canvas) {
        blurredBackground?.let { bitmap ->
            if (!bitmap.isRecycled) {
                canvas.save()
                canvas.clipPath(blurPath)
                canvas.drawBitmap(bitmap, 0f, 0f, null)
                canvas.restore()
            }
        }
        super.onDraw(canvas)
    }

    companion object {
        private const val TAG = "IrregularBlurView"
    }
}

其中 blurPath 为不规则区域的Path参数,上面控件中实现的是左上角缺失一个三角形的不规则区域图形,可以根据自己的需求自定义不规则图形区域。setSourceBitmap(bitmap: Bitmap) 设置值为整个背景的BItmap,控件内部根据当前控件所在区域截取Path区域的内容做高斯模糊处理。

样式配置如下:

    <declare-styleable name="IrregularBlurView">
        <attr name="triangleCutSize" format="reference" />
        <attr name="blurRadius" format="reference" />
    </declare-styleable>

扩展阅读:

  • Android 航线剖面图自定义控件绘制实现
  • Android 自定义棱形样式进度条控件
  • Android 弧形 RecyclerView 实现(Kotlin)
  • 美图手机音乐Widget动画实现
  • Android 心率动画自定义控件实现
  • Android 卡片旋转切换动效实现详解
  • Android 残影数字动画实现详解
  • Android 自定义菱形横向滑动指示器控件
  • Android 航线缩略图简易绘制实现
  • Android PDF文件浏览及目录显示交互实现
博客公众号

微信公众号

转载请注明出处:陈文管的博客 – Android不规则图形高斯模糊控件实现

发表回复 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注

16 − 2 =

博客公众号

闽ICP备18001825号-1 · Copyright © 2025 · Powered by chenwenguan.com