一、项目介绍
在现代 android ui 中,动态高斯模糊背景 常见于:
对话框或弹窗后面的模糊遮罩
侧滑菜单后面的实时模糊
滚动内容时的背景模糊
视频/图像播放器的模糊化背景
相比静态模糊图,动态模糊可随着内容滚动或变化实时更新,使界面更具层次感与沉浸感。但实时高斯模糊也带来性能挑战,需要在兼顾流畅度与画面清晰度之间权衡。
本项目目标是:
提供一个通用的
blurview
自定义控件,能在任意 api 级别上动态模糊其后面的视图。在 api 31+ 上使用 rendereffect(硬件加速、性能佳),在 api 21–30 上使用 renderscript(软件/兼容)。
支持可调节的 模糊半径、降采样比例(downsample)与 更新频率。
演示如何在布局中快速集成:在布局文件或者代码中一行即可使用。
兼顾 生命周期,避免泄漏和无效更新。
二、相关技术与知识
rendereffect(api 31+)
android.graphics.rendereffect.createblureffect(radiusx, radiusy, shader.tilemode.clamp)
直接通过
view.setrendereffect()
给控件或背景添加实时高斯模糊。
renderscript 与 scriptintrinsicblur(api 17+)
使用支持模式
renderscriptsupportmodeenabled
,在build.gradle
中启用:
android { defaultconfig { renderscripttargetapi 21 renderscriptsupportmodeenabled true } }
scriptintrinsicblur
接受输入allocation
,输出模糊后的allocation
,再拷贝回bitmap
。
降采样(downsampling)
先将目标
bitmap
缩小若干倍(如 1/4),再模糊,可大幅提升性能;最终将模糊图拉伸回原始大小显示,肉眼看差别不大。
viewtreeobserver.onpredrawlistener
在每次
blurview
自身重绘前捕获底层内容快照,生成模糊图并应用。需在
onattachedtowindow()
注册,在ondetachedfromwindow()
注销。
surfaceview / textureview / glsurfaceview
这些 view 的内容无法通过常规方式取到
bitmap
;需特殊处理或跳过。
性能权衡
模糊半径越大、采样缩放越小,效果越柔和,但计算量增加;
需要设置合理的 更新间隔,避免每帧都重模糊。
三、实现思路
自定义控件
blurview
继承
framelayout
,让所有子 view 显示在模糊图之上;在背景层绘制模糊后的快照;
通过自定义属性支持
blurradius
、downsamplefactor
、updateinterval
。
布局集成
在
activity_main.xml
或其他布局中,将内容放在blurview
之后,或将blurview
放在内容之上并设置match_parent
,即可遮罩。
blurview 内部逻辑
在
onattachedtowindow()
:判断 api 级别,初始化相应模糊引擎(rendereffect 或 renderscript);
注册
viewtreeobserver.onpredrawlistener
;
在
onpredrawlistener
:每隔
updateinterval
ms 获取父容器或指定目标 view 的快照(getdrawingcache()
或bitmap.createbitmap(view)
);根据 api 级别执行模糊:rendereffect 直接调用
setrendereffect()
;renderscript 生成bitmap
;将模糊结果绘制到
canvas
;
释放资源
在
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> */
五、方法解读
属性读取
blurradius
:高斯模糊半径(最大 25);downsamplefactor
:降采样比例,越大性能越好但细节越差;updateinterval
:两次模糊之间的最小间隔(避免每帧都模糊)。
模糊引擎选择
api 31+ 调用
view.setrendereffect()
,由系统硬件加速处理;api 21–30 使用 renderscript 的
scriptintrinsicblur
,在软件层或兼容层执行。
预绘制监听
在
onpredrawlistener
中获取父 view 快照,并进行降采样 & 模糊;每次更新后调用
invalidate()
,触发ondraw()
。
降采样再放大
先对父 view 按
1/downsamplefactor
比例绘制到小 bitmap,再模糊,最后在ondraw()
中放大回去;大幅降低模糊计算量,保证流畅。
生命周期管理
在
onattachedtowindow()
注册监听,ondetachedfromwindow()
注销并销毁 renderscript。确保在 view 不可见或被销毁时不再占用资源。
六、项目总结
性能与兼容
推荐 api 31+ 使用
rendereffect
,无需创建中间 bitmap,性能最佳;api 21–30 使用 renderscript + 降采样,可在大多数设备保持 30fps 左右;
合理调整
downsamplefactor
(建议 48)与updateinterval
(建议 100200ms)。
使用场景
对话框后模糊(仅首次静态一次),可直接在布局中包裹对话框根视图;
滚动时背景模糊(例如 recyclerview 下面),可将
blurview
放在内容之上;视频或动画背景模糊,需保证
updateinterval
足够长以免过度消耗。
扩展
边缘遮罩:在模糊后绘制渐变遮罩边缘;
抖动补偿:在快速滚动时暂停模糊更新,滚动停止后再模糊;
多区域模糊:支持对某个子区域进行模糊,而不是全屏;
jetpack compose:compose 1.3+ 中使用
modifier.graphicslayer { rendereffect = … }
简单实现;
以上就是android实现动态高斯模糊背景效果的详细内容,更多关于android高斯模糊背景的资料请关注代码网其它相关文章!
发表评论