TabLayout的tab滑块适配

前言

        TabLayout继承自HorizontalScrollView,用作页面切换指示器,因使用简便功能强大而广泛使用在App中。TabLayout通常都和ViewPager结合使用,而且一般情况下本身自带的属性也能满足大部分产品需求,效果如下:

TabLayout可以在相关属性上设置tab选中的指示器和背景图,但在效果上并不是特别完美。在不松开手指情况下,fragment左右滑动,只有指示器跟随移动,tab背景图并不会移动,只有当达到一定的偏移量的时候才会跳到下(上)一个tab。为了弥补这个不足之处,可以通过自定义view(SliderLayout)解决这个缺陷。

自定义View设计思路

1、滑块的绘制,可以读取资源文件里的drawable,也可以自己绘制一个。

2、滑块的移动(1),监听TabLayoutOnPageChangeListener,fragment的左右滑动要同步到到tab滑块的移动,即fragment的移动偏移量相当于tab滑块的偏移量。

3、滑块的移动(2),当TabLayout的tabMode属性改为scrollable时,还需处理TabLayout在横向移动时,滑块也要跟随当前选中的tab移动,可在OnScrollChangeListener监听方法处理。

监听方法相关代码

1、滑块的移动(1)

public static class SliderOnPageChangeListener extends TabLayout.TabLayoutOnPageChangeListener {
        private final SoftReference<SliderLayout> mSliderLayoutRef;

        public SliderOnPageChangeListener(TabLayout tabLayout, SliderLayout layout) {
            super(tabLayout);
            mSliderLayoutRef = new SoftReference<>(layout);
            layout.setupWithTabLayout(tabLayout);
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            super.onPageScrollStateChanged(state);
        }

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels);
            final SliderLayout layout = mSliderLayoutRef.get();
            if (layout != null) {
                layout.setScrollPosition(position, positionOffset);
            }
        }
    }
    /**
     * 把滑块滑动到指定的位置
     *
     * @param position       当前位置
     * @param positionOffset 滑动到下一个或上一个位置比例
     */
    private void setScrollPosition(int position, float positionOffset) {
        final int roundedPosition = Math.round(position + positionOffset);
        if (roundedPosition < 0 || roundedPosition >= totalNum) {
            return;
        }
        float scrollX = calculateScrollXForTab(position, positionOffset);
        scrollTo((int) scrollX, 0);
    }
    /**
     * 计算滑块需要滑动的距离
     *
     * @param position       当前选择的位置
     * @param positionOffset 滑动位置的百分百
     * @return 滑动的距离
     */
    private int calculateScrollXForTab(int position, float positionOffset) {
        if (mTabLayout == null) return 0;
        LinearLayout mTabStrip = getTabStrip();
        if (mTabStrip == null) return 0;
        //当前选择的View
        final View selectedChild = mTabStrip.getChildAt(position);
        //下一个View
        final View nextChild = position + 1 < mTabStrip.getChildCount()
                ? mTabStrip.getChildAt(position + 1)
                : null;
        //当前选择的View的宽度
        final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
        //下一个View的宽度
        final int nextWidth = nextChild != null ? nextChild.getWidth() : 0;
        //当前选择的View的左边位置,view的方位
        final int left = selectedChild != null ? selectedChild.getLeft() : 0;
        //计算滑块需要滑动的距离,左 + ,右 - ;
        int scrollX = -(left + ((int) ((selectedWidth + nextWidth) * positionOffset * 0.5f)));
        //现在是把SliderLayout直接放在TableLayout中的所以就不许要考虑TableLayout本身的滑动
        if (mTabLayout.getTabMode() == TabLayout.MODE_SCROLLABLE) {//当为滑动模式的时候TabLayout会有水平方向的滑动
            scrollX += mTabLayout.getScrollX();//计算在TabLayout有滑动的时候,滑块相对的滑动距离
        }
        return scrollX;
    }

监听方法(1)作用到ViewPager

    mViewPager.clearOnPageChangeListeners();
    mViewPager.addOnPageChangeListener(new SliderLayout.SliderOnPageChangeListener(mTabLayout, sliderLayout));

使用监听方法(1)后效果图如下:

       由上图可见,fragment左右滑动,tab滑动也能很好的跟随移动了,但当TabLayout横向移动的时候,tab滑块只会停留在当前位置上,并没有紧跟着选中的tab,因此,则需要用到监听方法(2)。

2、滑块的移动(2)

    @RequiresApi(api = Build.VERSION_CODES.M)
    public static class SlideOnScrollChangeListener implements OnScrollChangeListener {
        private final SoftReference<SliderLayout> mSliderLayoutRef;
        private TabLayout mTabLayout;
        private LinearLayout tabStrip;
        public SlideOnScrollChangeListener(TabLayout tabLayout, SliderLayout layout) {
            mSliderLayoutRef = new SoftReference<>(layout);
            mTabLayout = tabLayout;
            tabStrip = (LinearLayout) mTabLayout.getChildAt(0);
        }

        @Override
        public void onScrollChange(View view, int i, int i1, int i2, int i3) {
            final SliderLayout layout = mSliderLayoutRef.get();
            int selectedTabPosition = mTabLayout.getSelectedTabPosition();
            if (layout != null) {
                int width = tabStrip.getChildAt(selectedTabPosition).getWidth();
                layout.scrollTo(i - width * selectedTabPosition,0);
            }
        }
    }

监听方法(2)作用到TabLayout

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        mTabLayout.setOnScrollChangeListener(new SliderLayout.SlideOnScrollChangeListener(mTabLayout, sliderLayout));
    }

使用监听方法(2)后效果图如下:

       由此可见,之前的缺陷已经完美修复。当前的滑块既可以跟随fragment切换滑动,也可以完美跟随TabLayout的横向滑动而移动了。

项目代码下载

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值