Halo-智慧屏焦點動效實現(xiàn)方案

記得之前有人在文章下問過,華為智慧屏那種焦點框的實現(xiàn)扮宠。對于廠商來說西乖,優(yōu)先考慮最高效的實現(xiàn)方案,肯定是用c++編寫坛增,畢竟Android上層的繪制效率來說获雕,遠不及底層來的高效。
碰巧前段時間收捣,有個朋友他們公司有類似的需求届案,自己也不是特別忙,就抽空用上層實現(xiàn)幫朋友寫了一個通用組件罢艾。


取名Halo楣颠,光環(huán),已經(jīng)遠離游戲好多年了咐蚯,算是致敬下士官長吧童漩。題外話不說了,下面開始正文春锋。

我們先看看華為智慧屏的效果矫膨。當焦點選中海報,按鈕看疙,選項卡的時候,這些組件外圈都有一個光暈效果在環(huán)繞旋轉(zhuǎn)直奋,說實話能庆,在TV廠家的各種定制系統(tǒng)里,這焦點的動效設(shè)計真的是遙遙領(lǐng)先哈哈脚线。

看智慧屏的效果搁胆,會發(fā)現(xiàn)大部分原生可聚焦的組件都會自帶有這種效果,個人推測應該是系統(tǒng)統(tǒng)一客制化了這些基礎(chǔ)控件邮绿。
那我們獨立的應用開發(fā)渠旁,總不可能每個控件都去定制一遍實現(xiàn)吧,就像一些無縫換膚sdk的實現(xiàn)船逮,雖然是通過攔截的方式統(tǒng)一把原生控件替換成自定義對應的控件顾腊,但是內(nèi)部依舊需要維護一系列的自定義控件,去對應適配替換原生控件挖胃。因此杂靶,這里梆惯,首先淘汰定制化Button,TextView吗垮,CardView等等基礎(chǔ)控件的方式垛吗。說實話個人開發(fā)者很少會有精力和時間去將其都重新實現(xiàn)一遍。因此烁登,確定目標方案怯屉,通過wrapper方式包裹子控件實現(xiàn),自然就會考慮到輕量的ViewGroup:FrameLayout饵沧。

我們先來看個實現(xiàn)的效果圖吧锨络,GIF為了壓縮文件大小,降幀加速了捷泞,實際上是很流暢的足删。支持矩形,圓角锁右,圓形三種類型失受,支持光環(huán)顏色設(shè)置,環(huán)繞速度等:

halo.gif

這里的設(shè)計有一個地方其實把我卡殼了半天咏瑟,注意拂到,智慧屏上的效果是光環(huán)和內(nèi)部內(nèi)容區(qū)域是透明的。這樣一來码泞,也就不能簡單的直接往canvas上繪制了兄旬。
最初我想到了兩種方案:

  1. canvas進行save,然后按path裁剪后再繪制光暈余寥,恢復canvas后在繪制內(nèi)容區(qū)域领铐。這樣的確可以實現(xiàn)光環(huán)和內(nèi)容之間的間隔透明化,但是clipPath有一個大家都知道的致命缺點:鋸齒宋舷!當然绪撵,為了驗證效果,我還是實現(xiàn)了一遍祝蝠,結(jié)果卻有點意想不到音诈。總結(jié)一下:性能比較高效绎狭,在TV上和一些低版本的手機上的確存在明顯鋸齒细溅,尤其是圓形。但是在我的一加8儡嘶,android 11系統(tǒng)下喇聊,clipPath的圓滑程度竟然比下面的方案2還要完美。這就讓我尷尬了蹦狂,具體原因未知承疲,猜測是系統(tǒng)層面做了優(yōu)化邻耕,有知道的同學麻煩告知下。
  2. 使用PorterDuffXfermode混合模式燕鸽。這十多種模式兄世,說簡單簡單,說復雜也復雜啊研,坑是挺多的御滩,你按照說明和官方給的混合效果圖自己去寫,很大概率不會出現(xiàn)官方效果圖的結(jié)果党远∠鹘猓混合模式自己去看官方demo吧,這里就簡單說下沟娱,混合模式必須是bitmap的混合疊加氛驮,并且要注意srcdst先后順序。下面會介紹通過混合模式實現(xiàn)間隔透明化的具體實現(xiàn):

實現(xiàn)

  1. 首先我們聚焦點就是這個光環(huán)的光暈效果济似,它在動效執(zhí)行過程是繞著內(nèi)容移動的矫废,其實仔細想想,本質(zhì)上就是旋轉(zhuǎn)嘛砰蠢。漸變效果蓖扑,并且需要在旋轉(zhuǎn)過程中保持外環(huán)移動所在位置的漸變色相同,首選方案SweepGradient台舱,我們先上一段創(chuàng)建光環(huán)的代碼:
    private fun createHalo() {
        if (width > 0 && height > 0) {
            val shaderBound = sqrt((width * width + height * height).toDouble()).toInt()
            shaderBitmap = Bitmap.createBitmap(shaderBound, shaderBound, Bitmap.Config.ARGB_8888)
            val shaderCanvas = Canvas(shaderBitmap)
            val shaderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
                //      0.625      0.75       0.875
                //           +++++++++++++++++
                // white  0.5+---------------+0 white
                //           +++++++++++++++++
                //      0.375      0.25       0.125
                val shader = SweepGradient(shaderBound / 2f, shaderBound / 2f,
                        intArrayOf(haloColor, Color.TRANSPARENT, Color.TRANSPARENT, haloColor, Color.TRANSPARENT, Color.TRANSPARENT, haloColor),
                        floatArrayOf(0f, 0.125f, 0.375f, 0.5f, 0.625f, 0.875f, 1f)
                )
                this.shader = shader
            }
            shaderCanvas.drawCircle(shaderBound / 2f, shaderBound / 2f, shaderBound.toFloat(), shaderPaint)
            shaderLeft = -(shaderBound - width) / 2f
            shaderTop = -(shaderBound - height) / 2f
        }
    }

我們先分析下下圖律杠,白色是我們的canvas區(qū)域,我們的光環(huán)shader是圓形竞惋,在旋轉(zhuǎn)過程中要始終環(huán)繞在內(nèi)容區(qū)域外框柜去,那該shader的圓形半徑就是canvas的對角線的一半。上面提到混合模式是作用于bitmap拆宛,因此我們需要把shader繪制到一張bitmap上嗓奢,而這張bitmap的尺寸就如圖所示:

  1. 至此,我們完成了第一步胰挑,創(chuàng)建了一個光環(huán)效果蔓罚。說到光環(huán)和內(nèi)容區(qū)域的透明間隔椿肩,用混合模式怎么實現(xiàn)呢瞻颂?有同學了解過SurfaceView的原理吧,挖孔郑象,這個名詞應該聽過贡这。我這里采取的就是這種方式,通過一張挖孔bitmap與光環(huán)bitmap進行混合厂榛,達到把實體的光環(huán)圖中間挖出一個透明區(qū)域盖矫,供內(nèi)容繪制丽惭,haloStrokeWidth是我們光環(huán)的寬度,左右上下各減去光環(huán)寬度辈双,剩余的canvas區(qū)域就是我們繪制內(nèi)容的區(qū)域了:
    private fun createHole() {
        if (width > 0 && height > 0) {
            val holeWidth = width - haloStrokeWidth * 2
            val holeHeight = height - haloStrokeWidth * 2
            holeBitmap = Bitmap.createBitmap(holeWidth.toInt(), holeHeight.toInt(), Bitmap.Config.ARGB_8888)
            val holeCanvas = Canvas(holeBitmap)
            val holePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
                color = Color.WHITE
                style = Paint.Style.FILL
            }
            when (shapeType) {
                SHAPE_RECT -> {
                    holeCanvas.drawRect(0f, 0f, holeWidth, holeHeight, holePaint)
                }
                SHAPE_ROUND_RECT -> {
                    holeCanvas.drawRoundRect(0f, 0f, holeWidth, holeHeight, cornerRadius.toFloat(), cornerRadius.toFloat(), holePaint)
                }
                SHAPE_CIRCLE -> {
                    holeCanvas.drawCircle(holeWidth / 2f, holeHeight / 2f, holeWidth / 2f, holePaint)
                }
            }
        }
    }
  1. 至此责掏,我們就創(chuàng)建了shaderBitmapholeBitmap兩張圖片。開始混合運算湃望,我們先通過混合把外圈的光環(huán)繪制處理好换衬,再將剩余區(qū)域交給原生的繪制流程進行內(nèi)容區(qū)域(ChildView)的繪制。同時我們構(gòu)建一個基礎(chǔ)的ValueAnimate進行動畫運算证芭,不斷旋轉(zhuǎn)重繪就能產(chǎn)生光環(huán)環(huán)繞移動的效果啦:
    private val holePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
    }

    override fun dispatchDraw(canvas: Canvas?) {
        if (isFocused && canvas != null) {
            canvas.drawBitmap(holeBitmap, haloStrokeWidth, haloStrokeWidth, null)
            canvas.let {
                canvas.save()
                canvas.rotate(degrees, centerX, centerY)
                canvas.drawBitmap(shaderBitmap, shaderLeft, shaderTop, holePaint)
                canvas.restore()
            }
        }
        super.dispatchDraw(canvas)
    }
  1. 核心代碼就上面這些瞳浦,剩下就是一些形狀類型處理,資源釋放废士,自定義屬性叫潦,對外暴露設(shè)置參數(shù)方法等常規(guī)操作了。
  2. 最后看看使用方式:
    <com.seagazer.halo.Halo
        android:id="@+id/halo2"
        android:layout_width="230dp"
        android:layout_height="150dp"
        android:layout_marginStart="30dp"
        app:haloColor="#FFFF61" //光環(huán)顏色
        app:haloCornerRadius="10dp" //光環(huán)圓角(設(shè)置圓角時需要設(shè)置)
        app:haloInsertEdge="8dp" //光環(huán)與內(nèi)容的間距(不能小于光環(huán)寬度)
        app:haloShape="roundRect" //光環(huán)類型:直角官硝,圓角矗蕊,圓形
        app:haloWidth="3dp">// 光環(huán)的寬度

        <androidx.cardview.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:cardBackgroundColor="@color/halo_card"
            app:cardCornerRadius="8dp">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:text="Round Rect"
                android:textColor="@color/white"
                android:textSize="18sp" />
        </androidx.cardview.widget.CardView>
    </com.seagazer.halo.Halo>

時間不早了,年紀大了泛源,得早點休息拔妥,也就不多寫了,完整代碼和demo大家自己去看吧达箍,喜歡的話點個贊支持下吧没龙。
項目地址: https://github.com/seagazer/halo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缎玫,隨后出現(xiàn)的幾起案子硬纤,更是在濱河造成了極大的恐慌,老刑警劉巖赃磨,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筝家,死亡現(xiàn)場離奇詭異,居然都是意外死亡邻辉,警方通過查閱死者的電腦和手機溪王,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來值骇,“玉大人莹菱,你說我怎么就攤上這事≈ù瘢” “怎么了道伟?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我蜜徽,道長祝懂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任拘鞋,我火速辦了婚禮砚蓬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盆色。我一直安慰自己怜械,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布傅事。 她就那樣靜靜地躺著缕允,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蹭越。 梳的紋絲不亂的頭發(fā)上障本,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音响鹃,去河邊找鬼驾霜。 笑死,一個胖子當著我的面吹牛买置,可吹牛的內(nèi)容都是我干的粪糙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼忿项,長吁一口氣:“原來是場噩夢啊……” “哼蓉冈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起轩触,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤寞酿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后脱柱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伐弹,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年榨为,在試婚紗的時候發(fā)現(xiàn)自己被綠了惨好。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡随闺,死狀恐怖日川,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情板壮,我是刑警寧澤逗鸣,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布合住,位于F島的核電站绰精,受9級特大地震影響撒璧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笨使,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一卿樱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧硫椰,春花似錦级野、人聲如沸侈离。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潜索。三九已至,卻和暖如春聊记,著一層夾襖步出監(jiān)牢的瞬間先改,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工派继, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宾袜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓驾窟,卻偏偏與公主長得像庆猫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绅络,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容