RE: 从零开始的车载Android HMI(一) - Lottie

手机游戏开发者 2024-9-28 09:43:57 50 0 来自 中国
1.前言

多年从前汽车照旧以机器仪表主体的年代,各大汽车主机厂商并不非常关注利用体系UI的交互功能,但是随着车载SOC算力的不绝进步以及主机厂商对汽车座舱竞争的白热化。座舱的HMI在计划上在夸大功能性的同时也开始关注UI的艺术性,HMI的计划师们渴望艺术与功能应该协同工作,让用户沉醉在“第三空间”的体验中。
有了需求步伐员就必要关注怎样实验和落地,然而Android应用自己固然有着完备的动画框架支持,但是开发复杂、调试耗时,大型的gif或逐帧动画对于CPU&内存占用都不太抱负,以是许多Android的手机应用根本上不怎么有动画。而且车载HMI上越来越多的开始引入各种光影、粒子结果,假如基于Android的原生控件来实现这些粒子结果,难度非常大,这就必要本日的主角Lottie来实现了。

1.gif
2.Lottie概述

Lottie是一种基于JSON的动画文件格式,它使计划师可以大概在任何平台上发布动画,就像发布静态资产一样简单。它们是在任何装备上工作的小文件,可以在不举行像素化的情况下放大或缩小。
GitHub:https://github.com/airbnb/lottie-android
官方文档:http://airbnb.io/lottie/
Lottie在车载HMI中的上风

适量图形,不会出现失真
占用空间比序列帧动画小
可以修改属性,动态天生可交互的动画(使用视频动画难以实现交互功能)
节流HMI的开发、调试时间
可以更轻松的实现粒子、光影等殊效
Lottie的使用方法


  • 在build.gradle中添加依靠
dependencies {  def lottieVersion = "5.2.0"  implementation 'com.airbnb.android:lottielottieVersion'}

  • 使用LottieAnimationView
    起首将lottie动画的json文件放在assets文件夹下


然后就可以在布局文件中使用LottieAnimationView了
<com.airbnb.lottie.LottieAnimationView    android:id="@+id/dynamic_text"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    app:lottie_fileName="HamburgerArrow.json"    app:lottie_autoPlay="true"    app:lottie_loop="true"/>然后运行APP就可以看到动画结果

3.Lottie的常用属性&API

LottieAnimationView继续自AppCompatImageView,以是ImageView支持的属性,LottieAnimationView都是支持的,这部分就不再先容了。

  • lottie_fileName
    设定lottie动画所对应的json文件地点。json文件默认必要放置在assets下,设定时不必要再夸大assets
app:lottie_fileName="HamburgerArrow.json"假如设定 app:lottie_fileName="other/HamburgerArrow.json",那么lottie就会读取assets/other/HamburgerArrow.json。
void setAnimationFromJson(String jsonString, @Nullable String cacheKey)

  • lottie_rawRes
    设定lottie动画的json文件地点。json文件除了可以放置assets文件夹下,还可以放在raw文件夹下。使用时必要留意,使用lottie_rawRes引入资源时,json文件名前必要加上@raw,而且文件名不带.json后缀
app:lottie_rawRes="@raw/name"

  • lottie_autoPlay
    设定是否主动播放,取值为true | false
  • lottie_loop
    设定是否循环播放,取值为true | false
  • lottie_url
    当必要加载在线资源时,就可以使用lottie_url
    void setAnimationFromUrl(String url)
    void setAnimationFromUrl(String url, @Nullable String cacheKey)
  • lottie_fallbackRes
    设置一个drawable,假如lotticomposition由于任何缘故起因未能加载,则将出现该drawable。
    假如这是网络动画,可以使用它向用户体现错误,也可以添加一个失败的监听器重试下载。
    void setFallbackResource(@DrawableRes int fallbackResource)
  • lottie_repeatMode
    设定循环播放的序次。取值为restart | reverse 。restart表示正常循环播放,reverse表示倒序播放
    void setRepeatMode(@LottieDrawable.RepeatMode int mode)
    int getRepeatMode()
  • lottie_repeatCount
    设定循环播放次数,取值为整数范例。
    void setRepeatCount(int count)
    int getRepeatCount()
  • lottie_imageAssetsFolder
    设定图片文件在assets文件夹下的访问路径。有的时间使用AE导出lottie的json时也会导出一些图片,这时间就必要该属性设定图片的地点。
    void setImageAssetsFolder(String imageAssetsFolder)
    String getImageAssetsFolder()
  • void setFrame(int frame)
    将进度设置为指定的帧。将进度设置为指定的帧。假如尚未设置合成,则进度将在设置时设置为帧。
    通过int getFrame()可以获取当前渲染的帧。
  • void setMaxFrame(int endFrame)
    设置播放或循环时动画将竣事的最大帧。
    该值将被钳制到合成界限。比方,设置整数最大值将产生与合成雷同的结果。
    通过float getMaxFrame()可以获取当前设定的最大帧
  • void setMinFrame(int startFrame)
    设置播放或循环时动画开始的最小帧。
    设定最大、最小帧可以只播放lottie动画中的一部分,比方下面的两张图,第一张是完备的从0播放到183帧,第二张则是从60播放到100帧。



  • lottie_progress
    设定动画初次体现时的进度,范例为float。取值范围0.0 ~ 1.0
    void setProgress(@FloatRange(from = 0f, to = 1f) float progress)
    float getProgress()
  • lottie_speed
    设定播放速率,取值范例为float。当速率<1时,动画会慢放,当速率<0时,可以实现倒序播放。
    void setSpeed(float speed)
    float getSpeed()
    void reverseAnimationSpeed():反转当前动画速率。这不会播放动画。


    速率是一个比力告急的属性,与progress、frame等属性一起机动运用,我们就可以轻松地在HMI上实现炫酷而复杂的仪表盘结果,这对车载HMI尤为告急。
  • lottie_enableMergePathsForKitKatAndAbove
    设定是否开启MergePath属性,取值为true | false。默以为false
    void enableMergePathsForKitKatAndAbove(boolean enable)
    boolean isMergePathsEnabledForKitKatAndAbove()
  • void playAnimation()
    重新开始播放动画。假如速率<0,它将从尽头开始,并向出发点播放。必须在主线程中调用。
  • void cancelAnimation()
    取消动画,必须在主线程中调用。
  • void pauseAnimation()
    停息动画,必须在主线程中调用。
  • void resumeAnimation()
    从当前位置继续播放动画。假如速率<0,它将从当前位置向后播放。必须在主线程中调用。
  • long getDuration()
    获取动画的播放时长。
  • void setTextDelegate(TextDelegate textDelegate)
    设置此选项可在运行时用自定义文本更换动画文本
  • lottie_cacheComposition
    设定是否开启缓存,取值 true | false,默认开启。开启缓存可以提升动画的加载效率。
void setCacheComposition(boolean cacheComposition)

  • lottie_ignoreDisabledSystemAnimations
    答应忽略体系动画设置,因此纵然禁用动画,也答应运举措画。取值 true | false,默以为false。
    void setIgnoreDisabledSystemAnimations(boolean ignore)
  • lottie_clipToCompositionBounds
    设置lottie是否应剪辑到原始动画合成界限。设置为true时,父视图大概必要禁用clipChildren,以便Lottie可以在LottieAnimationView界限之外举行渲染。默以为true。
    void setClipToCompositionBounds(boolean clipToCompositionBounds)
  • lottie_renderMode
    设定渲染模式,取值为 automatic | hardware | software。设定渲染模式为hardware时,可以明显提升动画的渲染效率,但是有些体系函数大概并不支持硬件加速,现实使用时必要结合调试时的结果选择是否开启。
    void setRenderMode(RenderMode renderMode)
    RenderMode getRenderMode()
  • void addAnimatorListener(Animator.AnimatorListener listener)
    添加动画的属性监听。
    对应也提供了removeUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener)用来移除指定的监听。或者也可以使用removeAllAnimatorListeners()移除全部监听。
binding.animationView.addAnimatorUpdateListener(object : ValueAnimator.AnimatorUpdateListener {    override fun onAnimationUpdate(animation: ValueAnimator?) {    }})

  • void addAnimatorPauseListener(Animator.AnimatorPauseListener listener)
    添加动画停息/规复监听。
    对应也提供了removeAnimatorPauseListener(Animator.AnimatorPauseListener listener)用来移除指定的监听。
binding.animationView.addAnimatorPauseListener(object : Animator.AnimatorPauseListener{    override fun onAnimationPause(animation: Animator?) {            }    override fun onAnimationResume(animation: Animator?) {            }    })

  • void addAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener)
    添加动画发生更新时的监听
    对应也提供了removeUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener)用来移除指定的监听。或者也可以使用removeAllUpdateListeners()移除全部监听。
binding.animationView.addAnimatorUpdateListener(object : ValueAnimator.AnimatorUpdateListener{    override fun onAnimationUpdate(animation: ValueAnimator?) {            }})

  • void addValueCallback(KeyPath keyPath, T property, LottieValueCallback<T> callback)
    监听lottie动画json中某个片断的属性。
    此keypath可以剖析为多个内容,在这种情况下,回调的值将应用于全部回调。在内部会起首查抄是否已使用resolveKeyPath(KeyPath)剖析keypath,假如尚未剖析,则将对其举行剖析。
    8.png
    Lottie动画的Json中属性都是英文简写,我们很难把json中key与现实的属性对应起来,以是有了第二个参数LottieProperty,它的内部定义了大量的属性,当我们必要修改json时,只必要传入LottieProperty中属性即可。
    比方,必要监听json中LeftArmWave的持续时间,就可以这么写
animationView.addValueCallback(KeyPath("LeftArmWave"), LottieProperty.TIME_REMAP) { frameInfo ->} 4.Lottie的常见用法

Lottie的Demo中内置了许多官方自己开发的动画结果,目的是为我们展示Lottie的常见用法,作为开发者我们必须把握,并在恰当的时间运用到我们的应用中。
动态属性结果

该结果展示了lottie支持动态修改json,让动画中的一小部分属性发生改变。

  • 修改局部动画的速率
binding.animationView.addValueCallback(KeyPath("LeftArmWave"), LottieProperty.TIME_REMAP) { frameInfo ->2 * speed.toFloat() * frameInfo.overallProgress}KeyPath中的LeftArmWave是Json中的一个属性


修改的结果如下。留意看右手的摆动频率X3后比X1高,以至于录制的GIF直接丢帧了。

10.gif

  • 修改局部动画的颜色
val shirt = KeyPath("Shirt", "Group 5", "Fill 1")val leftArm = KeyPath("LeftArmWave", "LeftArm", "Group 6", "Fill 1")val rightArm = KeyPath("RightArm", "Group 6", "Fill 1")binding.animationView.addValueCallback(shirt, LottieProperty.COLOR) { COLORS[colorIndex] }binding.animationView.addValueCallback(leftArm, LottieProperty.COLOR) { COLORS[colorIndex] }binding.animationView.addValueCallback(rightArm, LottieProperty.COLOR) { COLORS[colorIndex] } 修改后的结果如下

11.gif

  • 修改局部动画的活动范围
val point = PointF()binding.animationView.addValueCallback(    KeyPath("Body"),    LottieProperty.TRANSFORM_POSITION) { frameInfo ->val startX = frameInfo.startValue.x    var startY = frameInfo.startValue.y    var endY = frameInfo.endValue.y    if (startY > endY) {        startY += EXTRA_JUMP[extraJumpIndex]    } else if (endY > startY) {        endY += EXTRA_JUMP[extraJumpIndex]    }    point.set(startX, lerp(startY, endY, frameInfo.interpolatedKeyframeProgress))    point} 修改后的结果如下

12.gif 动画笔墨结果

13.gif
该结果展示了动画笔墨结果。这个结果实现起来实在不难,从步伐中捕获输入的字母,再更换成lottie的资源文件即可。
val letter = "" + Character.toUpperCase(event.unicodeChar.toChar()) val fileName = "Mobilo/$letter.json"LottieCompositionFactory.fromAsset(context, fileName)    .addListener { addComposition(it) } 动态笔墨结果


14.gif
该结果展示动态更换动画中的笔墨。使用setTextDelegate就可以在动画运行中修改lottie动画中的笔墨
val textDelegate = TextDelegate(binding.dynamicTextView)binding.nameEditText.addTextChangedListener(object : TextWatcher {    override fun afterTextChanged(s: Editable?) {        textDelegate.setText("NAME", s.toString())    }    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}})binding.dynamicTextView.setTextDelegate(textDelegate)留意,这里实在用了两个lottieView,分别设定了差别的笔墨。
<LinearLayout    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:layout_gravity="center_horizontal"    androidrientation="horizontal">    <com.airbnb.lottie.LottieAnimationView        android:id="@+id/originalTextView"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginRight="16dp"        app:lottie_rawRes="@raw/name"        app:lottie_autoPlay="true"        app:lottie_loop="true"/>    <com.airbnb.lottie.LottieAnimationView        android:id="@+id/dynamicTextView"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        app:lottie_rawRes="@raw/name"        app:lottie_autoPlay="true"        app:lottie_loop="true"/></LinearLayout>手势交互结果



该结果展示了Lottie的手势交互。实在和第一个结果实现思绪雷同,都是通过addValueCallback修改json中的属性来实现的。
override fun onCreate(savedInstanceState: Bundle?) {    super.onCreate(savedInstanceState)    val largeValueCallback = LottieRelativePointValueCallback(PointF(0f, 0f))    binding.animationView.addValueCallback(KeyPath("First"), LottieProperty.TRANSFORM_POSITION, largeValueCallback)    val mediumValueCallback = LottieRelativePointValueCallback(PointF(0f, 0f))    binding.animationView.addValueCallback(KeyPath("Fourth"), LottieProperty.TRANSFORM_POSITION, mediumValueCallback)    val smallValueCallback = LottieRelativePointValueCallback(PointF(0f, 0f))    binding.animationView.addValueCallback(KeyPath("Seventh"), LottieProperty.TRANSFORM_POSITION, smallValueCallback)    var totalDx = 0f    var totalDy = 0f    val viewDragHelper = ViewDragHelper.create(binding.containerView, object : ViewDragHelper.Callback() {        override fun tryCaptureView(child: View, pointerId: Int) = child == binding.targetView        override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {            return top        }        override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {            return left        }        override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) {            totalDx += dx            totalDy += dy            smallValueCallback.setValue(getPoint(totalDx, totalDy, 1.2f))            mediumValueCallback.setValue(getPoint(totalDx, totalDy, 1f))            largeValueCallback.setValue(getPoint(totalDx, totalDy, 0.75f))        }    })    binding.containerView.viewDragHelper = viewDragHelper}在RecyclerView中使用

16.gif
该结果展示通过监听点击变瞎搅播放差别的lottie动画。这个结果最常见,APP中的点赞结果大多都是如许的实现思绪。
5.总结

在车载HMI开发中每每我们会在实现、调试UI上耗费大量的时间,假如可以大概机动的运用Lottie,就可以明显节流步伐的开发时间。比方,光影、粒子等殊效固然可以也思量用Kanzi等3D引擎实现,但是3D引擎会斲丧成倍的SOC性能,现实开发过程中,简单的殊效使用Lottie实现,可以极大的优化应用的性能,给用户一个更良好的体验。
固然这齐备的条件是,UI计划师乐意为步伐员切出一套Lottie的动画(F**K!)
本篇许多内容参考了《Android自定义控件高级进阶与出色实例(博文视点出品)》(启舰)【摘要 书评 试读】- 京东图书 这本书的内容,写得相当不错,非常值得认真阅读。
下一篇来讲讲车载HMI开发时都会用到的一个体系组件 - Widget
参考资料

还不知道什么是汽车HMI计划?进来带你快速相识
《Android自定义控件高级进阶与出色实例(博文视点出品)》(启舰)【摘要 书评 试读】- 京东图书
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 18:24, Processed in 0.169297 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表