美文网首页
Halo-智慧屏焦点动效实现方案

Halo-智慧屏焦点动效实现方案

作者: seagazer | 来源:发表于2021-03-17 23:06 被阅读0次

记得之前有人在文章下问过,华为智慧屏那种焦点框的实现。对于厂商来说,优先考虑最高效的实现方案,肯定是用c++编写,毕竟Android上层的绘制效率来说,远不及底层来的高效。
碰巧前段时间,有个朋友他们公司有类似的需求,自己独立开发的个人项目刚好用的上类似效果,就抽空用上层实现帮朋友写了一个通用组件。


取名Halo,光环,已经远离游戏好多年了,算是致敬下士官长吧。题外话不说了,下面开始正文。

我们先看看华为智慧屏的效果。当焦点选中海报,按钮,选项卡的时候,这些组件外圈都有一个光晕效果在环绕旋转,说实话,在TV厂家的各种定制系统里,这焦点的动效设计真的是甩开其他家很多。

接触过鸿蒙开发的同学,会发现即使使用原生button也会带有这种效果,可以推测应该是系统对这些基础控件都做了处理。
那我们独立的应用开发,总不可能每个控件都去定制修改一遍实现吧,就像一些无缝换肤sdk的实现,虽然是通过Factory的方式统一拦截,把原生控件替换成自定义对应的控件,但是内部依旧需要维护一系列的自定义控件,去对应适配替换原生控件。因此,这里,首先淘汰定制化Button,TextView,CardView等等基础控件的方式。说实话我也没精力和时间去帮都实现一遍。因此,确定目标方案,通过wrapper方式包裹子控件实现,自然就会考虑到轻量的ViewGroup:FrameLayout。

我们先来看个实现的效果图吧,GIF为了压缩文件大小,降帧加速了,实际上是很流畅的。支持矩形,圆角,圆形三种类型,支持光环颜色设置,环绕速度等:

halo.gif

这里有一个地方其实把我卡壳了半天,注意,智慧屏上的效果是光环和内部内容区域是透明的。这样一来,也就不能简单的直接往canvas上绘制了。
最初我想到了两种方案:

  1. canvas进行save,然后按path裁剪后再绘制光晕,恢复canvas后在绘制内容区域。这样的确可以实现光环和内容之间的间隔透明化,但是clipPath有一个大家都知道的致命缺点:锯齿!当然,为了验证效果,我还是实现了一遍,结果却有点意想不到。总结一下:性能比较高效,在TV上和一些低版本的手机上的确存在明显锯齿,尤其是圆形。但是在我的一加8,android 11系统下,clipPath的圆滑程度竟然比下面的方案2还要完美。这就让我尴尬了,具体原因未知,猜测是系统层面做了优化,有知道的同学麻烦告知下。
  2. 使用PorterDuffXfermode混合模式。这十多种模式,说简单简单,说复杂也复杂,坑是挺多的,你按照说明和官方给的混合效果图自己去写,很大概率不会出现官方效果图的结果。混合模式自己去看官方demo吧,这里就简单说下,混合模式必须是bitmap的混合叠加,并且要注意srcdst先后顺序。下面会介绍通过混合模式实现间隔透明化的具体实现:

实现

  1. 首先我们聚焦点就是这个光环的光晕效果,它在动效执行过程是绕着内容移动的,其实仔细想想,本质上就是旋转嘛。渐变效果,并且需要在旋转过程中保持外环移动所在位置的渐变色相同,首选方案SweepGradient,我们先上一段创建光环的代码:
    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区域,我们的光环shader是圆形,在旋转过程中要始终环绕在内容区域外框,那该shader的圆形半径就是canvas的对角线的一半。上面提到混合模式是作用于bitmap,因此我们需要把shader绘制到一张bitmap上,而这张bitmap的尺寸就如图所示:

  1. 至此,我们完成了第一步,创建了一个光环效果。说到光环和内容区域的透明间隔,用混合模式怎么实现呢?有同学了解过SurfaceView的原理吧,挖孔,这个名词应该听过。我这里采取的就是这种方式,通过一张挖孔bitmap与光环bitmap进行混合,达到把实体的光环图中间挖出一个透明区域,供内容绘制,haloStrokeWidth是我们光环的宽度,左右上下各减去光环宽度,剩余的canvas区域就是我们绘制内容的区域了:
    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. 至此,我们就创建了shaderBitmapholeBitmap两张图片。开始混合运算,我们先通过混合把外圈的光环绘制处理好,再将剩余区域交给原生的绘制流程进行内容区域(ChildView)的绘制。同时我们构建一个基础的ValueAnimate进行动画运算,不断旋转重绘就能产生光环环绕移动的效果啦:
    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. 核心代码就上面这些,剩下就是一些形状类型处理,资源释放,自定义属性,对外暴露设置参数方法等常规操作了。
  2. 最后看看使用方式:
    <com.seagazer.halo.Halo
        android:id="@+id/halo2"
        android:layout_width="230dp"
        android:layout_height="150dp"
        android:layout_marginStart="30dp"
        app:haloColor="#FFFF61" //光环颜色
        app:haloCornerRadius="10dp" //光环圆角(设置圆角时需要设置)
        app:haloInsertEdge="8dp" //光环与内容的间距(不能小于光环宽度)
        app:haloShape="roundRect" //光环类型:直角,圆角,圆形
        app:haloWidth="3dp">// 光环的宽度

        <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

相关文章

  • Halo-智慧屏焦点动效实现方案

    记得之前有人在文章下问过,华为智慧屏那种焦点框的实现。对于厂商来说,优先考虑最高效的实现方案,肯定是用c++编写,...

  • [iOS] 视频添加动效水印实现介绍

    [iOS] 视频添加动效水印实现介绍 [iOS] 视频添加动效水印实现介绍

  • 大屏动效设计

    背景动效: 背景数字流变成二进制码 主导航光旋特效微微闪烁 主导航 底部椭圆形光晕,放大效果闪烁 ————————...

  • 动效解决方案 Animate.css

    一、 Animate.css 是一个跨浏览器的动效基础库,是许多基础动效的解决方案。从经典的弹跳动效到独特的扭曲动...

  • Element-UI 问题记录(3/3)

    一. Tags 组件动效异常 使用列表渲染时,如果使用index作为key的话,会导致组件的动效异常。 解决方案:...

  • iOS 图片使用探究(2)-- iOS 动效方案对比

    动效方案特性SVGA支持跨端、高效(动画由设计师实现)、高性能(播放性能优于GIF、WebP,需测试)、文件小、支...

  • 最全的动效函数曲线

    最全的动效函数曲线速查表,程序和设计实现无缝对接效果 动效的运用能对产品起到加分作用,流畅的动效不仅能引导用户使用...

  • Lottie使用小结

    由于项目中需要实现一个比较复杂的动效,比较了一下VectorDrawable和Lottie两种方案,最终还是采用了...

  • android微动效

    感觉android的各种动画和动效交互真的很麻烦,好多的东西需要喇嘛,好的各种效果,各种实现方案 先看SVG矢量图...

  • 实现前端弹簧动效

    原文链接参考文章npm用到了一个css-spring的库虽然这些动画不是页面重要的部分,但是感觉这些小动画会提升页...

网友评论

      本文标题:Halo-智慧屏焦点动效实现方案

      本文链接:https://www.haomeiwen.com/subject/eihycltx.html