【Android】Lottie - 实现炫酷的Android导航栏动画
【Android】Lottie - 实现炫酷的Android导航栏动画
文章目录
- 【Android】Lottie - 实现炫酷的Android导航栏动画
- Lottie是什么?
- 效果展示
- 使用步骤
- 1.添加Lottie动画依赖
- 2.下载Lottie JSON文件并导入到Android Studio中
- 3.在代码中引用Lottie动画资源
- 3.1 在xml中引用
- 3.2 在代码中设置
- 3.3添加动画监听器
- 4.自定义BottomNavigationView实现炫酷的底部导航栏点击动画
- 4.1 使用效果
- 4.2 自定义LottieBottomNavigationView继承自BottomNavigationView
- 4.3 在xml中使用LottieBottomNavigationView
- 4.4 在代码中设置Lottie图标
Lottie是什么?
Lottie 是一个由Airbnb 开发并开源的高级动画解决方案,它能将设计师用 After Effects 制作的动画导出为轻量的 JSON 文件,让我们可以轻松地在移动端(iOS/Android)、Web 以及桌面端渲染高质量的矢量动画。
简单来说,Lottie 就像一个“动画播放器”,它能够解析用 After Effects 制作的动画,并完美地还原在各类应用和网站上。
Lottie项目地址:https://github.com/airbnb/lottie-android
效果展示

以上是基于Lottie实现的炫酷的移动端动画,而在项目中应用Lottie却非常简单,接下来主要介绍Lottie在Android Studio中的使用,最后会介绍怎么在BottomNavigationView中使用Lottie动画作为图标。
使用步骤
分享几个我经常用的Lottie动画资源网站:
https://lottiefiles.com/ (动画非常多,不过要付费)
https://lordicon.com/(虽然动画少点,不过质量还可以,最重要的是完全免费)
https://icons8.com/icons/set/popular–animated(npc,还行吧)
1.添加Lottie动画依赖
在app下的build.gradle中添加依赖,并点击Syan Now
dependencies {implementation "com.airbnb.android:lottie:6.4.0"
}
最新版本可以访问GitHub项目地址查看
2.下载Lottie JSON文件并导入到Android Studio中
动画Json文件下载后,在AS中的app\src\main目录下创建assets文件夹,将下载好的动画JSON文件复制到这个目录下,这样就可以直接在代码中引用了。

这里还有另一种方式,在res目录下创建raw文件夹,然后把JSON文件复制到这个目录下,两种方式区别不大,也就是在代码中的引用方式不同而已,这个后面会说到。

接下来就可以在xml布局或者代码中设置Lottie了
3.在代码中引用Lottie动画资源
3.1 在xml中引用
在布局文件中添加LottieAnimationView组件
<com.airbnb.lottie.LottieAnimationViewandroid:id="@+id/lottieAnimationView"android:layout_width="wrap_content"android:layout_height="wrap_content"app:lottie_rawRes="@raw/ic_video"app:lottie_autoPlay="true"app:lottie_loop="true“/>
这里通过app:lottie_rawRes来指定为raw目录下的动画资源,另外要是指定assets目录下的JSON,可以使用app:lottie_fileName属性,例如
app:lottie_fileName="ic_video.json"
没什么区别,只是app:lottie_fileName可能会存在找不到路径的错误,在代码中设置记得加上异常处理
常用属性:
| 属性 | 说明 | 取值 |
|---|---|---|
app:lottie_autoPlay | 是否自动播放 | true/false(默认false) |
app:lottie_loop | 是否循环播放 | true/false(默认false) |
app:lottie_repeatCount | 循环次数 | 整数或 -1(无限循环) |
app:lottie_repeatMode | 循环模式 | restart(重新开始) / reverse(倒放) |
app:lottie_speed | 播放速度 | 浮点数,如 1.0(正常), 2.0(2倍速), -1.0(倒放) |
另外,在xml中设置动画资源后,直接运行是没有任何效果的,因为这时Lottie动画还没有加载,你需要在代码中加载动画并开始播放动画才能生效。不过在xml中设置app:lottie_autoPlay,app:lottie_loop这两个属性为true后直接运行是可以看到动画的,这是因为设置app:lottie_autoPlay 后,动画会自动加载并开始播放,配合app:lottie_loop属性就能完成自动播放+无限循环的效果。
3.2 在代码中设置
在MainActivity中:
public class MainActivity extends AppCompatActivity {private LottieAnimationView lottieAnimationView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化ViewlottieAnimationView = findViewById(R.id.lottieAnimationView);// 从raw资源设置动画lottieAnimationView.setAnimation(R.raw.loading_animation);// 从assets文件夹设置动画// lottieAnimationView.setAnimation("ic_video.json");// 使用这个记得加上异常处理// 开始播放动画// lottieAnimationView.playAnimation();}
}
初始化LottieAnimationView,并设置动画源,同样有两种方式。如果没有在代码中设置自动播放,在代码中记得开始播放动画,由于已经带xml中设置自动播放和循环,这里就会自动开始一直循环播放动画。
播放设置:
// 播放动画
lottieAnimationView.playAnimation();// 暂停动画(保留当前进度)
lottieAnimationView.pauseAnimation();// 取消动画(重置进度)
lottieAnimationView.cancelAnimation();// 恢复播放(从暂停处继续)
lottieAnimationView.resumeAnimation();// 判断是否正在播放
boolean isPlaying = lottieAnimationView.isAnimating();// 获取动画时长(毫秒)
long duration = lottieAnimationView.getDuration();
进度控制:
// 设置播放进度(0.0f - 1.0f)
lottieAnimationView.setProgress(0.5f);// 获取当前进度
float currentProgress = lottieAnimationView.getProgress();// 从指定进度开始播放
lottieAnimationView.setProgress(0.3f);
lottieAnimationView.playAnimation();// 播放特定区间(从30%到70%)
lottieAnimationView.setMinProgress(0.3f);
lottieAnimationView.setMaxProgress(0.7f);
lottieAnimationView.playAnimation();
在Java代码中,你可以通过以上方法轻松实现对Lottiie动画的播放控制以及进度设置,如果想要配合点击事件实现更复杂的交互效果,你可以添加一个动画监听器。
3.3添加动画监听器
// 添加动画监听器
lottieAnimationView.addAnimatorListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {// 动画开始时的回调Log.d("Lottie", "动画开始播放");}@Overridepublic void onAnimationEnd(Animator animation) {// 动画结束时的回调Log.d("Lottie", "动画播放结束");}@Overridepublic void onAnimationCancel(Animator animation) {// 动画被取消时的回调Log.d("Lottie", "动画被取消");}@Overridepublic void onAnimationRepeat(Animator animation) {// 每次循环重复时的回调Log.d("Lottie", "动画开始新一轮循环");}
});
通过动画监听器,你可以监听动画播放过程中的各种状态变化,在对应实际执行自定义逻辑,实现更复杂的交互逻辑,比如你可以在onAnimationEnd方法,也就是动画结束方法回调中执行页面跳转的逻辑,效果就是在动画播完后再跳转。再比如在onAnimationStart方法中可以添加一个文字提示,点击播放加载中动画,提高用户体验。
怎么样,使用起来是不是非常容易,接下来将介绍怎么自定义BottomNavigationView将其中的ImageView替换成LottieAnimationView,实现炫酷的底部导航栏动画。
4.自定义BottomNavigationView实现炫酷的底部导航栏点击动画
4.1 使用效果
首先,还是先看看具体效果:

定义LottieBottomNavigationView类继承自BottomNavigationView在updateMenuItemIcon方法中调用replaceImageViewWithLottie方法将BottomNavigationView中原有的ImageView替换成LottieAnimationView,并在init方法中设置LottieAnimationView的点击事件,点击播放Lottie动画,效果就是点击播放,如果再次点击时动画还没有播放完毕,那么就从头开始再次播放动画。
4.2 自定义LottieBottomNavigationView继承自BottomNavigationView
LottieBottomNavigationView:
import android.content.Context;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageView;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;import com.airbnb.lottie.LottieAnimationView;
import com.google.android.material.bottomnavigation.BottomNavigationView;public class LottieBottomNavigationView extends BottomNavigationView {private SparseArray<String> animationMap = new SparseArray<>();private boolean iconsSet = false;public LottieBottomNavigationView(@NonNull Context context) {super(context);init();}public LottieBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}public LottieBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {setLabelVisibilityMode(LABEL_VISIBILITY_LABELED);getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {if (!iconsSet && getChildCount() > 0) {getViewTreeObserver().removeOnGlobalLayoutListener(this);updateAllMenuIcons();iconsSet = true;// forceRemovePadding();setPadding(0, 0, 0, 0);}}});setOnNavigationItemSelectedListener(new OnNavigationItemSelectedListener() {@Overridepublic boolean onNavigationItemSelected(@NonNull MenuItem item) {int itemId = item.getItemId();playLottieAnimation(itemId); // 点击时播放相应动画return true; // 返回true表示菜单项已被处理}});}private void forceRemovePadding() {// 强制设置高度为更小的值ViewGroup.LayoutParams params = getLayoutParams();params.height = dipToPx(48); // 设置为较小的值// 设置垂直偏移setTranslationY(-dipToPx(4));if (getChildCount() > 0) {ViewGroup menuView = (ViewGroup) getChildAt(0);ViewGroup.LayoutParams menuParams = menuView.getLayoutParams();menuParams.height = dipToPx(40);menuView.setLayoutParams(menuParams);}}public void setLottieIcon(int menuItemId, String animationAssetName) {animationMap.put(menuItemId, animationAssetName);if (iconsSet) {updateMenuItemIcon(menuItemId);}}private void updateAllMenuIcons() {for (int i = 0; i < animationMap.size(); i++) {updateMenuItemIcon(animationMap.keyAt(i));}}private void updateMenuItemIcon(int menuItemId) {View menuItemView = findMenuItemView(menuItemId);if (menuItemView == null) return;String assetName = animationMap.get(menuItemId);FrameLayout iconContainer = menuItemView.findViewById(android.R.id.icon);if (iconContainer != null) {setupLottieInContainer(iconContainer, assetName);return;}ImageView iconView = findIconImageView(menuItemView);if (iconView != null) {replaceImageViewWithLottie(iconView, assetName);return;}if (menuItemView instanceof ViewGroup) {setupLottieDirectly((ViewGroup) menuItemView, assetName);}}private View findMenuItemView(int menuItemId) {View view = findViewById(menuItemId);if (view != null) return view;if (getChildCount() > 0) {ViewGroup menuView = (ViewGroup) getChildAt(0);for (int i = 0; i < menuView.getChildCount(); i++) {View child = menuView.getChildAt(i);if (child.getId() == menuItemId) return child;}}return null;}private ImageView findIconImageView(View menuItemView) {if (menuItemView instanceof ImageView) {return (ImageView) menuItemView;}if (menuItemView instanceof ViewGroup) {ViewGroup group = (ViewGroup) menuItemView;for (int i = 0; i < group.getChildCount(); i++) {ImageView result = findIconImageView(group.getChildAt(i));if (result != null) return result;}}return null;}private void setupLottieInContainer(FrameLayout container, String assetName) {container.removeAllViews();LottieAnimationView lottieIcon = createLottieView(assetName);container.addView(lottieIcon);}private void replaceImageViewWithLottie(ImageView oldIcon, String assetName) {ViewGroup parent = (ViewGroup) oldIcon.getParent();int index = parent.indexOfChild(oldIcon);LottieAnimationView lottieIcon = createLottieView(assetName);lottieIcon.setProgress(1f);lottieIcon.setLayoutParams(oldIcon.getLayoutParams());parent.removeViewAt(index);parent.addView(lottieIcon, index);}private void setupLottieDirectly(ViewGroup container, String assetName) {LottieAnimationView lottieIcon = createLottieView(assetName);container.addView(lottieIcon, 0);}private LottieAnimationView createLottieView(String assetName) {LottieAnimationView lottieIcon = new LottieAnimationView(getContext());lottieIcon.setAnimation(assetName);lottieIcon.setRepeatCount(0);lottieIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(dipToPx(30), dipToPx(30), Gravity.CENTER);lottieIcon.setLayoutParams(params);return lottieIcon;}public void playLottieAnimation(int menuItemId) {View menuItemView = findMenuItemView(menuItemId);if (menuItemView != null) {LottieAnimationView lottieView = findLottieView(menuItemView);if (lottieView != null) lottieView.playAnimation();}}private LottieAnimationView findLottieView(View view) {if (view instanceof LottieAnimationView) {return (LottieAnimationView) view;}if (view instanceof ViewGroup) {ViewGroup group = (ViewGroup) view;for (int i = 0; i < group.getChildCount(); i++) {LottieAnimationView result = findLottieView(group.getChildAt(i));if (result != null) return result;}}return null;}public int dipToPx(float dipValue) {float scale = getContext().getResources().getDisplayMetrics().density;return (int) (dipValue * scale + 0.5f);}
}
4.3 在xml中使用LottieBottomNavigationView
在xml中使用:
<com.example.musicapp.Heapler.LottieBottomNavigationViewandroid:id="@+id/bottom_navigation"android:layout_width="match_parent"android:layout_height="90dp"android:background="@color/white"app:labelVisibilityMode="labeled"app:itemActiveIndicatorStyle="@null"app:itemRippleColor="@null"app:itemGravity="center"android:padding="0dp"app:itemPaddingTop="13dp"app:itemPaddingBottom="0dp"app:layout_constraintBottom_toBottomOf="parent"app:menu="@menu/bottom_menu" />
com.example.musicapp.Heapler这部分换成你自己的包名,基本用法和BottomNavigationView基本一致,自定义menu菜单并引用,另外,在menu菜单中正常设置一个随意的占位图,纯色背景也行,运行时会自动替换成Lottie动画。如果不想要BottomNavigationView的点击水波纹效果,可以设置app:itemActiveIndicatorStyle="@null",app:itemRippleColor="@null"。
4.4 在代码中设置Lottie图标
// 获取自定义的 BottomNavigationView
LottieBottomNavigationView lottieBottomNavigationView = findViewById(R.id.bottom_navigation);// 设置每个菜单项的 Lottie 动画
lottieBottomNavigationView.setLottieIcon(R.id.opt_home, "ic_home.json");
lottieBottomNavigationView.setLottieIcon(R.id.opt_browse, "ic_video.json");
lottieBottomNavigationView.setLottieIcon(R.id.opt_message, "ic_message.json");
lottieBottomNavigationView.setLottieIcon(R.id.opt_shopping_car, "ic_shopping_car.json");
lottieBottomNavigationView.setLottieIcon(R.id.opt_mine, "ic_mine.json");
使用起来非常简单,初始化LottieBottomNavigationView,并为每一item项设置动画Json文件,就可以实现非常炫酷的导航栏点击动画。
分享一些Android学习之余看到的有趣的小知识,希望大家喜欢~
