【Android】SDK31 原生api高斯模糊

1,概述

高斯模糊数学理论是根据正态分布,对图片进行一遍过滤,本质是一个低通滤波器。在sdk31中,原生提供了两种类型的模糊; 

Background blur: 模糊区域是窗口

Blur behind:模糊区域是整个屏幕

【Android】SDK31 原生api高斯模糊_第1张图片

 API:

WindowManager
 
# Background blur
android.view.Window#setBackgroundBlurRadius(int blurRadius)
 
# Blur behind
android.view.WindowManager.LayoutParams#FLAG_BLUR_BEHIND
android.view.WindowManager.LayoutParams#setBlurBehindRadius(int blurBehindRadius)
 
# Cross-window blur enabled listener
android.view.WindowManager#isCrossWindowBlurEnabled()
android.view.WindowManager#addCrossWindowBlurEnabledListener(@NonNull Consumer listener)
android.view.WindowManager#removeCrossWindowBlurEnabledListener(Consumer listener)
 
attrs
 
   
    
    
    

2,源码分析

Window实现类PhoneWindow新增setBackgroundBlurRadius方法,

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);
            }
        }
    }

可知,进一步调用mDecor.setBackgroundBlurRadius,而DecorView作为顶层View,本质也是个View。从该方法可知,会设置高斯模糊监听,如果没有则默认设置一个,重点是updateBackgroundBlurRadius方法。

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();
        }
    }

跟进updateBackgroundBlurRadius,可知设置高斯模糊target为ViewRootImpl,这个不必多说,是view的根viewParent,注意view.getRootView拿到的是根View,一般是DecorView(也有特例,比如部分系统window就不是DecorView),

private void updateBackgroundBlurRadius() {
        if (getViewRootImpl() == null) return;

        mBackgroundBlurRadius = mCrossWindowBlurEnabled && mWindow.isTranslucent()
                ? mOriginalBackgroundBlurRadius : 0;
        if (mBackgroundBlurDrawable == null && mBackgroundBlurRadius > 0) {
            mBackgroundBlurDrawable = getViewRootImpl().createBackgroundBlurDrawable();
            updateBackgroundDrawable();
        }

        if (mBackgroundBlurDrawable != null) {
            mBackgroundBlurDrawable.setBlurRadius(mBackgroundBlurRadius);
        }
    }

sdk31提供了新的类BackgroundBlurDrawable,继承Drawable,保存到mBackgroundBlurDrawable,并且设置模糊度数,跟进updateBackgroundDrawable,这个就是调用view.setBackgroundDrawable将高斯模糊Drawable设置进去即可,

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) {
            destDrawable = new LayerDrawable(new Drawable[] {mBackgroundBlurDrawable,
                                                             mOriginalBackgroundDrawable});
        }

        if (destDrawable != null && !mBackgroundInsets.equals(Insets.NONE)) {
            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);
                }
            };
        }

        // Call super since we are intercepting setBackground on this class.
        super.setBackgroundDrawable(destDrawable);

        mLastBackgroundInsets = mBackgroundInsets;
        mLastBackgroundBlurDrawable = mBackgroundBlurDrawable;
        mLastOriginalBackgroundDrawable = mOriginalBackgroundDrawable;
    }

3,跨窗口控件模糊

抛出一个问题,如果通过Application.getSystemService(WindowManager.class).addView(view,layoutParams)添加的View怎么模糊呢?由于不是在Activity一层,读取源码发现Application.windowManagerd的窗口存在WindowManagerImpl#mParentWindow成员,但是创建时,

 registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx);
            }});

发现只传入ContextImpl,而该ctx是newApplication时attach的,因此应用进程唯一,回调另一个构造方法,如下,发现传入的window为null,那么问题来了,在Application添加view时没有window,就没办法getWindow设置高斯模糊了呢,怎么办呢?

public WindowManagerImpl(Context context) {
        this(context, null /* parentWindow */, null /* clientToken */);
    }

【答案】根据源码,通过反射ViewRootImpl创建高斯模糊Drawable即可,代码如下,注意需要在view.onAttachToWindow回调该方法。

@TargetApi(value = 31)
    public static void setBackgroundBlurRadius(View view, int radius) {
        if (view == null) {
            return;
        }
        if (view instanceof ViewGroup){
            ViewGroup viewGroup = (ViewGroup) view;
            WindowManager.LayoutParams lp = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT);
            // 这是添加高斯模糊背景,
            BlurView blurView = new BlurView(viewGroup.getContext());
            blurView.setBlurLayerColor(viewGroup.getContext().getResources().getColor(R.color.blur_color));
            viewGroup.setBackground(null);
            viewGroup.addView(blurView, 0, lp);
        }
        ViewParent target = view.getParent();

        while (target != null) {
            if (target instanceof ViewRootImpl) {
                Drawable blurDrawable = getBackgroundBlurRadius((ViewRootImpl) target, radius);
                Drawable realDrawable = view.getBackground();
                LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{realDrawable, blurDrawable});
                view.setBackground(layerDrawable);
                Log.d(TAG, "setBackgroundBlurRadius: success " + radius);
                return;
            }
            target = target.getParent();
        }
    }

    private static Drawable getBackgroundBlurRadius(ViewRootImpl target, int radius) {
        Drawable bd = ReflectUtils.invokeObject(ViewRootImpl.class, target, "createBackgroundBlurDrawable",
                Drawable.class, null);

        try {
            ReflectUtils.invokeObject(Class.forName("com.android.internal.graphics.drawable.BackgroundBlurDrawable"), bd,
                    "setBlurRadius", void.class, new Class[]{int.class}, radius);

        } catch (ClassNotFoundException e) {
            Log.e(TAG, "realSetBackgroundBlurRadius:" + e.getMessage());
        }
        return bd;
    }

上述问题是跨窗口控件模糊,流程简单总结如下:


BackgroundBlurDrawable backgroundBlurDrawable = myView.getViewRootImpl().createBackgroundBlurDrawable();
backgroundBlurDrawable.setBlurRadius(150);
backgroundBlurDrawable.setCornerRadius(10, 10, 10, 10);
myView.setBackground(backgroundBlurDrawable);

4,控件模糊

添加了新的RenderEffect API,可以作用于RenderNode

renderNode.setRenderEffect(
    RenderEffect.createBlurEffect(radiusX, radiusY, Shader.TileMode.CLAMP))

myView.setRenderEffect(
    RenderEffect.createBlurEffect(radiusX, radiusY, Shader.TileMode.CLAMP))

API:

android.graphics.RenderNode#setRenderEffect(RenderEffect effect)
android.graphics.RenderEffect#createBlurEffect(float radiusX, float radiusY,
@NonNull TileMode edgeTreatment);

你可能感兴趣的:(android,java,apache)