快速了解
-
解决的问题
-
BottomNavigationView依赖版本:
implementation 'com.google.android.material:material:1.2.0'
-
BottomNavigationView的层级结构:
看图我们可以知道,BottomNavigationView
有一个直接子View(BottomNavigationMenuView
)里面装的是每一个Menu的Item(BottomNavigationItemView
)这就是我们点击或长按item时的实际的View。 -
获取具体BottomNavigationItemView的方式:
bottomNavigationView.getChildView(0).findViewById(R.id.menu3)
猜想与验证
猜想1
:既然长按Item会出现Toast,那么将具体BottomNavigationItemView
设置长按事件LongClickListener
为null就不会触发长按事件了,那是不是就不会弹出Toast了呢?
答:无效
,长按仍然会显示Toast,我不是将LongClick置空了吗!!为什么?接下来带你一步步看。猜想2
:🔍一下别人的解决方式,设置BottomNavigationView
的长按监听事件并且返回true;
答:有效!
,如果你只想解决问题,那么使用下方代码即可解决。
//注意BottomNavigationItemView,是具体点击的那个按钮控件~~删除线格式~~
BottomNavigationItemView.setOnLongClickListener{true}//lambda表达式、返回true代表消费这个事件
- 验证:为什么将
BottomNavigationItemView
的长按事件设置null
不起作用,但是给它设置一个长按事件并返回true即可解决呢?
- 从BottomNavigationItemView的源码入手;
我们可以看到没有任何长按相关的逻辑,也就是说给BottomNavigation设置menu的时候,它并不会自动的帮我们设置长按事件。没有长按事件,我们就从弹出的文字入手,弹出的Toast显示的menu的title
,那么我们看下他的title在哪里被使用了!
通过上面的源码方法我们可以发现title被设置在smallLabel
和largeLabel
两个TextView
上,还有最后一行的TooltipCompat.setTooltipText(this, tooltipText);
使用了title,跟进去看看
这个是一个兼容静态方法,我们继续更进去看API大于等于26的实现(小于26的自行分析啦);
我们可以看它的英文注释描述,Sets the tooltip text which will be displayed in a small popup next to the view
大意是通过它会利用一个的弹框展示文本内容,这里的文本内容不就是我们传进来的title吗!!它将传进来的title放到mTooltipInfo.mTooltipText = tooltipText;
里,我们看一下mTooltipText
在哪里被使用了,有好几处,但是关键的是一个showTooltip
的方法
该方法的主要作用就是构建一个TooltipPopup弹框(Toast其实也是一个弹框)并显示。已经找到了显示Toast的方法了,那么我们看看它何时被调用。
上图可以看到showLongClickTooltip
的方法,看方法名好像是和长按事件有关,继续看一下它被谁调用
它被两处调用,但是这一处View.performLongClickInternal
最关键,这不就是触发View的长按事件的方法吗!我们仔细看一下这个方法的实现
handled
临时变量来保存是否被处理
第一个if,它判断是否有设置OnLongClickListener
长按监听,如果有则调用OnLongClick
的方法,并且将返回值赋值给handled
,如果handled为true则不会走以下的两个if;第二个if是如果handled为false,它会去看是否要显示上下文菜单ContextMenu
(可以具体🔍一下是什么东西),并将返回值赋值给handled,第三个if是我们关注的那个显示Toast的方法,如果上面两个都未被处理,则会调用它;
这个方法很好的解释了,为什么设置OnLongClickListener为null的时候,仍然会显示Toast,因为它handled仍为false,第二个if如果没有用上下文菜单默认也是false,进而会继续调用showLongClickTooltip的方法
。这也能说明为什么设置OnLongClickListener返回true就不会显示Toast了。到这里你就可以明白长按BottomNavigationView的菜单为什么会显示Toast了
,接下来我们继续看,我设置长按事件为null了,为什么仍然会走这个方法呢?
它的直接调用者是performLongClick方法,那么我们看下谁又调用它
它被调用的地方有好几处,最终找到这个Runnable子类CheckForLongPress
中调用的是关键,我们继续看它在哪里被实例化
它在checkForLongClick方法被实例化,最终调用postDelayed(mPendingCheckForLongPress, delay);
将上面的Runnable方法放到消息队列中等待一定的延时后调用runnable进而调用显示Toast的方法;继续看谁又调用它,我们发现最终它会被View#onTouchEvent
调用,终于回到了事件分发机制(理解它的流程很关键!!)的流程
它在Down事件中去检查LongClick,所以不管你设不设置长按监听,依然会走上方的流程。
总结
设置BottomNavigationView的menu不会自动给Item设置长按监听,但是会给Item的View设置tooltipText
;- 长按事件的触发仍然是走事件分发机制的那套流程,它在Down事件的时候去调用
View.checkForLongClick
,即便你不设置长按监听也会走接下来的流程;它是在特定条件
不满足的情况下会执行View.showTooltip
方法将ItemView的tooltipText通过一个弹窗进行显示出来; - 特定条件是,设置了
onLongClickListener
并且OnLongClick
方法中返回true,设置了上下文菜单,并且它的方法也返回true,否则长按就会显示title的Toast; - 经过一下午的研究,结合网上给的答案,终于解决为什么长按BottomNavigationView的item会弹出Toast了!!好开心即便这仅是一个小小的细节,写这个文章是记录自己一次通过分析源码解决问题O(∩_∩)O哈哈~