ViewPager2+TabLayout监听Indicator指示器选中状态

时间:2024-04-05 11:01:23

项目中碰到一个问题,MainActivity中有一个FrameLayout,四个Fragment(主页、社区、商城、我的)切换。其中社区Fragment中有ViewPager2+TabLayout,下面还有4个子Fragment(推荐、关注、我的、好友)。对每个Fragment缓存rootView后,有个问题:切换到社区Fragment中,滑动ViewPager2到一半时,不松开手,切换到其他Fragment(比如切换到商城Fragment),再切换回社区Fragment,ViewPager2的滑动状态还保持在上次的 滑动到一半的状态

ViewPager2+TabLayout监听Indicator指示器选中状态

正常状态是,松开手时,它会自动滑到指示器文本选中的positon对应的Fragment

但现在切换到其他Fragment再切换回来后,它没有自动滑动到Tab指示器文本选中position对应的的Fragment

 

想了个办法,监听TabLayout的指示器中的文本的选中状态,然后在切换到其他Fragment时(主页、商城、我的),手动设置到对应的Fragment(推荐、关注、我的、好友)。这样就不会出现ViewPager2卡在中间的情况了

试了两个监听器:

new ViewPager2.OnPageChangeCallback() {
    //只有松开手,选中后,才会调用
    @Override
    public void onPageSelected(int position) {
        super.onPageSelected(position);
    }

    //当ViewPager2左右滑动超过一定距离后position的位置会更新,但是Tab指示器的文本选中position和这个不能准确对应,因此放弃。Tab指示器的文本选中position ViewPager2滑动超过一半的样子 就会更新,但这里的position,要大概超过四分之三才会更新
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        super.onPageScrolled(position, positionOffset, positionOffsetPixels);

        mPosition = position;
        logD("mPosition=" + mPosition);
    }

    //滑动状态,比如拖拽、松手等等
    @Override
    public void onPageScrollStateChanged(int state) {
        super.onPageScrollStateChanged(state);
    }
};

这个也不行,也是松开手后才会回调。我要的是没有松手也能监听到Tab指示器文本选中状态

new TabLayout.OnTabSelectedListener() {
    //选中
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        mPosition = tab.getPosition();
        logD("mPosition=" + mPosition);
    }

    //取消选中
    @Override
    public void onTabUnselected(TabLayout.Tab tab) {

    }

    //重复选中
    @Override
    public void onTabReselected(TabLayout.Tab tab) {

    }
};

 

显然系统没有提供监听器来满足我的需求,那就只能想办法了

在源码里找,滑动时Tab指示器文本会切换选中状态,找到它是何时、在哪被切换的,那就是我要的监听了

从vp_community.setCurrentItem(mPosition);的源码里找,setCurrentItem调用后指示器文本选中会改变,找到修改指示器文本选中状态的代码,然后再找这代码被哪里调用,调用的地方的判断条件就是我要的了

辗转找到了TabLayoutMediator,滑动ViewPager2的时候Tab会改变,那应该是这个类里做了什么操作(ViewPager2和TabLayout的联动是由这个类管理的)

找到这个方法,滑动ViewPager2时会被回调

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
  TabLayout tabLayout = tabLayoutRef.get();
  if (tabLayout != null) {
    // Only update the text selection if we're not settling, or we are settling after
    // being dragged
    boolean updateText =
        scrollState != SCROLL_STATE_SETTLING || previousScrollState == SCROLL_STATE_DRAGGING;
    // Update the indicator if we're not settling after being idle. This is caused
    // from a setCurrentItem() call and will be handled by an animation from
    // onPageSelected() instead.
    boolean updateIndicator =
        !(scrollState == SCROLL_STATE_SETTLING && previousScrollState == SCROLL_STATE_IDLE);
    tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);//TabLayout指示器文本选中状态就是在这里被更改的
  }
}

但好像并没有条件判断的代码。调试了下,看滑动(不松手)导致TabLayout指示器文本选中状态改变时,这几个值会不会有什么不同,如果有,我就把这段代码拿过去用就行了。不过调试发现并没有改变

然后继续往下找

public void setScrollPosition(
    int position,
    float positionOffset,
    boolean updateSelectedText,
    boolean updateIndicatorPosition) {
  final int roundedPosition = Math.round(position + positionOffset);
  if (roundedPosition < 0 || roundedPosition >= slidingTabIndicator.getChildCount()) {
    return;
  }

  // Set the indicator position, if enabled
  if (updateIndicatorPosition) {
    slidingTabIndicator.setIndicatorPositionFromTabPosition(position, positionOffset);
  }

  // Now update the scroll position, canceling any running animation
  if (scrollAnimator != null && scrollAnimator.isRunning()) {
    scrollAnimator.cancel();
  }
  scrollTo(calculateScrollXForTab(position, positionOffset), 0);

  // Update the 'selected state' view as we scroll, if enabled
  if (updateSelectedText) {
    setSelectedTabView(roundedPosition);//这里,继续往下找
  }
}

但是始终没有条件判断,没有什么时候切换的条件判断 

private void setSelectedTabView(int position) {
  final int tabCount = slidingTabIndicator.getChildCount();
  if (position < tabCount) {
    for (int i = 0; i < tabCount; i++) {
      final View child = slidingTabIndicator.getChildAt(i);
      child.setSelected(i == position);//但是找到了这个,继续往下,就到View的源码里了。原来Tab的文本选中状态是通过View的setSelected方法来设置的
      child.setActivated(i == position);
    }
  }
}

然后试了下,可以获取到TabLayout指示器文本选中的position。这个能获取到滑动时未松手状态 选中的Tab的position

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    super.onPageScrolled(position, positionOffset, positionOffsetPixels);

    for (int i = 0; i < tl_title.getTabCount(); i++) {
        TabLayout.Tab tab = tl_title.getTabAt(i);
        if (tab.view.isSelected()) {
            logD("tab.getPosition()=" + tab.getPosition());
            mPosition = tab.getPosition();
            break;
        }
    }
}

博客写到一半的时候发现position上面已经给了ViewPager2+TabLayout监听Indicator指示器选中状态