当前位置: 代码网 > it编程>App开发>Android > Android实现动态高斯模糊背景效果

Android实现动态高斯模糊背景效果

2025年04月20日 Android 我要评论
一、项目介绍在现代androidui 中,动态高斯模糊背景常见于:对话框或弹窗后面的模糊遮罩侧滑菜单后面的实时模糊滚动内容时的背景模糊视频/图像播放器的模糊化背景相比静态模糊图,动态模糊可随着内容滚动

一、项目介绍

在现代 android ui 中,动态高斯模糊背景 常见于:

  • 对话框或弹窗后面的模糊遮罩

  • 侧滑菜单后面的实时模糊

  • 滚动内容时的背景模糊

  • 视频/图像播放器的模糊化背景

相比静态模糊图,动态模糊可随着内容滚动或变化实时更新,使界面更具层次感与沉浸感。但实时高斯模糊也带来性能挑战,需要在兼顾流畅度与画面清晰度之间权衡。

本项目目标是:

  1. 提供一个通用的 blurview 自定义控件,能在任意 api 级别上动态模糊其后面的视图。

  2. 在 api 31+ 上使用 rendereffect(硬件加速、性能佳),在 api 21–30 上使用 renderscript(软件/兼容)。

  3. 支持可调节的 模糊半径降采样比例(downsample)与 更新频率

  4. 演示如何在布局中快速集成:在布局文件或者代码中一行即可使用。

  5. 兼顾 生命周期,避免泄漏和无效更新。

二、相关技术与知识

  1. rendereffect(api 31+)

    • android.graphics.rendereffect.createblureffect(radiusx, radiusy, shader.tilemode.clamp)

    • 直接通过 view.setrendereffect() 给控件或背景添加实时高斯模糊。

  2. renderscript 与 scriptintrinsicblur(api 17+)

    • 使用支持模式 renderscriptsupportmodeenabled,在 build.gradle 中启用:

android {
  defaultconfig {
    renderscripttargetapi 21
    renderscriptsupportmodeenabled true
  }
}
  • scriptintrinsicblur 接受输入 allocation,输出模糊后的 allocation,再拷贝回 bitmap
  1. 降采样(downsampling)

    • 先将目标 bitmap 缩小若干倍(如 1/4),再模糊,可大幅提升性能;

    • 最终将模糊图拉伸回原始大小显示,肉眼看差别不大。

  2. viewtreeobserver.onpredrawlistener

    • 在每次 blurview 自身重绘前捕获底层内容快照,生成模糊图并应用。

    • 需在 onattachedtowindow() 注册,在 ondetachedfromwindow() 注销。

  3. surfaceview / textureview / glsurfaceview

    • 这些 view 的内容无法通过常规方式取到 bitmap;需特殊处理或跳过。

  4. 性能权衡

    • 模糊半径越大、采样缩放越小,效果越柔和,但计算量增加;

    • 需要设置合理的 更新间隔,避免每帧都重模糊。

三、实现思路

  1. 自定义控件 blurview

    • 继承 framelayout,让所有子 view 显示在模糊图之上;

    • 在背景层绘制模糊后的快照;

    • 通过自定义属性支持 blurradiusdownsamplefactorupdateinterval

  2. 布局集成

    • 在 activity_main.xml 或其他布局中,将内容放在 blurview 之后,或将 blurview 放在内容之上并设置 match_parent,即可遮罩。

  3. blurview 内部逻辑

    • 在 onattachedtowindow()

      • 判断 api 级别,初始化相应模糊引擎(rendereffect 或 renderscript);

      • 注册 viewtreeobserver.onpredrawlistener

    • 在 onpredrawlistener

      • 每隔 updateinterval ms 获取父容器或指定目标 view 的快照(getdrawingcache() 或 bitmap.createbitmap(view));

      • 根据 api 级别执行模糊:rendereffect 直接调用 setrendereffect();renderscript 生成 bitmap

      • 将模糊结果绘制到 canvas

  4. 释放资源

    • 在 ondetachedfromwindow()

      • 注销 onpredrawlistener

      • 销毁 renderscript rs.destroy()

四、完整代码

// ==============================================
// 文件:blurdemoactivity.java
// 功能:演示动态高斯模糊背景的使用
// 包含:布局 xml,gradle 配置,blurview 控件源码
// ==============================================
 
package com.example.blurviewdemo;
 
import android.graphics.bitmap;
import android.os.build;
import android.os.bundle;
import androidx.annotation.nullable;
import androidx.appcompat.app.appcompatactivity;
 
/*
=========================== app/build.gradle ===========================
android {
  compilesdk 33
  defaultconfig {
    applicationid "com.example.blurviewdemo"
    minsdk 21
    targetsdk 33
    // 启用 renderscript 兼容模式
    renderscripttargetapi 21
    renderscriptsupportmodeenabled true
  }
  // ...
}
dependencies {
  implementation 'androidx.appcompat:appcompat:1.5.1'
  implementation 'com.google.code.gson:gson:2.9.0' // 如需 json 解析
}
=========================== gradle 结束 ===========================
*/
 
/*
=========================== res/layout/activity_main.xml ===========================
<?xml version="1.0" encoding="utf-8"?>
<!-- 父布局:背景内容 -->
<framelayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/rootcontainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- 1. 背景内容示例 -->
    <imageview
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaletype="centercrop"
        android:src="@drawable/your_large_image"/>
    <!-- 2. 动态模糊遮罩层 -->
    <com.example.blurviewdemo.blurview
        android:id="@+id/blurview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:blurradius="16"
        app:downsamplefactor="4"
        app:updateinterval="100"/>
    <!-- 3. 前端 ui 元素 -->
    <textview
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="动态高斯模糊示例"
        android:textsize="24sp"
        android:textcolor="#ffffffff"
        android:layout_gravity="center"/>
</framelayout>
=========================== 布局结束 ===========================
*/
 
public class blurdemoactivity extends appcompatactivity {
    @override protected void oncreate(@nullable bundle s) {
        super.oncreate(s);
        setcontentview(r.layout.activity_main);
        // 无需额外代码,blurview 会在 attached 时自动工作
    }
}
 
// ==============================================
// 文件:blurview.java
// 功能:通用的动态高斯模糊遮罩控件
// ==============================================
 
package com.example.blurviewdemo;
 
import android.content.context;
import android.content.res.typedarray;
import android.graphics.*;
import android.os.build;
import android.renderscript.*;
import android.util.attributeset;
import android.view.*;
import android.widget.framelayout;
 
import androidx.annotation.nullable;
 
public class blurview extends framelayout {
 
    private int blurradius;         // 模糊半径
    private int downsamplefactor;   // 降采样倍数
    private long updateinterval;    // 更新间隔 ms
 
    private bitmap bitmapbuffer;
    private canvas  bitmapcanvas;
    private paint   paint = new paint(paint.anti_alias_flag);
    private boolean userendereffect;
    private renderscript rs;
    private scriptintrinsicblur instblur;
    private allocation allocin, allocout;
 
    private viewtreeobserver.onpredrawlistener predrawlistener;
    private long lastupdatetime = 0;
 
    public blurview(context c) { this(c, null); }
    public blurview(context c, attributeset attrs) { this(c, attrs, 0); }
    public blurview(context c, attributeset attrs, int defstyle) {
        super(c, attrs, defstyle);
        // 读取属性
        typedarray a = c.obtainstyledattributes(attrs, r.styleable.blurview);
        blurradius       = a.getint(r.styleable.blurview_blurradius, 10);
        downsamplefactor = a.getint(r.styleable.blurview_downsamplefactor, 4);
        updateinterval   = a.getint(r.styleable.blurview_updateinterval, 100);
        a.recycle();
 
        // 决定使用哪种模糊方式
        userendereffect = build.version.sdk_int >= build.version_codes.s;
 
        if (!userendereffect) {
            // 初始化 renderscript 模糊
            rs = renderscript.create(c);
            instblur = scriptintrinsicblur.create(rs, element.u8_4(rs));
            instblur.setradius(blurradius);
        }
 
        setwillnotdraw(false); // 允许 ondraw
    }
 
    @override
    protected void onattachedtowindow() {
        super.onattachedtowindow();
        // 注册 predraw 监听
        predrawlistener = () -> {
            long now = system.currenttimemillis();
            if (now - lastupdatetime >= updateinterval) {
                lastupdatetime = now;
                blurandinvalidate();
            }
            return true;
        };
        getviewtreeobserver().addonpredrawlistener(predrawlistener);
    }
 
    @override
    protected void ondetachedfromwindow() {
        super.ondetachedfromwindow();
        // 清理
        getviewtreeobserver().removeonpredrawlistener(predrawlistener);
        if (rs != null) rs.destroy();
    }
 
    /** 执行模糊并重绘自己 */
    private void blurandinvalidate() {
        // 获取父容器快照
        view root = (view) getparent();
        if (root == null) return;
 
        int width  = root.getwidth();
        int height = root.getheight();
        if (width == 0 || height == 0) return;
 
        int bw = width  / downsamplefactor;
        int bh = height / downsamplefactor;
 
        // 初始化缓存
        if (bitmapbuffer == null ||
            bitmapbuffer.getwidth()!=bw ||
            bitmapbuffer.getheight()!=bh) {
            bitmapbuffer = bitmap.createbitmap(bw, bh, bitmap.config.argb_8888);
            bitmapcanvas = new canvas(bitmapbuffer);
        }
        // 将 root 缩放绘制到 bitmap
        bitmapcanvas.save();
        bitmapcanvas.scale(1f/downsamplefactor, 1f/downsamplefactor);
        root.draw(bitmapcanvas);
        bitmapcanvas.restore();
 
        // 模糊
        if (userendereffect) {
            // api 31+: 直接在自己上设置 rendereffect
            rendereffect effect = rendereffect.createblureffect(
                blurradius, blurradius, shader.tilemode.clamp);
            setrendereffect(effect);
        } else {
            // renderscript 模糊
            if (allocin!=null) allocin.destroy();
            if (allocout!=null) allocout.destroy();
            allocin  = allocation.createfrombitmap(rs, bitmapbuffer);
            allocout = allocation.createtyped(rs, allocin.gettype());
            instblur.setinput(allocin);
            instblur.foreach(allocout);
            allocout.copyto(bitmapbuffer);
            // 将模糊结果拷贝到自己的 bitmap
            invalidate();
        }
    }
 
    @override
    protected void ondraw(canvas canvas) {
        super.ondraw(canvas);
        if (!userendereffect && bitmapbuffer!=null) {
            // 绘制放大回屏幕
            canvas.save();
            canvas.scale(downsamplefactor, downsamplefactor);
            canvas.drawbitmap(bitmapbuffer, 0, 0, paint);
            canvas.restore();
        }
    }
}
 
// ==============================================
// res/values/attrs.xml(整合在此)
// ==============================================
/*
<resources>
  <declare-styleable name="blurview">
    <attr name="blurradius" format="integer"/>
    <attr name="downsamplefactor" format="integer"/>
    <attr name="updateinterval" format="integer"/>
  </declare-styleable>
</resources>
*/
 

五、方法解读

  1. 属性读取

    • blurradius:高斯模糊半径(最大 25);

    • downsamplefactor:降采样比例,越大性能越好但细节越差;

    • updateinterval:两次模糊之间的最小间隔(避免每帧都模糊)。

  2. 模糊引擎选择

    • api 31+ 调用 view.setrendereffect(),由系统硬件加速处理;

    • api 21–30 使用 renderscript 的 scriptintrinsicblur,在软件层或兼容层执行。

  3. 预绘制监听

    • 在 onpredrawlistener 中获取父 view 快照,并进行降采样 & 模糊;

    • 每次更新后调用 invalidate(),触发 ondraw()

  4. 降采样再放大

    • 先对父 view 按 1/downsamplefactor 比例绘制到小 bitmap,再模糊,最后在 ondraw() 中放大回去;

    • 大幅降低模糊计算量,保证流畅。

  5. 生命周期管理

    • 在 onattachedtowindow() 注册监听,ondetachedfromwindow() 注销并销毁 renderscript。

    • 确保在 view 不可见或被销毁时不再占用资源。

六、项目总结

  • 性能与兼容

    • 推荐 api 31+ 使用 rendereffect,无需创建中间 bitmap,性能最佳;

    • api 21–30 使用 renderscript + 降采样,可在大多数设备保持 30fps 左右;

    • 合理调整 downsamplefactor(建议 48)与 updateinterval(建议 100200ms)。

  • 使用场景

    • 对话框后模糊(仅首次静态一次),可直接在布局中包裹对话框根视图;

    • 滚动时背景模糊(例如 recyclerview 下面),可将 blurview 放在内容之上;

    • 视频或动画背景模糊,需保证 updateinterval 足够长以免过度消耗。

  • 扩展

    1. 边缘遮罩:在模糊后绘制渐变遮罩边缘;

    2. 抖动补偿:在快速滚动时暂停模糊更新,滚动停止后再模糊;

    3. 多区域模糊:支持对某个子区域进行模糊,而不是全屏;

    4. jetpack compose:compose 1.3+ 中使用 modifier.graphicslayer { rendereffect = … } 简单实现;

以上就是android实现动态高斯模糊背景效果的详细内容,更多关于android高斯模糊背景的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com