前言
在 Android 12 中,提供了一些用于实现窗口模糊处理效果(例如背景模糊处理和模糊处理后方屏幕)的公共 API。窗口模糊处理或跨窗口模糊处理用于模糊处理给定窗口后方的屏幕。
有两种窗口模糊处理方式,可用于实现不同的视觉效果:
-
背景模糊处理(Background blur):可用于创建具有模糊背景的窗口,创造出磨砂玻璃效果,模糊区域是窗口。
-
模糊处理后方屏幕(Blur behind):可用于模糊处理(对话框)窗口后方的整个屏幕,创造出景深效果,模糊区域是整个屏幕。
这两种效果可以单独使用,也可以组合使用,如下图所示:
上面的三张效果图是谷歌官方所提供的效果图:
(a)仅背景模糊处理(Background blur)
(b)仅模糊处理后方屏幕(Blur behind)
(c)背景模糊处理和模糊处理后方屏幕(Background blur)+(Blur behind)
上一篇我们已经讲述了Android12如何使用原生API实现高斯模糊效果,本篇文章我们将会结合源码具体分析原生API实现高斯模糊效果的底层逻辑。
一、API对应的源码
1、PhoneWindow的setBackgroundBlurRadius方法如下所示:
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public final void setBackgroundBlurRadius(int blurRadius) {
super.setBackgroundBlurRadius(blurRadius);
if (CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED) {
if (mBackgroundBlurRadius != Math.max(blurRadius, 0)) {
mBackgroundBlurRadius = Math.max(blurRadius, 0);
mDecor.setBackgroundBlurRadius(mBackgroundBlurRadius);
}
}
}
}
setBackgroundBlurRadius首先判断用户有没有设置blurRadius,如果设置了会继续调用DecorView的setBackgroundBlurRadius方法。
2、DecorView的setBackgroundBlurRadius方法如下所示:
frameworks/base/core/java/com/android/internal/policy/DecorView.java
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
void setBackgroundBlurRadius(int blurRadius) {
mOriginalBackgroundBlurRadius = blurRadius;
if (blurRadius > 0) {
if (mCrossWindowBlurEnabledListener == null) {
mCrossWindowBlurEnabledListener = enabled -> {
mCrossWindowBlurEnabled = enabled;
updateBackgroundBlurRadius();
};
getContext().getSystemService(WindowManager.class)
.addCrossWindowBlurEnabledListener(mCrossWindowBlurEnabledListener);
getViewTreeObserver().addOnPreDrawListener(mBackgroundBlurOnPreDrawListener);
} else {
//更新高斯模糊背景的高斯半径
updateBackgroundBlurRadius();
}
} else if (mCrossWindowBlurEnabledListener != null) {
updateBackgroundBlurRadius();
removeBackgroundBlurDrawable();
}
}
}
DecorView的setBackgroundBlurRadius方法进行一些判断之后,会调用一个比较关键的方法updateBackgroundBlurRadius。
3、DecorView的updateBackgroundBlurRadius方法如下所示:
//更新高斯模糊背景的高斯半径
private void updateBackgroundBlurRadius() {
//如果viewRootImpl为空直接返回
if (getViewRootImpl() == null) return;
//获取背景高斯模糊效果半径
mBackgroundBlurRadius = mCrossWindowBlurEnabled && mWindow.isTranslucent()
? mOriginalBackgroundBlurRadius : 0;
if (mBackgroundBlurDrawable == null && mBackgroundBlurRadius > 0) {
//调用ViewRootImpl的方法创建高斯模糊Drawable文件
mBackgroundBlurDrawable = getViewRootImpl().createBackgroundBlurDrawable();
updateBackgroundDrawable();
}
if (mBackgroundBlurDrawable != null) {
mBackgroundBlurDrawable.setBlurRadius(mBackgroundBlurRadius);
}
}
updateBackgroundBlurRadius方法首先获取ViewRootImpl实例对象,如果为空直接返回;如果不为空则获取背景高斯模糊效果半径,当mCrossWindowBlurEnabled 为true且窗口时透明样式的时候,才会获取之前设置的高斯模糊效果半径,否则高斯模糊效果半径直接设置为0;然后会检测mBackgroundBlurDrawable是否为空,如果为空且获取的mBackgroundBlurRadius大于0,便会调用ViewRootImpl的createBackgroundBlurDrawable方法创建BackgroundBlurDrawable对象实例。
4、ViewRootImpl的createBackgroundBlurDrawable方法如下所示:
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
private final BackgroundBlurDrawable.Aggregator mBlurRegionAggregator =
new BackgroundBlurDrawable.Aggregator(this);
public BackgroundBlurDrawable createBackgroundBlurDrawable() {
return mBlurRegionAggregator.createBackgroundBlurDrawable(mContext);
}
}
createBackgroundBlurDrawable方法仅仅是进一步调用mBlurRegionAggregator的createBackgroundBlurDrawable方法,mBlurRegionAggregator是BackgroundBlurDrawable的一个内部静态类。
5、Aggregator的createBackgroundBlurDrawable方法如下所示:
public final class BackgroundBlurDrawable extends Drawable {
public static final class Aggregator {
private final ViewRootImpl mViewRoot;
...代码省略...
public Aggregator(ViewRootImpl viewRoot) {
mViewRoot = viewRoot;
}
/**
* 使用默认背景模糊圆角半径创建一个模糊区域
*/
public BackgroundBlurDrawable createBackgroundBlurDrawable(Context context) {
BackgroundBlurDrawable drawable = new BackgroundBlurDrawable(this);
drawable.setBlurRadius(context.getResources().getDimensionPixelSize(R.dimen.default_background_blur_radius));
return drawable;
}
...代码省略...
}
}
Aggregator的createBackgroundBlurDrawable将自己作为参数,创建了一个继承自Drawable的BackgroundBlurDrawable对象实例,并设置模糊半径为dimens.xml文件中配置的default_background_blur_radius字段。
6、重新回到第3步DecorView的updateBackgroundBlurRadius方法中:
//更新高斯模糊背景的高斯半径
private void updateBackgroundBlurRadius() {
...代码省略...
if (mBackgroundBlurDrawable == null && mBackgroundBlurRadius > 0) {
mBackgroundBlurDrawable = getViewRootImpl().createBackgroundBlurDrawable();
//更新背景Drawable
updateBackgroundDrawable();
}
if (mBackgroundBlurDrawable != null) {
mBackgroundBlurDrawable.setBlurRadius(mBackgroundBlurRadius);
}
}
在调用createBackgroundBlurDrawable方法为mBackgroundBlurDrawable进行赋值之后,会继续调用DecorView的updateBackgroundDrawable方法来更新当前DecorView所对应的背景Drawable,最后再使用mBackgroundBlurRadius更新mBackgroundBlurDrawable的模糊圆角半径数值。
7、DecorView的updateBackgroundDrawable方法如下所示:
private void updateBackgroundDrawable() {
// Background insets can be null if super constructor calls setBackgroundDrawable.
if (mBackgroundInsets == null) {
mBackgroundInsets = Insets.NONE;
}
if (mBackgroundInsets.equals(mLastBackgroundInsets)
&& mBackgroundBlurDrawable == mLastBackgroundBlurDrawable
&& mLastOriginalBackgroundDrawable == mOriginalBackgroundDrawable) {
return;
}
Drawable destDrawable = mOriginalBackgroundDrawable;
if (mBackgroundBlurDrawable != null) {
//将新获取的类型为BackgroundBlurDrawable的mBackgroundBlurDrawable
// 和原来类型为Drawable的mOriginalBackgroundDrawable合并成一个LayerDrawable实例对象
destDrawable = new LayerDrawable(new Drawable[]{mBackgroundBlurDrawable,
mOriginalBackgroundDrawable});
}
if (destDrawable != null && !mBackgroundInsets.equals(Insets.NONE)) {
//将当前类型为LayerDrawable的destDrawable再封装成InsetDrawable实例对象
destDrawable = new InsetDrawable(destDrawable,
mBackgroundInsets.left, mBackgroundInsets.top,
mBackgroundInsets.right, mBackgroundInsets.bottom) {
/**
* Return inner padding so we don't apply the padding again in
* {@link DecorView#drawableChanged()}
*/
@Override
public boolean getPadding(Rect padding) {
return getDrawable().getPadding(padding);
}
};
}
//调用父类方法设置这个类的背景Drawable
super.setBackgroundDrawable(destDrawable);
mLastBackgroundInsets = mBackgroundInsets;
mLastBackgroundBlurDrawable = mBackgroundBlurDrawable;
mLastOriginalBackgroundDrawable = mOriginalBackgroundDrawable;
}