一、项目介绍
在现代 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:每隔
updateintervalms 获取父容器或指定目标 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高斯模糊背景的资料请关注代码网其它相关文章!
发表评论