美文网首页
BottomNavigationView的Item长按为什么会弹

BottomNavigationView的Item长按为什么会弹

作者: Horps | 来源:发表于2023-07-18 16:16 被阅读0次
  • 概述

    应用BottomNavigationView的时候偶然发现长按菜单项的时候会弹出一个toast,toast的内容和菜单项的title是一致的,通常我们不会喜欢有这么个画蛇添足的效果,想着能不能把这个效果关掉,但是看过源码后没发现可配置这个效果的属性,也没发现设置长按事件的代码,最后发现一个View内嵌的长按设置,比较新颖,记录一下。

  • 最初思路的源码分析

    最开始,找到NavigationBarMenuView中添加item的地方:

    public void buildMenuView() {
      removeAllViews();
      ...
      buttons = new NavigationBarItemView[menu.size()];
      ...
      for (int i = 0; i < menu.size(); i++) {
        ...
        NavigationBarItemView child = getNewItem();
        buttons[i] = child;
        ...
        int itemId = item.getItemId();
        child.setOnTouchListener(onTouchListeners.get(itemId));
        child.setOnClickListener(onClickListener);
        ...
        addView(child);
      }
      ...
    }
    

    可以看到,这里并没有发现child调用setOnLongClickListener方法,buttons也搜了,没有额外的设置长按事件,BottomNavigationView、NavigationBarView、NavigationBarItemView都没搜到设置长按。

    事情好像开始无解了。

  • 新鲜的东西

    常规思路无法解决的时候,我试着去网上搜一搜,结果有很多说设置:

    //遍历子View,重写长按点击事件
    for (position in 0 until ids.size){
        bottomNavigationMenuView.getChildAt(position).findViewById<View>(ids[position]).setOnLongClickListener { true }
    }
    

    这还真的奏效了,不过你不能设置setOnLongClickListener为null。

    那这是为什么呢?

    然后我搜到了一位同学写的针对这个问题Why的源码分析。

    在NavigationBarItemView中的setTitle方法中:

    @Override
    public void setTitle(@Nullable CharSequence title) {
      smallLabel.setText(title);
      largeLabel.setText(title);
      ...
      if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP || VERSION.SDK_INT > VERSION_CODES.M) {
        TooltipCompat.setTooltipText(this, tooltipText);
      }
    }
    

    重点在于TooltipCompat.setTooltipText:

    public static void setTooltipText(@NonNull View view, @Nullable CharSequence tooltipText) {
        if (Build.VERSION.SDK_INT >= 26) {
            view.setTooltipText(tooltipText);
        } else {
            TooltipCompatHandler.setTooltipText(view, tooltipText);
        }
    }
    

    小于26版本时TooltipCompatHandler.setTooltipText中会设置长按事件为null,而在26版本以上的才会设置长按事件,接着往下看:

    public void setTooltipText(@Nullable CharSequence tooltipText) {
        if (TextUtils.isEmpty(tooltipText)) {
            setFlags(0, TOOLTIP);
            hideTooltip();
            mTooltipInfo = null;
        } else {
            setFlags(TOOLTIP, TOOLTIP);
            if (mTooltipInfo == null) {
                mTooltipInfo = new TooltipInfo();
                mTooltipInfo.mShowTooltipRunnable = this::showHoverTooltip;
                mTooltipInfo.mHideTooltipRunnable = this::hideTooltip;
                mTooltipInfo.mHoverSlop = ViewConfiguration.get(mContext).getScaledHoverSlop();
                mTooltipInfo.clearAnchorPos();
            }
            mTooltipInfo.mTooltipText = tooltipText;
        }
    }
    

    这里会根据tooltipText是否为空来决定是否设置TOOLTIP这个flag,如果不为空则设置TOOLTIP,然后再往下就没逻辑了,我们还是没有找到设置长按事件的地方。

    但是我们可以找到show的方法:

    private boolean showTooltip(int x, int y, boolean fromLongClick) {
        if (mAttachInfo == null || mTooltipInfo == null) {
            return false;
        }
        if (fromLongClick && (mViewFlags & ENABLED_MASK) != ENABLED) {
            return false;
        }
        if (TextUtils.isEmpty(mTooltipInfo.mTooltipText)) {
            return false;
        }
        hideTooltip();
        mTooltipInfo.mTooltipFromLongClick = fromLongClick;
        mTooltipInfo.mTooltipPopup = new TooltipPopup(getContext());
        final boolean fromTouch = (mPrivateFlags3 & PFLAG3_FINGER_DOWN) == PFLAG3_FINGER_DOWN;
        mTooltipInfo.mTooltipPopup.show(this, x, y, fromTouch, mTooltipInfo.mTooltipText);
        mAttachInfo.mTooltipHost = this;
        // The available accessibility actions have changed
        notifyViewAccessibilityStateChangedIfNeeded(CONTENT_CHANGE_TYPE_UNDEFINED);
        return true;
    }
    

    可以看到,这里通过TooltipPopup(可以认为是一个PopupWindow,只是通过WindowManager.addView来添加的)来展示的。

    我们找一下showTooltip方法调用的地方,发现有一个showLongClickTooltip方法,查看他的调用链,发现源头在onTouchEvent的ACTION_DOWN分支下:

    public boolean onTouchEvent(MotionEvent event) {
        ...
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                      //这里控制在手指抬起后经过一定时间才隐藏toast
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    ...
                    break;
                case MotionEvent.ACTION_DOWN:
                    ...
                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                       ...
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                          //在这里触发到showTooltip中去的
                        checkForLongClick(
                                ViewConfiguration.getLongPressTimeout(),
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    }
                    break;
    
                case MotionEvent.ACTION_CANCEL:
                    ...
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    ...
                    break;
            }
            return true;
        }
        return false;
    }
    
    private void checkForLongClick(long delay, float x, float y, int classification) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
            mHasPerformedLongPress = false;
    
            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            mPendingCheckForLongPress.rememberPressedState();
            mPendingCheckForLongPress.setClassification(classification);
            postDelayed(mPendingCheckForLongPress, delay);
        }
    }
    

    checkForLongClick方法中会调用postDelayed方法,delay是ViewConfiguration.getLongPressTimeout(),就是延迟长按的时间,运行的Runnable就是mPendingCheckForLongPress,它是CheckForLongPress类型的:

    private final class CheckForLongPress implements Runnable {
          ...
        @Override
        public void run() {
            if ((mOriginalPressedState == isPressed()) && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                recordGestureClassification(mClassification);
                if (performLongClick(mX, mY)) {
                    mHasPerformedLongPress = true;
                }
            }
        }
        ...
    }
    

    这里会调用performLongClick,最终会调用到performLongClickInternal方法:

    private boolean performLongClickInternal(float x, float y) {
        ...
        boolean handled = false;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLongClickListener != null) {
            handled = li.mOnLongClickListener.onLongClick(View.this);
        }
        ...
        if ((mViewFlags & TOOLTIP) == TOOLTIP) {
            if (!handled) {
                handled = showLongClickTooltip((int) x, (int) y);
            }
        }
        ...
        return handled;
    }
    

    因为我们之前在setTooltipText方法中设置了TOOLTIP,因此这里只要handled为false就能执行showLongClickTooltip方法,如果没有设置OnLongClickListener并且在其onLongClick方法中返回true的话,showLongClickTooltip肯定是会被执行的,这也就是为什么BottomNavigationView的菜单项默认会有长按弹出toast的效果,也是为什么给NavigationBarItemView设置OnLongClickListener为null后仍然无法禁止长按弹出toast的效果的原因。

    看到这,你可能知道应该怎么做来禁用这个效果了。

    //禁用item的长按事件
    val menuView: NavigationBarMenuView = getChildAt(0) as NavigationBarMenuView
    for (index in 0 until menuView.childCount) {
        menuView.getChildAt(index).setOnLongClickListener { true }
    }
    

相关文章

网友评论

      本文标题:BottomNavigationView的Item长按为什么会弹

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