Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 亚洲欧美在线不卡,中文字幕美日韩在线高清,精品久久久久中文字幕日本

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          CSS設(shè)置背景模糊

          CSS設(shè)置背景模糊

          SS設(shè)置背景模糊

          在做一些頁面的時(shí)候,為了讓頁面更好看,我們常常需要設(shè)置一些背景圖片,但是,當(dāng)背景圖片太過花哨的時(shí)候,又會(huì)影響我們的主體內(nèi)容,所以我們就需要用到filter屬性來設(shè)置他的模糊值。

          html代碼如下

          <!DOCTYPE html>
          <html>
          <head>
           <meta charset="utf-8">
           <meta name="viewport" content="width=device-width">
           <title>JS Bin</title>
          </head>
          <body>
          <div class="cover">
           <h1>我是需要突出顯示的內(nèi)容
          </div>
          </body>
          </html>
          

          但是如果直接在背景圖片上使用的話,

          .cover{
           width:600px;
           height:300px;
           position:relative;
           text-align:center;
           line-height:300px;
           color:white;
           background:transparent url(http://91jean.oss-cn-hangzhou.aliyuncs.com/18-8-31/16567303.jpg) center center no-repeat;
           filter:blur(5px);
           background-size:cover;
          }
          

          可能會(huì)造成下面的這種情況。

          我們會(huì)發(fā)現(xiàn)背景和主體內(nèi)容都變糊了。

          解決辦法:給背景圖片增加一個(gè)偽元素,將背景圖片和filter屬性設(shè)置在偽元素上,具體代碼如下

          .cover{
           width:600px;
           height:300px;
           position:relative;
           text-align:center;
           line-height:300px;
           color:white;
          }
          .cover::before{ 
           content:'';
           position:absolute;
           top:0;
           left:0;
           width:600px;
           height:300px;
           background:transparent url(http://91jean.oss-cn-hangzhou.aliyuncs.com/18-8-31/16567303.jpg) center center no-repeat;
           filter:blur(5px);
           z-index:-1;
           background-size:cover;
          }
          

          復(fù)制代碼



          在一名 Android 程序猿的職業(yè)生涯中,大概率會(huì)與設(shè)計(jì)獅有過這樣的討論:

          :“我要這個(gè)毛玻璃效果”

          :“這個(gè)效果實(shí)現(xiàn)不了”

          (掏出iPhone):“你看人家蘋果都有,你怎么做不了?”

          :“iOS 可以,Android 真的做不了,童叟無欺”


          這個(gè)讓設(shè)計(jì)師心心念念的視覺效果,就是背景模糊。

          iOS 8 加入的 UIVisualEffectView、CSS 中的backdrop-filter 以及 Flutter 中的 BackdropFilter 類,都可以實(shí)現(xiàn)這個(gè)效果,只有 Android 一直沒有支持該能力。


          在 Android 領(lǐng)域內(nèi),背景模糊效果有兩個(gè)不同場(chǎng)景,需要做一下區(qū)分:

          • Window 級(jí)背景模糊:將某個(gè)半透明 Window 的背景內(nèi)容進(jìn)行模糊處理,常見于通知欄下拉之類的場(chǎng)景。
          • View 級(jí)背景模糊:在某一個(gè)App頁面內(nèi),某控件的背景內(nèi)容進(jìn)行模糊處理。這個(gè)能力 Android 一直沒有支持,只有一些開源框架進(jìn)行過嘗試。

          Window 級(jí)的背景模糊,多年以來各手機(jī)廠商都有自己的實(shí)現(xiàn)方案。而 Android 12 里,AOSP 對(duì)SurfaceFlinger 進(jìn)行了重構(gòu),GPU 合成的部分也使用 Skia 進(jìn)行渲染,同時(shí)對(duì)跨 Window 的背景模糊做了官方的支持[1],并 public 了相關(guān)的 API。

          Android 12 支持的Window背景模糊

          (模糊內(nèi)容屬于背后的窗口)


          而本文要討論的是后者——View 級(jí)背景模糊的實(shí)現(xiàn)方式。

          iOS 上的 View 級(jí)背景模糊效果

          (模糊內(nèi)容屬于同一窗口內(nèi)背后的控件)


          筆者整理了現(xiàn)在開源庫中的兩類解決方案,外加酷派團(tuán)隊(duì)調(diào)研出的兩種方案,共四種寫法,供大家參考。





          開源方案

          如果要從應(yīng)用側(cè)實(shí)現(xiàn)這個(gè)效果,最重要的一個(gè)步驟是獲取模糊控件背景的內(nèi)容,目前 Github 上的相關(guān)框架,大概分為兩個(gè)方案:



          方案一:找到背景控件、調(diào)用draw()

          代表框架:

          500px/500px-android-blur、Dimezis/BlurView、mmin18/RealtimeBlurView,這三個(gè)框架的??數(shù)量均為 2.7k 左右,接受度比較高。


          思路解析

          創(chuàng)建一個(gè)鏈接到 Bitmap 的離屏Canvas,在模糊控件繪制之前,將下層布局手動(dòng)繪制到這個(gè) Canvas 里,這樣在 Bitmap 里就拿到了控件背景內(nèi)容。


          其中:500px-android-blur 的做法是手動(dòng)指定背景的布局,在模糊控件 onDraw 的時(shí)候,對(duì)指定的下層布局進(jìn)行繪制。這種做法實(shí)現(xiàn)起來比較簡(jiǎn)單,但每次都需要手動(dòng)指定下層布局,而且模糊控件不能包含在該布局里,缺少靈活性。

          // 手動(dòng)指定下層布局
          
          
          blurringView.setBlurredView(blurredView);
          

          而 Dimezis/BlurView、mmin18/RealtimeBlurView 的解法則更加靈活,下層布局不用特別指定,直接使用rootView(一般為DecorView)。在模糊控件onPreDraw的時(shí)候,將rootView繪制到離屏Canvas。


          但 rootView 并不是下層布局,因?yàn)槟:丶舶趦?nèi)。這兩個(gè)框架使用比較取巧的辦法解決了這個(gè)問題:在draw(Canvas)方法里,判斷如果是離屏 Canvas 在繪制,則跳過自身繪制。


          獲取背景內(nèi)容后,剩下的步驟則是對(duì) Bitmap 進(jìn)行模糊處理并繪制,由于比較簡(jiǎn)單就不贅述了。


          方案效果(以500px-android-blur為例)

          優(yōu)點(diǎn)

          • 兼容性好。沒有使用系統(tǒng)隱藏接口,理論上在任意 Android 版本上都能運(yùn)行。


          缺點(diǎn)

          方案缺點(diǎn)主要是額外的性能開銷。

          • 無法使用 Hardware Bitmap。這幾個(gè)框架都使用了 RenderScript 做模糊處理,無法使用 Hardware Bitmap。模糊的結(jié)果在上屏之前還需要進(jìn)行一次紋理上傳。頻繁的 Bitmap 更新會(huì)帶來內(nèi)存及性能的更多開銷[2]。
          • 額外離屏繪制。由于調(diào)用了 draw 方法,每一幀都需要進(jìn)行一次額外離屏繪制。而且由于 Canvas 對(duì)應(yīng)的是非 Hardware Bitmap,這個(gè)離屏繪制也無法使用硬件加速




          方案二:Canvas GL Functor

          代表框架:HokoFly/HokoBlurDrawable

          方案效果


          這個(gè)框架使用起來非常簡(jiǎn)單,不需要做額外的設(shè)置,只需要給View設(shè)置一個(gè)background即可。

          final BlurDrawable blurDrawable=new BlurDrawable();
          view.setBackgroundDrawable(blurDrawable);

          調(diào)查了源碼,它也沒有把下層布局再次繪制,那它是如何獲取到背景內(nèi)容的?


          思路解析

          秘密在于這個(gè)隱藏方法:RecordingCanvas.callDrawGLFunction2()

          /**
          * Records the functor specified with the drawGLFunction function pointer. This is
          * functionality used by webview for calling into their renderer from our display lists.
          *
          * @param drawGLFunction A native function pointer
          *
          * @hide
          * @deprecated Use {@link #drawWebViewFunctor(int)}
          */
          @Deprecated
          public void callDrawGLFunction2(long drawGLFunction) {
              nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunction, null);
          }

          這個(gè)隱藏的方法看起來比較陌生,因?yàn)樗皇窃O(shè)計(jì)給 App 使用的。它的作用是將外部的 OpenGL 方法鏈接到 Canvas 的繪制流程里,目前官方的使用場(chǎng)景是 Android 的 WebView。

          WebView 使用自己的 OpenGL 方法對(duì)網(wǎng)頁進(jìn)行渲染,再調(diào)用這個(gè)方法,將結(jié)果鏈接到你的 App Window 里。

          這剛好能解釋,為什么 WebView 使用獨(dú)立的渲染機(jī)制,但不需要使用 SurfaceView 的獨(dú)立 layer 也能顯示到屏幕上。

          經(jīng)過一番調(diào)查發(fā)現(xiàn),參數(shù)drawGLFunction,是 Android Native 層的一個(gè)通用函數(shù)指針,結(jié)構(gòu)如下:

          // File: system/core/libutils/include/utils/Functor.h
          
          class Functor {
          
          public:
          
              Functor() {}
          
              virtual ~Functor() {}
          
              virtual status_t operator()(int /*what*/, void* /*data*/) { return OK; }
          
          };

          在 UI 線程調(diào)用callDrawGLFunction2()方法后,只是設(shè)置了函數(shù)指針,并沒有起到繪制效果。真正的繪制邏輯,發(fā)生在 RenderThread 里:

          // File: frameworks/base/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
          
          
          void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
          
          
              // 省略部分代碼
          
          
              ……
          
          
          
          
          
          
              GLuint fboID=0;
          
          
              SkISize fboSize;
          
          
              GetFboDetails(canvas, &fboID, &fboSize);
          
          
          
          
          
          
              // 省略部分代碼:初始化GLContext、判斷離屏Layer等
          
          
              ……
          
          
              DrawGlInfo info;
          
          
              info.clipLeft=clipBounds.fLeft;
          
          
              info.clipTop=clipBounds.fTop;
          
          
              info.clipRight=clipBounds.fRight;
          
          
              info.clipBottom=clipBounds.fBottom;
          
          
              info.isLayer=fboID !=0;
          
          
              info.width=fboSize.width();
          
          
              info.height=fboSize.height();
          
          
              mat4.getColMajor(&info.transform[0]);
          
          
              info.color_space_ptr=canvas->imageInfo().colorSpace();
          
          
          
          
          
          
              // 省略部分代碼:綁定FBO、設(shè)置GL環(huán)境
          
          
              ……
          
          
              if (mAnyFunctor.index()==0) {
          
          
                  std::get<0>(mAnyFunctor).handle->drawGl(info);
          
          
              } else {
          
          
                  // 這里會(huì)調(diào)用到函數(shù)指針Functor
          
          
                  (*(std::get<1>(mAnyFunctor).functor))(DrawGlInfo::kModeDraw, &info);
          
          
              }
          
          
              // 省略部分代碼
          
          
              ……
          
          
          }
          

          看到這里,為防止有讀者不了解 hwui,補(bǔ)充一些前提知識(shí):
          我們都知道 Android 的 View 系統(tǒng),在底層是使用 Skia 圖形庫進(jìn)行渲染,而連接 View 與 Skia 的組件便是 libhwui。App 界面的繪制指令,最終都通過 hwui 庫,在 RenderThread 中得以執(zhí)行。


          Skia 的SkDrawable結(jié)構(gòu),與 Android 的Drawable類似,都是在onDraw(canvas)方法中執(zhí)行繪制指令,實(shí)際上后者也是對(duì)前者的一個(gè)效仿設(shè)計(jì)。

          在 hwui 庫中,一共有這幾種 SkDrawable 類型:

          • RenderNodeDrawable:最常見的一個(gè)SkDrawable,99% 的 View 和布局的繪制指令會(huì)保存到 RenderNode 中,最終都由這個(gè) Drawable 進(jìn)行繪制。
          • AnimatedImageDrawable:對(duì)應(yīng)到 Java 層的同名類,一般用于播放 gif 圖片。
          • AnimatedRoundRect、AnimatedCircle、AnimatedRippleDrawable:Material Design中的按鈕Ripple 效果、Canvas的drawCircledrawRect方法的底層實(shí)現(xiàn)。
          • LayerDrawable:繪制一個(gè) OpenGL 紋理。TextureView的底層正是用它實(shí)現(xiàn)。
          • FunctorDrawable:在 Skia 繪制指令中,鏈接到外部繪制指令。它有三個(gè)子類:GLFunctorDrawable、VkFunctorDrawable,分別鏈接 OpenGL 與 Vulkan 指令。


          而方案二,正是使用了 GLFunctorDrawable


          回到這個(gè)庫中,上面的GLFunctorDrawable,會(huì)調(diào)用上層設(shè)置過來的 functor方法,并將此時(shí)的DrawGlInfo傳遞過去。

          我們來看看這個(gè)庫的 Functor 真正做了些什么:

          經(jīng)過 jni 的多次中轉(zhuǎn),F(xiàn)unctor 最終調(diào)用到了上面的 Java 層邏輯。與一般的 Java 層邏輯不同,圖中的代碼實(shí)際上運(yùn)行在 RenderThread 中,而且擁有 GLFunctorDrawable 已經(jīng)設(shè)置好的 OpenGL 上下文。


          這里的邏輯大概分為三步:獲取當(dāng)前屏幕內(nèi)容、進(jìn)行 X/Y 軸兩次模糊運(yùn)算、放大并疊加顏色

          最關(guān)鍵的代碼就是這行glCopyTexSubImage2D,它可以將當(dāng)前屏幕已繪制內(nèi)容進(jìn)行區(qū)域拷貝[3]。由于這個(gè)代碼的執(zhí)行在背景內(nèi)容繪制和控件內(nèi)容繪制之間,這樣便獲取到了控件的背景內(nèi)容


          總結(jié)

          這個(gè)方案使用GLFunctorDrawable的機(jī)制,將自己的 OpenGL 指令嵌入到 RenderThread 每一幀的繪制中,獲取背景內(nèi)容,做模糊并上屏。


          優(yōu)點(diǎn)

          • 使用方便:可以用 Drawable 的方式使用,嵌入到任意層級(jí)的布局。


          缺點(diǎn)

          當(dāng)然,這個(gè)方案的缺點(diǎn)也十分明顯,導(dǎo)致它無法在 app 里商用。

          1. 兼容性差

          callDrawGLFunctor2 是一個(gè) hide 方法,其在不同 Android 版本都有不同實(shí)現(xiàn),框架本身做了多平臺(tái)兼容。

          但在 Google 對(duì) hide 方法的態(tài)度及采取的措施面前,這個(gè)做法顯得不可持續(xù)。

          首先,Android 11 開始采取了更嚴(yán)格的反射限制,框架使用者需要額外去處理限制突破邏輯。

          其次,callDrawGLFunctor2(long functor)是一個(gè)廢棄方法,在 Android 12 開始被移除。

          在 Android 12 中,被換成 drawWebViewFunctor(int functor),不僅 functor 換了結(jié)構(gòu),還必須同時(shí)支持 OpenGLVulkan 兩種實(shí)現(xiàn)。

          這個(gè)方案的兼容成本已經(jīng)非常高了,框架作者也沒有繼續(xù)進(jìn)行維護(hù),目前該框架在 Android 12 上無法使用。


          1. 使用場(chǎng)景受限制

          在 hwui 的 pipeline 里,如果這塊內(nèi)容被繪制到了一塊離屏 buffer 再上屏,那么這里的 GLFunctor 便無法獲取到控件背后的內(nèi)容。

          這是因?yàn)椋?hwui 每一幀繪制開始之前,會(huì)先把離屏的 Layer 先渲染完成得到結(jié)果,再把這些結(jié)果當(dāng)做圖像資源,在這一幀參與繪制。
          需要離屏 buffer 的場(chǎng)景有這幾種:
          設(shè)置小于 1 的 alpha需要 clip 的 Functor有拉伸或者RenderEffect 效果(Android 12 添加)等,比較常見。

          // File: frameworks/base/libs/hwui/RenderProperties.h
          
          
          bool promotedToLayer() const {
          
          
              return mLayerProperties.mType==LayerType::None && fitsOnLayer() &&
          
          
                      // 是functor且有clip、animation、translation等情況。
          
          
                     (mComputedFields.mNeedLayerForFunctors ||
          
          
                      // 設(shè)置了RenderEffect(Android 12新增)
          
          
                      mLayerProperties.mImageFilter !=nullptr ||
          
          
                      // 當(dāng)前有拉伸效果(Android 12新增)
          
          
                      mLayerProperties.getStretchEffect().requiresLayer() ||
          
          
                      // 當(dāng)前View設(shè)置了alpha,且hasOverlappingRendering為true
          
          
                      (!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 &&
          
          
                       mPrimitiveFields.mHasOverlappingRendering));
          
          
              }
          

          所以當(dāng) GLFunctor 被離屏渲染時(shí),它會(huì)提前執(zhí)行,此時(shí)便無法獲取到它背后的內(nèi)容。也就是說這種方案,無法對(duì)模糊控件設(shè)置 alpha 或者切圓角,這點(diǎn)也在框架的 issues 里得到了印證:






          自研方案

          方案三:擴(kuò)展 Canvas.saveLayer()


          筆者發(fā)現(xiàn),F(xiàn)lutter 中的 BackdropFilter 類也能實(shí)現(xiàn)效果,其也使用 Skia 作為渲染引擎。


          BackdropFilter這個(gè) Dart 層的 Widget,經(jīng)過 SceneBuilder 的中轉(zhuǎn),最終映射到 native 層的BackdropFilterLayer類:

          它并沒有特殊的繪制邏輯,只是在繪制 children 內(nèi)容之前,調(diào)用了一個(gè)saveLayer操作。難道 Flutter僅僅通過saveLayer,就實(shí)現(xiàn)了背景模糊?

          Android中也有Canvas.saveLayer()這個(gè)API,其作用是創(chuàng)建一個(gè)新的Layer,后續(xù)的繪制均發(fā)生在這個(gè)新的Layer里,繪制完成后將結(jié)果再一起上屏。所以這個(gè)方法開銷比較大,非必要不建議使用。
          更關(guān)鍵的是,它并不支持背景模糊功能。

          看來紅框中的這一行代碼是關(guān)鍵。調(diào)查SaveLayerRec發(fā)現(xiàn),它是 skia 引擎中的結(jié)構(gòu)體,大致結(jié)構(gòu)如下:

          enum SaveLayerFlagsSet {
          
          
              kInitWithPrevious_SaveLayerFlag=1 << 2, //!< initializes with previous contents
          
          
          };
          
          
          
          
          
          
          struct SaveLayerRec {
          
          
              SaveLayerRec(const SkRect* bounds, const SkPaint* paint, SaveLayerFlags saveLayerFlags=0)
          
          
                  : fBounds(bounds)
          
          
                  , fPaint(paint)
          
          
                  , fSaveLayerFlags(saveLayerFlags)
          
          
              {}
          
          
              SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop, SaveLayerFlags saveLayerFlags)
          
          
                  : fBounds(bounds)
          
          
                  , fPaint(paint)
          
          
                  , fBackdrop(backdrop)
          
          
                  , fSaveLayerFlags(saveLayerFlags)
          
          
              {}
          
          
              /** hints at layer size limit */
          
          
              const SkRect*        fBounds=nullptr;
          
          
              /** modifies overlay */
          
          
              const SkPaint*       fPaint=nullptr;
          
          
              /**
          
          
               *  If not null, this triggers the same initialization behavior as setting
          
          
               *  kInitWithPrevious_SaveLayerFlag on fSaveLayerFlags: the current layer is copied into
          
          
               *  the new layer, rather than initializing the new layer with transparent-black.
          
          
               *  This is then filtered by fBackdrop (respecting the current clip).
          
          
               */
          
          
              const SkImageFilter* fBackdrop=nullptr;
          
          
              /** preserves LCD text, creates with prior layer contents */
          
          
              SaveLayerFlags       fSaveLayerFlags=0;
          
          
          };
          

          這里的SkImageFilter是 Skia 中可以實(shí)現(xiàn)圖形效果的工具類,常見的 filter 有:Blur、ColorFilter、Matrix、XferMode 等等。
          Skia 在很早就支持了這個(gè)能力,在 Android 12 中,谷歌在上層封裝了RenderEffect類,第一次將其開放給上層調(diào)用[4]。

          看下真正執(zhí)行模糊運(yùn)算的地方:

          void SkCanvas::internalSaveLayer(const SaveLayerRec& rec, SaveLayerStrategy strategy) {
          
          
              // 省略代碼
          
          
              ……
          
          
              // If we have a backdrop filter, then we must apply it to the entire layer (clip-bounds)
          
          
              // regardless of any hint-rect from the caller. skbug.com/8783
          
          
              if (rec.fBackdrop) {
          
          
                  bounds=nullptr;
          
          
              }
          
          
              // 兩種情況下會(huì)繪制背景內(nèi)容
          
          
              bool initBackdrop=(rec.fSaveLayerFlags & kInitWithPrevious_SaveLayerFlag) || rec.fBackdrop;
          
          
              // 省略代碼
          
          
              ……
          
          
              if (initBackdrop) {
          
          
                  DrawDeviceWithFilter(priorDevice, rec.fBackdrop, newDevice.get(), { ir.fLeft, ir.fTop }, fMCRec->fMatrix.asM33());
          
          
              }
          
          
              // 省略代碼
          
          
              ……
          
          
          }
          
          
          
          
          
          
          void SkCanvas::DrawDeviceWithFilter(SkBaseDevice* src, const SkImageFilter* filter, SkBaseDevice* dst, const SkIPoint& dstOrigin, const SkMatrix& ctm) {
          
          
              // 省略代碼
          
          
              ……
          
          
              // 截取當(dāng)前已經(jīng)繪制的內(nèi)容
          
          
              auto special=src->snapSpecial(backdropBounds);
          
          
              if (!special) {
          
          
                  return;
          
          
              }
          
          
              // 省略代碼
          
          
              …
          
          
              SkIPoint offset;
          
          
              // 使用指定的filter進(jìn)行處理
          
          
              special=as_IFB(filter)->filterImage(ctx).imageAndOffset(&offset);
          
          
              if (special) {
          
          
                  offset +=layerInputBounds.topLeft();
          
          
                  SkMatrix dstCTM=toRoot;
          
          
                  dstCTM.postTranslate(-dstOrigin.x(), -dstOrigin.y());
          
          
                  dstCTM.preTranslate(offset.fX, offset.fY);
          
          
                  // 將處理結(jié)果進(jìn)行繪制
          
          
                  dst->drawSpecial(special.get(), dstCTM, sampling, p);
          
          
              }
          
          
              // 省略代碼
          
          
              ……
          
          
          }
          
          

          結(jié)合注釋與代碼得知,有兩個(gè)組合可以實(shí)現(xiàn)背景模糊效果:

          • 組合一:backdrop設(shè)置為對(duì)應(yīng)的filter,比如SkBlurImageFilter即可,將saveLayerFlags設(shè)為0。
          • 組合二:backdrop仍然為null,但saveLayerFlags設(shè)為kInitWithPrevious_SaveLayerFlag,此時(shí)這個(gè)新建的layer會(huì)將之前l(fā)ayer的內(nèi)容復(fù)制一份。然后使用paint->setImageFilter(),將 filter 設(shè)置到 paint 中。


          區(qū)別在于,前者會(huì)將整個(gè) Canvas 的內(nèi)容都進(jìn)行處理,然后 clip 到相應(yīng)區(qū)域;而后者只會(huì)截取指定區(qū)域的內(nèi)容,另外也不會(huì)立即處理,而是選擇在新 layer 上屏的時(shí)刻統(tǒng)一處理。

          Flutter 使用的是第一個(gè)組合。筆者兩個(gè)方案都進(jìn)行了嘗試,本文與Flutter保持一致,討論第一種組合。


          思路解析

          看完 Flutter,我們?cè)賮砜纯?Android 里的saveLayer邏輯,經(jīng)過一些中轉(zhuǎn),它最終調(diào)用到了這里

          // File: frameworks/base/libs/hwui/SkiaCanvas.cpp
          
          
          int SkiaCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, SaveFlags::Flags flags) {
          
          
              const SkRect bounds=SkRect::MakeLTRB(left, top, right, bottom);
          
          
              // 這里只用到了 bounds, paint, layerFlags 三個(gè)參數(shù)
          
          
              const SkCanvas::SaveLayerRec rec(&bounds, paint, layerFlags(flags));
          
          
              return mCanvas->saveLayer(rec);
          
          
          }
          

          可以看到,Android 直接忽略了后面兩個(gè)參數(shù),并沒有提供任何暴露途徑。


          那我們的思路也就很簡(jiǎn)單了:

          1. 新增Canvas.saveLayer()的overload方法,增加backdropFilter參數(shù)。在內(nèi)部轉(zhuǎn)為對(duì)SaveLayerRec的后兩個(gè)參數(shù)的設(shè)置。

          1. 暴露對(duì)SkImageFilter對(duì)象的創(chuàng)建方法。如果是Android 12環(huán)境,則可以跳過這一步,使用RenderEffect.getNativeInstance()即可。

          1. 由于是對(duì) Canvas 的調(diào)用,簡(jiǎn)單的辦法是封裝成 Drawable,供 View 設(shè)置為background使用。
          <?xml version="1.0" encoding="utf-8"?>
          
          
          <coolx.graphics.drawable.BackdropBlurDrawable
          
          
              xmlns:android="http://schemas.android.com/apk/res/android"
          
          
              xmlns:app="http://schemas.android.com/apk/res-auto"
          
          
              app:blurRadius="30dp"
          
          
              app:saturation="1.8"
          
          
              app:fallbackColor="#AAFFFFFF">
          
          
              <shape
          
          
                  android:shape="rectangle">
          
          
                  <solid android:color="#BFEFEFEF"/>
          
          
              </shape>
          
          
          </coolx.graphics.drawable.BackdropBlurDrawable>
          
          

          實(shí)機(jī)效果演示


          優(yōu)點(diǎn)

          • 使用簡(jiǎn)單、效果好。將SkImageFilter進(jìn)行組合,可以輕松實(shí)現(xiàn)透亮的毛玻璃效果。圖例疊加了模糊和飽和度的修改,效果非常接近 iOS。
          • 兼容性高。模糊控件可以隨意動(dòng)畫、clip。


          缺點(diǎn)

          • 使用場(chǎng)景受限制。由于是調(diào)用Canvas接口,所以封裝為 Drawable 設(shè)置為 View 的背景更適合使用。此時(shí)也會(huì)受到與方案二相同的限制:雖然限制少一些,可以正常 clip圓角,但當(dāng) View 設(shè)置 alpha 的時(shí)候,模糊仍會(huì)失效
          • 需要修改系統(tǒng)源碼


          這個(gè)方案我提交到了AOSP[5],谷歌工程師給了兩個(gè)反饋:

          1. 性能差(very, very slow)。
          2. 在 alpha 的時(shí)候會(huì)失效。


          第一條反饋與實(shí)際表現(xiàn)不符合,關(guān)于性能是否符合要求,需要進(jìn)一步的調(diào)查和實(shí)驗(yàn)。

          但第二條確實(shí)如此,所以還需要繼續(xù)找尋新的解法。




          方案四:修改libhwui,增加模糊計(jì)算

          View 的 alpha 發(fā)生改變,其實(shí)是設(shè)置的RenderNode.setAlpha()方法。
          方案二與方案三,由于都使用了Canvas的接口,所以無論是重寫View.onDraw()方法,還是封裝 Drawable,這個(gè)調(diào)用指令都在該ViewRenderNode內(nèi)部。這樣當(dāng) alpha 變化時(shí),就無法獲取到背景內(nèi)容。

          除了 alpha 外,這次也打算將所有的邊界場(chǎng)景一次考慮清楚:View 的 transform、動(dòng)畫、clip 等。目前能想到的方案,是讓背景模糊邏輯脫離RenderNode


          參照前文,RenderNode的真正繪制,是在RenderNodeDrawable中,我們可以新定義一個(gè)BackdropFilterDrawable類型,與其平級(jí)。


          BackdropFilterDrawable的繪制順序,提前到RenderNodeDrawable之前即可。


          BackdropFilterDrawable類的關(guān)鍵邏輯如下:

          void BackdropFilterDrawable::onDraw(SkCanvas* canvas) {
          
          
              // 對(duì)后面內(nèi)容進(jìn)行截圖(并不會(huì)創(chuàng)建新的buffer),此截圖為Canvas完整截圖。
          
          
              auto backdropImage=canvas->getSurface()->makeImageSnapshot();
          
          
              // 從target RenderNode那里,同步properties,無論它是否在做動(dòng)畫、縮放、是否有clip等,都進(jìn)行同步。計(jì)算結(jié)果保存到 mImageSubset 里,這是我們上層RenderNode真正的可見區(qū)域。
          
          
              if (!prepareToDraw(canvas, properties, backdropImage->width(), backdropImage->height())) {
          
          
                  // 當(dāng)返回false的時(shí)候,說明不可見,則我們也跳過繪制。
          
          
                  return;
          
          
              }
          
          
          
          
          
          
              auto imageSubset=mImageSubset.roundOut();
          
          
              // 將截圖里的上層區(qū)域進(jìn)行filter處理。
          
          
              backdropImage=        backdropImage->makeWithFilter(canvas->recordingContext(), backdropFilter, imageSubset,
          
          
                                                    imageSubset, &mOutSubset, &mOutOffset);
          
          
              // 將filter結(jié)果進(jìn)行繪制。
          
          
              canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds,
          
          
                                    SkSamplingOptions(SkFilterMode::kLinear), &mPaint,
          
          
                                    SkCanvas::kStrict_SrcRectConstraint);
          
          
          }
          


          ,時(shí)長(zhǎng)00:06


          優(yōu)點(diǎn)

          與方案三相同,優(yōu)勢(shì)在于性能和效果。

          • 使用簡(jiǎn)單、效果好。將SkImageFilter進(jìn)行組合,可以輕松實(shí)現(xiàn)透亮的毛玻璃效果。圖例疊加了模糊和飽和度的修改,效果非常接近 iOS。
          • 兼容性高。模糊控件可以隨意動(dòng)畫、clip、變換 alpha。


          缺點(diǎn)

          • 使用場(chǎng)景受限制。雖然解決了方案二的 alpha 問題,但如果該控件的父布局設(shè)置了 alpha,它仍然無法拿到父布局以外的背景內(nèi)容。這算是個(gè)小小的遺憾。
          • 需要修改系統(tǒng)源碼。對(duì)系統(tǒng)源碼的改動(dòng),比方案三多不少。


          這個(gè)方案我也提交到了AOSP[6],目前狀態(tài)為待 Review。感興趣的可以編譯看下效果。





          方案對(duì)比



          我們?cè)诳崤蒀OOL 20s 5G上,將四種方案進(jìn)行橫向?qū)Ρ取?/span>

          這臺(tái)機(jī)器配置為天璣700、6GB內(nèi)存、128GB存儲(chǔ)、1080p 90Hz的屏幕。


          ,時(shí)長(zhǎng)00:02


          使用perfetto抓取的trace,來衡量和計(jì)算每種方案在Choreographer.doFrame()RenderThread分別消耗的時(shí)間。

          在perfetto里可以直觀地看到平均耗時(shí)


          抓取無模糊效果的耗時(shí),作為基準(zhǔn)指標(biāo)。分別為:doFrame 1.588ms, RenderThread: 3.485ms。

          最終對(duì)比結(jié)果如下:



          方案一

          方案二

          方案三

          方案四

          實(shí)現(xiàn)復(fù)雜度

          位移

          ??

          ??

          ??

          ??

          縮放

          ?

          內(nèi)容錯(cuò)誤

          ??

          ??

          ??

          clip圓角

          ??

          ?

          ??

          ??

          alpha

          ??

          ?

          ?

          ??

          parent alpha仍然不支持)

          視覺效果

          ?

          僅支持模糊

          ?

          僅支持模糊

          ??

          支持更多效果擴(kuò)展(如saturation)

          ??

          支持更多效果擴(kuò)展(如saturation)

          兼容性

          ??

          ?

          Android 12不兼容

          修改源碼

          修改源碼

          doFrame

          性能開銷

          +2.934ms

          (4.552ms)

          -0.167ms

          (1.421ms)

          -0.071ms

          (1.517ms)

          -0.047ms

          (1.547ms)

          RenderThread

          開銷

          +0.587ms

          (4.072ms)

          +1.678ms

          (5.163ms)

          +1.771ms

          (5.256ms)

          +1.579ms

          (5.064ms)


          方案一綜合開銷最高,后面三個(gè)方案的doFrame耗時(shí),與基準(zhǔn)耗時(shí)的差異在誤差范圍內(nèi),幾乎沒有引入額外的計(jì)算。


          綜合來看,方案四各方面表現(xiàn)都很優(yōu)秀,酷派在自研的COOLOS里,已經(jīng)有多處采用了它。





          后記



          經(jīng)過這樣一番調(diào)研,筆者的有很多感悟和提升,其中感觸最深的是:如果對(duì)一塊領(lǐng)域感興趣,但網(wǎng)絡(luò)和社區(qū)沒有更好解法時(shí),就自己讀源碼吧。


          在這個(gè)能力的調(diào)研過程中,有非常多有意思的技術(shù)問題,每一項(xiàng)都值得深入去探討和研究。

          比如:

          • 為什么有時(shí)候模糊邊緣會(huì)閃爍?
          • 模糊運(yùn)算原理是什么?模糊計(jì)算本身有沒有性能優(yōu)化空間?
          • 想使用酷派調(diào)研的方案,有沒有辦法不修改源碼來用到它們?
          • ……


          由于篇幅限制,本文不再展開,有機(jī)會(huì)可以開些續(xù)文,詳細(xì)講講。

          感興趣的讀者,歡迎評(píng)論區(qū)跟我們一起討論!



          參考鏈接

          1. Window Blurs | Android Open Source Project
          2. (https://source.android.com/devices/tech/display/window-blurs)
          3. Glide v4 : Hardware Bitmaps
          4. (https://bumptech.github.io/glide/doc/hardwarebitmaps.html#why-should-we-use-hardware-bitmaps)
          5. glCopyTexSubImage2D | khronos.org
          6. (https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glCopyTexSubImage2D.xml)
          7. RenderEffect | Android Developers
          8. (https://developer.android.com/reference/android/graphics/RenderEffect)
          9. 方案三 | Android Code Review
          10. (https://android-review.googlesource.com/c/platform/frameworks/base/+/1985086)
          11. 方案四 | Android Code Review
          12. (https://android-review.googlesource.com/c/platform/frameworks/base/+/2033223)

          作者:每天都吃麥當(dāng)勞

          來源-微信公眾號(hào):酷派技術(shù)團(tuán)隊(duì)

          出處:https://mp.weixin.qq.com/s/GJU2JxrjqRpyuJkrHJNC1w

          砂玻璃效果已經(jīng)在互聯(lián)網(wǎng)上流行了很多年,Mac OS以其磨砂玻璃效果而聞名,Windows 10也通過其他一些燈光,深度,運(yùn)動(dòng),材質(zhì),比例尺實(shí)現(xiàn)了磨砂玻璃的效果

          在CSS中使用磨砂玻璃效果時(shí),我們中的一些人知道該怎么做,而其他人仍會(huì)在百度搜索:

          怎么做??

          “ css光澤效果”
          “ css毛玻璃”
          “透明模糊背景css”
          “毛玻璃效果photoshop”
          “僅cs模糊背景”
          “ css玻璃窗格”
          “ css背景濾鏡”
          “ css模糊覆蓋物”
          “ css div后面的模糊背景”

          今天,我將展示僅CSS的一種方法,教你可以使用該方法在CSS中進(jìn)行磨砂玻璃效果。

          1,創(chuàng)建一個(gè)HTML標(biāo)記

          為簡(jiǎn)單起見,我將向你展示如何在空的div上制作磨砂玻璃效果。因此,HTML中所需的只是一個(gè)空的div。

          <div> </div>

          2.重置瀏覽器默認(rèn)樣式

          *{
           margin: 0;
           padding: 0;
          }

          3.添加背景圖片

          我們需要我們的背景占據(jù)頁面的整個(gè)寬度和高度,并且我們不想重復(fù)我們的背景,我們也希望我們的背景是固定的。我們希望背景是固定的,因?yàn)槲覀儾幌M院笤诶^承背景時(shí)將整個(gè)背景顯示在div中。

          body{
           background-image: url(http://bit.ly/2gPLxZ4); //add "" if you want
           background-repeat: no-repeat;
           background-attachment: fixed;
           background-size: cover;
          }

          4.現(xiàn)在給Div一些樣式

          現(xiàn)在,我們將使用背景繼承為div設(shè)置一些寬度和高度。我們還需要確定絕對(duì)位置,以確保疊加層不會(huì)占用網(wǎng)頁的整個(gè)寬度和高度

          div{
           background: inherit;
           width: 250px;
           height: 350px;
           position: absolute;
          }

          5,固定和不固定附件的示例

          現(xiàn)在,我們知道在固定了背景附件的情況下,我們只能看到div后面的div內(nèi)部的背景圖像區(qū)域,而這正是我們希望毛玻璃效果起作用的地方

          6,現(xiàn)在我們需要?jiǎng)?chuàng)建一個(gè)覆蓋層

          我們需要content: “”確保之前的偽類能夠正常工作。我們還從其父級(jí)繼承了背景,并且我們使用絕對(duì)位置將其在其父元素DIV中對(duì)齊。我們正在使用盒子陰影添加白色透明疊加層,并且正在使用模糊來模糊該疊加層。

          div:before{
           content: “ ”;
           background: inherit; 
           position: absolute;
           left: 0;
           right: 0;
           top: 0; 
           bottom: 0;
           box-shadow: inset 0 0 0 3000px rgba(255,255,255,0.3);
           filter: blur(10px);
          }

          7,修復(fù)DIV的不模糊邊緣

          現(xiàn)在,我們需要修復(fù)div的未模糊邊緣,為此,我們需要增加疊加層的尺寸,使其比其父尺寸稍高一些,然后將其上下位置設(shè)為負(fù)(-25)。我們還需要給它的父對(duì)象提供隱藏的溢出,以確保父DIV之外的任何覆蓋都不會(huì)顯示并被隱藏。

          div{
           background: inherit;
           width: 250px;
           height: 350px;
           position: absolute;
           overflow: hidden;  //adding overflow hidden
          }
          div:before{
           content: ‘’;
           width: 300px;
           height: 400px;
           background: inherit; 
           position: absolute;
           left: -25px;  //giving minus -25px left position
           right: 0;
           top: -25px;   //giving minus -25px top position 
           bottom: 0;
           box-shadow: inset 0 0 0 200px rgba(255,255,255,0.3);
           filter: blur(10px);
          }

          到這里,我們就實(shí)現(xiàn)了CSS的磨砂玻璃效果

          你還可以使用CSS屬性“backdrop-filter: blur(20px)”來實(shí)現(xiàn)此效果,該屬性在工作方面要容易得多,并且是更好的選擇,但兼容性還不是很強(qiáng)。


          主站蜘蛛池模板: 精品aⅴ一区二区三区| 国产女人乱人伦精品一区二区| 一区二区三区日本视频| 丝袜美腿一区二区三区| 麻豆果冻传媒2021精品传媒一区下载 | 国产成人精品无码一区二区三区| 精品国产日韩亚洲一区| 国产精品视频一区二区噜噜| 亚洲宅男精品一区在线观看| 精品国产一区二区三区免费| 日韩一区二区超清视频| 一区二区三区电影网| 一区二区三区亚洲| 日本一区二区三区在线看 | 亚洲综合一区国产精品| 国产成人一区二区精品非洲| 中文字幕人妻丝袜乱一区三区 | 亚洲综合一区无码精品| 国产精品第一区第27页| 日韩av无码一区二区三区| 国产无吗一区二区三区在线欢| 国产一区二区三区在线视頻 | 国产一区三区三区| 日韩一区二区三区四区不卡| 波多野结衣高清一区二区三区 | 国产精品高清一区二区三区| 日本国产一区二区三区在线观看| 国产天堂一区二区综合| 中字幕一区二区三区乱码 | 最新欧美精品一区二区三区| 久久99精品免费一区二区| 一区一区三区产品乱码| 中文字幕一区二区三区精华液| bt7086福利一区国产| 国产麻豆精品一区二区三区v视界 国产美女精品一区二区三区 | 动漫精品专区一区二区三区不卡| 3d动漫精品啪啪一区二区中| 区三区激情福利综合中文字幕在线一区亚洲视频1 | 天堂不卡一区二区视频在线观看| 亚洲无圣光一区二区 | 亚洲av成人一区二区三区观看在线|