目录
- 一、项目介绍
- 二、相关技术与知识
- 三、实现思路
- 四、完整代码
- 五、方法解读
- 六、项目总结
一、项目介绍
在现代 android UI 中,动态高斯模糊背景 常见于:
对话框或弹窗后面的模糊遮罩
侧滑菜单后面的实时模糊
滚动内容时的背景模糊
视频/图像播放器的模糊化背景
相比静态模糊图,动态模糊可随着内容滚动或变化实时更新,使界面更具层次感与沉浸感。但实时高斯模糊也带来性能挑战,需要在兼顾流畅度与画面清晰度之间权衡。
本项目目标是:
提供一个通用的
BlurView自定义控件,能在任意 API 级别上动态模糊其后面的视图。在 API 31+ 上使用 RenderEffect(硬件加速、性能佳),在 API 21–30 上使用 RenderScript(软件/兼容)。
支持可调节的 模糊半径、降采样比例(downsample)与 android;更新频率。
演示如何在布局中快速集成:在布局文件或者代码中一行即可使用。
兼顾 生命周期,避免泄漏和无效更新。
二、相关技术与知识
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
python // 启用 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.*;
imjavascriptport 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 / downsajsmpleFactor;
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(整合在此)
// ==============================================
android/*
<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高斯模糊背景的资料请关注编程客栈(www.devze.com)其它相关文章!
加载中,请稍侯......
精彩评论