}
复制代码

封装个 BasicData,存放 App 内置的一些基本数据,这里主要针对 Lottie 文件:

val mNavigationAnimationList = arrayListOf(
LottieAnimation.HOME,
LottieAnimation.SUBSCRIBE,
LottieAnimation.DISCOVERY,
LottieAnimation.ACCOUNT
)

val mNavigationAnimationNightList = arrayListOf(
LottieAnimation.HOME_NIGHT,
LottieAnimation.SUBSCRIBE_NIGHT,
LottieAnimation.DISCOVERY_NIGHT,
LottieAnimation.ACCOUNT_NIGHT
)
复制代码

Step 3:导入对应依赖,新增 Lottie Utils

api ‘com.google.android.material:material:1.2.0’
api ‘com.airbnb.android:lottie:3.4.1’
复制代码

工具类方法:

/**

  • 获取 Lottie Drawable
    */
    fun getLottieDrawable(
    animation: LottieAnimation,
    bottomNavigationView: BottomNavigationView
    ): LottieDrawable {
    return LottieDrawable().apply {
    val result = LottieCompositionFactory.fromAssetSync(
    bottomNavigationView.context.applicationContext, animation.value
    )
    callback = bottomNavigationView
    composition = result.value
    }
    }

/**

  • 获取不同模式下 Lottie json 文件
    */
    fun getLottieAnimationList(context: Context): ArrayList {
    return if (isDarkTheme(context)) {
    mNavigationAnimationNightList
    } else {
    mNavigationAnimationList
    }
    }
    复制代码

判断是否深色模式我单独提取了一个工具类,Lottie-android 中也有对深色模式的兼容方法:

/**

  • 验证当前是否为深色模式
    */
    fun isDarkTheme(context: Context): Boolean {
    val flag = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
    return flag == Configuration.UI_MODE_NIGHT_YES
    }
    复制代码

Step 4:设置布局

先添加个 tab 字体选中和非选中的字体颜色 selecor:

<?xml version="1.0" encoding="utf-8"?> 复制代码

整一波布局文件:

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:background="@color/colorBackground"
tools:context=".module.home.activity.HomeActivity">

<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_bottom_bar"
android:layout_width="@dimen/dp_0"
android:layout_height=“wrap_content”
android:background="@color/colorBackground"
app:itemIconSize="@dimen/dp_30"
app:itemTextColor="@color/selector_menu_state_navigation"
app:labelVisibilityMode=“labeled”
app:layout_constraintBottom_toBottomOf=“parent”
app:layout_constraintEnd_toEndOf=“parent”
app:layout_constraintStart_toStartOf=“parent” />

</androidx.constraintlayout.widget.ConstraintLayout>
复制代码

Step 5:初始化 BottomNavigationView 以及 Menu

private fun initBottomNavigationView() {
nav_bottom_bar.menu.apply {
for (i in 0 until mNavigationTitleList.size) {
add(Menu.NONE, i, Menu.NONE, mNavigationTitleList[i])
}
setLottieDrawable(getLottieAnimationList(mSelfActivity))
}
initEvent()
}

private fun initEvent() {
nav_bottom_bar.setOnNavigationItemSelectedListener(this)
nav_bottom_bar.setOnNavigationItemReselectedListener(this)
// 默认选中第一个
nav_bottom_bar.selectedItemId = 0
// 处理长按 MenuItem 提示 TooltipText
nav_bottom_bar.menu.forEach {
val menuItemView = mSelfActivity.findViewById(it.itemId) as BottomNavigationItemView
menuItemView.setOnLongClickListener {
true
}
}
}

private fun Menu.setLottieDrawable(lottieAnimationList: ArrayList) {
for (i in 0 until mNavigationTitleList.size) {
findItem(i)?.icon =
getLottieDrawable(lottieAnimationList[i], nav_bottom_bar)
}
}

override fun onNavigationItemSelected(item: MenuItem): Boolean {
handleNavigationItem(item)
return true
}

override fun onNavigationItemReselected(item: MenuItem) {
handleNavigationItem(item)
}

private fun handleNavigationItem(item: MenuItem) {
handlePlayLottieAnimation(item)
mPreClickPosition = item.itemId
}

private fun handlePlayLottieAnimation(item: MenuItem) {
val currentIcon = item.icon as? LottieDrawable
currentIcon?.apply {
playAnimation()
}
// 处理 tab 切换,icon 对应调整
if (item.itemId != mPreClickPosition) {
nav_bottom_bar.menu.findItem(mPreClickPosition).icon =
getLottieDrawable(
getLottieAnimationList(mSelfActivity)[mPreClickPosition],
nav_bottom_bar
)
}
}
复制代码

问题汇总

鸡老大说:

  • 遇到问题是好事儿,多总结,多积累,掌握一个循循渐进的过程。

1、BottomNavigationView 切换对应的 Lottie 不改变,怎么玩?

这个问题是我从一开始就陷入了固有思维循环中。

下面是我陷入误区的思路:

  • 我想着因为是通过 playAnimation 开始执行动画从而过渡到最后的颜色,那么对应的 endAnimation 应该是直接能回到初始状态。那么我直接缓存上一此点击 MenuItem 然后修改状态不就好了嘛。
  • tint 着色器修改?

整整折腾了好久,折腾到韩总说,不行咱就放弃吧。

想想鸡老大,怎能轻易放弃?

昨天突然想到,为什么我不重新给设置一次 Drawable 呢?反正初始的 Drawable 就是灰色,当然也是未选中的状态,随后赶紧实战测试了一波,附上关键代码:

override fun onNavigationItemReselected(item: MenuItem) {
handleNavigationItem(item)
}

private fun handleNavigationItem(item: MenuItem) {
handlePlayLottieAnimation(item)
mPreClickPosition = item.itemId
}

private fun handlePlayLottieAnimation(item: MenuItem) {
val currentIcon = item.icon as? LottieDrawable
currentIcon?.apply {
playAnimation()
}
// 这里判断如果当前点击的和上一次点击索引不同,则将上一次点击索引位置的 MenuItem Icon 替换
if (item.itemId != mPreClickPosition) {
// 获取到上一个 MenuItem 并修改对应的 icon drawable
nav_bottom_bar.menu.findItem(mPreClickPosition).icon =
getLottieDrawable(
getLottieAnimationList(mSelfActivity)[mPreClickPosition],
nav_bottom_bar
)
}
}
复制代码

具体代码参考文章实战部分。

小教训(心得):

  • 真的是有时候不得不换种思维方式,首要的便是实现,随后才是优化。基本雏形都没有,何谈优化?

  • 身为猿猿,面对实际开发中遇到的问题,一定要采取多方案,首要保证内容、结果的输出,其次才是合理的循循渐进的优化。

2、BottomNavigationView Item 长按提示怎么搞掉?

先来看个效果图:

比较尴尬的是,用了很久了,头一次某天闲来无事长按发现的这个东西,当时还好奇,没事弹个 Toast 干啥玩意?怀揣着满满的自信,去找源码关于我理想中认为的这个 Toast,没找到。

来,正好一起翻翻源码,看看能否从源码的角度去思考并解决这个问题。

进行一波简要分析:

从上图以及实际编码过程中,我们可以得知,所谓的底部 item,其实只是一个数量不能超过 5 个的 Menu,而实际触发这个提示肯定是 ItemView,那么一起来看下针对 itemView 它做了什么操作?

在初始化 ItemView 时,最后有这么一段设置,一起来看下:

@Override
public void initialize(@NonNull MenuItemImpl itemData, int menuType) {
// …
CharSequence tooltipText = !TextUtils.isEmpty(itemData.getTooltipText())
? itemData.getTooltipText()
: itemData.getTitle();
TooltipCompat.setTooltipText(this, tooltipText);
setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
}
复制代码

tooltipText,工具提示文本?有点茫然,似乎有点意思。这里有个验证,如果 tooltipText 为空则使用 title 值,反正使用自身值,最后将最终的判断结果进行 setTooltipText,我们一起来看看这个 set 里面干了什么?

/**

  • Helper class used to emulate the behavior of {@link View#setTooltipText(CharSequence)} prior
  • to API level 26.

/
public class TooltipCompat {
/
*

  • Sets the tooltip text for the view.
  • Prior to API 26 this method sets or clears (when tooltip is null) the view's

  • OnLongClickListener and OnHoverListener. A toast-like subpanel will be created on long click
  • or mouse hover.
  • @param view the view to set the tooltip text on
  • @param tooltipText the tooltip text
    */
    public static void setTooltipText(@NonNull View view, @Nullable CharSequence tooltipText) {
    if (Build.VERSION.SDK_INT >= 26) {
    view.setTooltipText(tooltipText);
    } else {
    TooltipCompatHandler.setTooltipText(view, tooltipText);
    }
    }

private TooltipCompat() {}
}
复制代码

注释写的倒是蛮清楚的,在 Api 26 添加的一个工具提示文本,主要用于长按或者鼠标悬停的一个提示,类似 Toast。

继续往下看,set 到底干了啥?

/**

  • Sets the tooltip text which will be displayed in a small popup next to the view.
  • // …
  • // 重点是下面参数描述,如果不需要设置 null
  • @param tooltipText the tooltip text, or null if no tooltip is required
  • @see #getTooltipText()
  • @attr ref android.R.styleable#View_tooltipText
    */
    public void setTooltipText(@Nullable CharSequence tooltipText) {
    if (TextUtils.isEmpty(tooltipText)) {
    setFlags(0, TOOLTIP);
    // hide 隐藏?鸡老大万岁
    hideTooltip();
    mTooltipInfo = null;
    } else {
    // …
    }
    }
    复制代码

可以很明确的看到,如果不想显示这个所谓的 tooltipText 只需要将其设置为空即可。看到这里大概有个思路了,但是还是好奇弹出的这个东西是啥?继续往下找。

@UnsupportedAppUsage
void hideTooltip() {
// …
if (mTooltipInfo.mTooltipPopup == null) {
return;
}
mTooltipInfo.mTooltipPopup.hide();
mTooltipInfo.mTooltipPopup = null;
// …
}
复制代码

ummm,原来是个 PopupWindow。

好吧,回归正题,取消 BottomNavigationView 长按时的 tooltipText 提示。

循环遍历 Menu 并将 tooltipText 设置为 null。

nav_bottom_bar.menu.forEach {
TooltipCompat.setTooltipText(mSelfActivity.findViewById(it.itemId),null)
}
复制代码

来看下效果图:

ummm。不对呀。首次进来两个 Tab 长按符合预期,后续呢?

ummm,或者,我直接断了丫的念想?直接拦截长按事件一波?

nav_bottom_bar.menu.forEach {
val menuItemView = mSelfActivity.findViewById(it.itemId) as BottomNavigationItemView
menuItemView.setOnLongClickListener {
true
}
}

运行一波看看?

ummm,好扎心。

3、选中时,想来个动画,怎么搞?(2020/09/03)

昨天大半夜,韩总发消息,部分内容如下:

ummm,我这个扎心的啊。都要准备入眠来着。

好吧,简单分析波。

先附上关于 BottomNavigationView 的 item 部分代码,其实是 Menu Item:

各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。

文件夹有以下学习笔记,自行下载!

  • BAT大厂面试题、独家面试工具包,

  • 资料免费领取,包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,


    的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。

  • 点击此处,与我们一起交流学习

文件夹有以下学习笔记,自行下载!

  • BAT大厂面试题、独家面试工具包,

  • 资料免费领取,包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,

    [外链图片转存中…(img-R7UMVnWk-1645164336797)]

Android-Notes|BottomNavigationView-爱上-Lottie,android高级开发面试题相关推荐

  1. 耗时118天爆肝【1296页】的“Android高级开发面试题”,终于成功上岸字节

    前言 本人16年毕业于一家普通二本,考研裂开了且没有实习经验,只做过两个项目,每天就是不断地投简历.刷面经,感觉自己都要抑郁了,最后勉强进入了一家学校合作的互联网公司,后面陆陆续续也换了几家公司,毕业 ...

  2. Android面试题——高级开发面试题一

    一 面试题概述 请简单的分析一下Android系统启动流程的原理? App启动状态有哪几种,各自的启动流程是怎么样的? 当项目中遇到黑白屏问题,你有什么好的解决方案? 如何查看方法内的耗时时间与方法分 ...

  3. python高级开发面试题_python面试的100题(16)

    Python高级 元类 42.Python中类方法.类实例方法.静态方法有何区别? 类方法: 是类对象的方法,在定义时需要在上方使用 @classmethod 进行装饰,形参为cls,表示类对象,类对 ...

  4. 用友java面试题_用友网络科技Java高级开发面试题(2019)

    面试岗位:Java高级开发 面试形式:电话面试 这些天在boss上逛了下,看见北京Java开发工资比较诱人,便萌生了去北京的想法,做一名北漂的程序猿.约了几家面试,由于是异地,当然优先电话面了.本篇记 ...

  5. 高级开发面试题,朋友给的,怕忘了

    基本概念 操作系统中 heap 和 stack 的区别 什么是基于注解的切面实现 什么是 对象/关系 映射集成模块 什么是 Java 的反射机制 什么是 ACID BS与CS的联系与区别 Cookie ...

  6. java高级开发面试题

    1,java堆,分新生代老年代,新生代有Eden,from surviver,to surviver三个空间,堆被所有线程共.eden内存不足时,发生一次minor GC,会把from survivo ...

  7. Java高级开发面试题整理

    一.并发编程 1.什么是进程和线程? 进程是指程序的一次执行过程,是系统运行程序的基本单位,系统运行一个程序就是一个进程创建.运行.到销毁的过程:一个进程可以有多个线程.比如我跑一个java的main ...

  8. 某公司.Net高级开发面试题(1)

    1.将字符串转换成时间类型,有几种方式?有什么注意事项? Convert.ToDateTime(string) Convert.ToDateTime(string, IFormatProvider) ...

  9. 2022高级JAVA开发面试题精选

    面试过程是一个由浅入深的过程,面试官先给求职者抛出一个相对简单的问题,然后通过一环套一环的追问深入考察求职者对知识点的理解掌握程度. 如下是一个实际的关于redis知识点的面试场景: 面试官:你用过r ...

最新文章

  1. 【AI】在win10上安装TensorFlow2,安装成功,但是import tensorflow时报错:pywrap_tensorflow.py“, line 58
  2. 计算机组成原理A原是什么,计算机组成原理A
  3. 哈理工计算机学院孙广路,我校举行人工智能前沿领域-计算机视觉专题报告会...
  4. 笔试真题及其答案解析:国内各大银行计算机方向笔试之计算机基础知识—选择题120题
  5. Java按照时间顺序从hbase中读出数据
  6. hdu1010 Tempter of the Bone
  7. 利用DataSet、DataTable、DataView按照自定义条件过滤数据
  8. c语言中变量后减号大于号,大于等于运算符.ppt
  9. OpenCore黑苹果引导配置说明-基于OpenCore-0.7.0-06-08正式版
  10. 你的深度思考能力,是如何一步步被毁掉的?奶嘴战略
  11. 【《Unity Shader入门精要》 提炼总结】(十三)第十三章·透明度测试的Shader实现透明度混合的Shader实现
  12. 保龄球计分c语言,保龄球的好处、起源、计分规则、常用技法
  13. DM达梦数据库存储过程和触发器
  14. Lending Club信贷违约风险分析(R语言)
  15. 秒杀全网!研发、运营必备实用工具网站
  16. 3.MyBatis源码解析-CRUD执行流程--阿呆中二
  17. 回收站是计算机硬盘,如何查找移动硬盘回收站
  18. 操作系统 -- pcb
  19. 轻松搬运30斤货物,波士顿动力Handle再秀新技能
  20. [附源码]Python计算机毕业设计JAVA医院信息管理系统

热门文章

  1. Spring Cloud架构教程 (三)服务网关(基础)
  2. 超声波传感器与液晶屏显示实验
  3. 做html时css经常无效果,修改CSS样式无效有什么原因?
  4. 每日记录 8.28 TP(真阳率) NP(假阳率) FP
  5. 关于“上海电信IPTV系统开机广告不能关闭“问题的看法
  6. 使用阿里云的短信服务发送短信
  7. 启用DWA R8.02 Lite模式
  8. CSS动画实现3D隧道效果
  9. Python学习笔记(一)三步走安装pip
  10. #常用传感器讲解十二--倾斜开关传感器(KY-020)