TabLayoutNiubility

Introduction: Android 自定义控件之 RecyclerView 打造万能 ViewPager TabLayout(仿今日头条 Tab 滑动、Tab 多布局、indicator 蠕动、自定义 indicator、文字颜色渐变、自定义 Fragment)
More: Author   ReportBugs   
Tags:
tab-tablayout-recyclerview-导航-

文章目录

GitHub:https://github.com/AnJiaoDe/TabLayoutNiubility

CSDN:https://blog.csdn.net/confusing_awakening/article/details/107635695

该轮子特异功能如下:

使用方法

注意:该轮子适用于 androidx 中的 ViewPager2 和 ViewPager

注意:如果轮子死活下载不下来,说明 maven 地址有毛病,你需要找到 jitpack 的官网首页,查看最新的官网地址

注意:记得去 gayhub 查看最新版本,最新版本最 niubility

详细使用如下

Tab 均分不滑动(ViewPager、ViewPager2 均支持)

Tab 滑动、 indicator 蠕动、多布局(ViewPager、ViewPager2 均支持)

根据 item 个数动态设置 Tab 均分还是滑动

Tab 文字颜色渐变(ViewPager、ViewPager2 均支持)

自定义 Indicator 如三角形(ViewPager、ViewPager2 均支持)

ViewPager 双层嵌套(建议不要使用 ViewPager2 进行双层嵌套,ViewPager2 嵌套滑动冲突几乎无法处理,贼鸡儿坑)

仿微信主页 Tab

相关 API

TabMediator

FragmentPageAdapter

TabAdapter

TabLayoutScroll、TabLayoutNoScroll、TabLayoutMulti、IndicatorLineView 、 IndicatorTriangleView

TabLayoutScroll 和 indicator style 设置

自定义 indicator

实现原理剖析

说真的,这自定义控件还真不简单

涉及到的难点场景

搞清楚 ViewPager 监听的 onPageSelected、onPageScrolled 和 onPageScrollStateChanged 回调执行特点

自定义 HorizontalRecyclerView 实现 TabLayout

源码如下

TabLayout 的 item 宽度均分

RecyclerView 的 item 刷新如何做到不闪烁

UML 类图如下

面向接口编程(面向多态编程)的思想

CSDN:https://blog.csdn.net/confusing_awakening/article/details/107635695

该轮子特异功能如下:

Tab 均分不滑动(ViewPager、ViewPager2 均支持)

Tab 滑动、 indicator 蠕动、多布局(ViewPager、ViewPager2 均支持)

根据 item 个数动态设置 Tab 均分还是滑动

Tab 文字颜色渐变(ViewPager、ViewPager2 均支持)

自定义 Indicator 如三角形(ViewPager、ViewPager2 均支持)

ViewPager 双层嵌套(建议不要使用 ViewPager2 进行双层嵌套,ViewPager2 嵌套滑动冲突几乎无法处理,贼鸡儿坑)

仿微信主页 Tab

使用方法

注意:该轮子适用于 androidx 中的 ViewPager2 和 ViewPager

1.工程目录下的build.gradle中添加代码:

注意:如果轮子死活下载不下来,说明 maven 地址有毛病,你需要找到 jitpack 的官网首页,查看最新的官网地址

allprojects {
        repositories {
                    maven { url 'https://www.jitpack.io' }
        }
    }

2.直接在需要使用的模块的build.gradle中添加代码:

dependencies {
api 'com.github.AnJiaoDe:TabLayoutNiubility:V1.3.1'
}

注意:记得去 gayhub 查看最新版本,最新版本最 niubility

3.如果你想使用ViewPager2,那么添加代码:

api 'androidx.viewpager2:viewpager2:1.0.0'//版本必须>=1.0.0

4.混淆已配置到库内部,无需做混淆配置

详细使用如下

Tab 均分不滑动(ViewPager、ViewPager2 均支持)

在这里插入图片描述 activity 布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.cy.tablayoutniubility.TabLayoutNoScroll
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        app:space_horizontal="0dp"
        android:background="#fff">

        <com.cy.tablayoutniubility.IndicatorLineView
            android:layout_width="match_parent"
            app:width_indicator_max="80dp"
            app:width_indicator_selected="30dp"
            android:layout_height="wrap_content" />
    </com.cy.tablayoutniubility.TabLayoutNoScroll>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:overScrollMode="never" />
</LinearLayout>

tab_item 布局:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textColor="#444444"
    android:textSize="16sp" >
</TextView>

JAVA 代码:

    ViewPager2 viewPager2 = findViewById(R.id.view_pager);
        TabLayoutNoScroll tabLayoutLine = findViewById(R.id.tablayout);
//        tabLayoutLine.setSpace_horizontal(0).setSpace_vertical(0);
        FragPageAdapterVp2NoScroll<String> fragmentPageAdapter = new FragPageAdapterVp2NoScroll<String>(this) {
            @Override
            public Fragment createFragment(String bean, int position) {
                return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));
            }

            @Override
            public void bindDataToTab(TabNoScrollViewHolder holder, int position, String bean, boolean isSelected) {
                TextView textView = holder.getView(R.id.tv);
                if (isSelected) {
                    textView.setTextColor(0xffe45540);
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
                } else {
                    textView.setTextColor(0xff444444);
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
                }
                textView.setText(bean);
            }

            @Override
            public int getTabLayoutID(int position, String bean) {
                return R.layout.item_tab_center;
            }
        };

        TabAdapterNoScroll<String> tabAdapter = new TabMediatorVp2NoScroll<String>(tabLayoutLine, viewPager2).setAdapter(fragmentPageAdapter);

        List<String> list = new ArrayList<>();
        list.add("关注");
        list.add("推荐");
        list.add("上课");
        list.add("抗疫");
        fragmentPageAdapter.add(list);
        tabAdapter.add(list);

Tab 滑动、 indicator 蠕动、多布局(ViewPager、ViewPager2 均支持)

多布局:

            @Override
            public int getTabLayoutID(int position, String bean) {
                if (position == 0) {
                    return R.layout.item_tab_msg;
                }
                return R.layout.item_tab;
            }

在这里插入图片描述 activity 布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.cy.tablayoutniubility.TabLayoutScroll
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="#fff">

        <com.cy.tablayoutniubility.IndicatorLineView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </com.cy.tablayoutniubility.TabLayoutScroll>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:overScrollMode="never" />
</LinearLayout>

tab item 布局:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:gravity="center"
    android:textSize="16sp"
    android:textColor="#444444"
    android:id="@+id/tv">

</TextView>

JAVA 代码:

   ViewPager2 viewPager2 = findViewById(R.id.view_pager);
        TabLayoutScroll tabLayoutLine = findViewById(R.id.tablayout);
//        tabLayoutLine.setSpace_horizontal(dpAdapt(20)).setSpace_vertical(dpAdapt(8));
        FragPageAdapterVp2<String> fragmentPageAdapter = new FragPageAdapterVp2<String>(this) {

            @Override
            public Fragment createFragment(String bean, int position) {
                return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));
            }

            @Override
            public void bindDataToTab(TabViewHolder holder, int position, String bean, boolean isSelected) {
                TextView textView = holder.getView(R.id.tv);
                if (isSelected) {
                    textView.setTextColor(0xffe45540);
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
                } else {
                    textView.setTextColor(0xff444444);
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
                }
                textView.setText(bean);
            }

            @Override
            public int getTabLayoutID(int position, String bean) {
                if (position == 0) {
                    return R.layout.item_tab_msg;
                }
                return R.layout.item_tab;
            }
        };
        TabAdapter<String> tabAdapter = new TabMediatorVp2<String>(tabLayoutLine, viewPager2).setAdapter(fragmentPageAdapter);

        List<String> list = new ArrayList<>();
        list.add("关注");
        list.add("推荐");
        list.add("视频");
        list.add("抗疫");
        list.add("酷玩");
        list.add("彩票");
        list.add("漫画");
        fragmentPageAdapter.add(list);
        tabAdapter.add(list);

根据 item 个数动态设置 Tab 均分还是滑动

使用TabLayoutMulti

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.cy.tablayoutniubility.TabLayoutMulti
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        app:space_horizontal="0dp"
        android:background="#fff">

        <com.cy.tablayoutniubility.IndicatorLineView
            android:layout_width="match_parent"
            app:width_indicator_max="80dp"
            app:width_indicator_selected="30dp"
            android:layout_height="wrap_content" />
    </com.cy.tablayoutniubility.TabLayoutMulti>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:overScrollMode="never" />
</LinearLayout>

JAVA 代码:

ViewPager2 viewPager2 = findViewById(R.id.view_pager);
        TabLayoutMulti tabLayoutMulti = findViewById(R.id.tablayout);
//        tabLayoutLine.setSpace_horizontal(0).setSpace_vertical(0);
        List<String> list = new ArrayList<>();
        list.add("关注");
        list.add("推荐");
        list.add("上课");
        list.add("抗疫");
        list.add("文化");
//        list.add("经济");
//        list.add("幸福里");

        //根据 item 个数设置是否需要滚动
        if (list.size() > 6) tabLayoutMulti.setScrollable(true);

        BaseFragPageAdapterVp2 fragmentPageAdapter;

        if (tabLayoutMulti.isScrollable()) {
            fragmentPageAdapter = new FragPageAdapterVp2<String>(this) {
                @Override
                public Fragment createFragment(String bean, int position) {
                    return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));
                }

                @Override
                public void bindDataToTab(TabViewHolder holder, int position, String bean, boolean isSelected) {
                    TextView textView = holder.getView(R.id.tv);
                    if (isSelected) {
                        textView.setTextColor(0xffe45540);
                        textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
                    } else {
                        textView.setTextColor(0xff444444);
                        textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
                    }
                    textView.setText(bean);
                }

                @Override
                public int getTabLayoutID(int position, String bean) {
                    return R.layout.item_tab_center;
                }
            };
        }else {
            fragmentPageAdapter = new FragPageAdapterVp2NoScroll<String>(this) {
                @Override
                public Fragment createFragment(String bean, int position) {
                    return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));
                }

                @Override
                public void bindDataToTab(TabNoScrollViewHolder holder, int position, String bean, boolean isSelected) {
                    LogUtils.log("bindDataToTab");
                    TextView textView = holder.getView(R.id.tv);
                    if (isSelected) {
                        textView.setTextColor(0xffe45540);
                        textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
                    } else {
                        textView.setTextColor(0xff444444);
                        textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
                    }
                    textView.setText(bean);
                }

                @Override
                public int getTabLayoutID(int position, String bean) {
                    return R.layout.item_tab_center;
                }
            };
        }


        ITabAdapter tabAdapter = new TabMediatorMulti<String>(tabLayoutMulti).setAdapter(viewPager2, fragmentPageAdapter);
        fragmentPageAdapter.add(list);
        tabAdapter.add(list);
        //或者
//        if(tabLayoutMulti.isScrollable()){
//            TabAdapter tabAdapt= (TabAdapter) tabAdapter.getAdapter();
//            tabAdapt.add(list);
//        }else {
//            TabAdapterNoScroll tabAdapt= (TabAdapterNoScroll) tabAdapter.getAdapter();
//            tabAdapt.add(list);
//        }

Tab 文字颜色渐变(ViewPager、ViewPager2 均支持)

在这里插入图片描述 activity 代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:background="#fff"
    android:layout_height="match_parent" >
    <com.cy.tablayoutniubility.TabLayoutScroll
        android:layout_width="match_parent"
        android:background="#fff"
        android:layout_height="40dp"
        android:id="@+id/tablayout">
        <com.cy.tablayoutniubility.IndicatorLineView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </com.cy.tablayoutniubility.TabLayoutScroll>
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#eee"/>
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:overScrollMode="never"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

tab item 布局:

<?xml version="1.0" encoding="utf-8"?>
<com.cy.tablayoutniubility.TabGradientTextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/tv"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:gravity="center"
    android:textSize="16sp"
    app:textColorNormal="#ff444444"
    app:textColorSelected="#ffe45540">

</com.cy.tablayoutniubility.TabGradientTextView>

JAVA 代码:

     ViewPager2 viewPager2= findViewById(R.id.view_pager);
        TabLayoutScroll tabLayoutLine= findViewById(R.id.tablayout);

//        tabLayoutLine.setSpace_horizontal(dpAdapt(20)).setSpace_vertical(dpAdapt(8));
        FragPageAdapterVp2<String> fragmentPageAdapter = new FragPageAdapterVp2<String>(this) {

            @Override
            public Fragment createFragment(String bean, int position) {
                return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));
            }

            @Override
            public void bindDataToTab(TabViewHolder holder, int position, String bean, boolean isSelected) {
                TabGradientTextView textView = holder.getView(R.id.tv);
                if (isSelected) {
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
                    //因为            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    //positionOffset 没有为 1 的时候
                    //必须
                    textView.setProgress(1);
                } else {
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
                    //因为快速滑动时,            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    //positionOffset 不会出现 0
                    //必须
                    textView.setProgress(0);
                }
                textView.setText(bean);
            }

            @Override
            public int getTabLayoutID(int position, String bean) {
                return R.layout.item_tab_gradient;
            }

            @Override
            public void onTabScrolled(TabViewHolder holderCurrent, int positionCurrent, boolean fromLeft2RightCurrent, float positionOffsetCurrent, TabViewHolder holder2, int position2, boolean fromLeft2Right2, float positionOffset2) {
                super.onTabScrolled(holderCurrent, positionCurrent, fromLeft2RightCurrent, positionOffsetCurrent, holder2, position2, fromLeft2Right2, positionOffset2);
                TabGradientTextView textViewCurrent = holderCurrent.getView(R.id.tv);
                TabGradientTextView textView2= holder2.getView(R.id.tv);
                LogUtils.log("onTabScrolled");
                textViewCurrent.setDirection(fromLeft2RightCurrent?TabGradientTextView.DIRECTION_FROM_LEFT:TabGradientTextView.DIRECTION_FROM_RIGHT)
                        .setProgress(positionOffsetCurrent);
                textView2.setDirection(fromLeft2Right2?TabGradientTextView.DIRECTION_FROM_LEFT:TabGradientTextView.DIRECTION_FROM_RIGHT)
                        .setProgress(positionOffset2);


            }
        };

        TabAdapter<String> tabAdapter = new TabMediatorVp2<String>(tabLayoutLine, viewPager2).setAdapter(fragmentPageAdapter);

        List<String> list = new ArrayList<>();
        list.add("关注");
        list.add("推荐");
        list.add("视频");
        list.add("抗疫");
        list.add("彩票");
        list.add("漫画");
        fragmentPageAdapter.add(list);
        tabAdapter.add(list);

自定义 Indicator 如三角形(ViewPager、ViewPager2 均支持)

可以在布局或者代码中设置三角形的选中宽度和最大宽度,使三角形宽度不改变

在这里插入图片描述 activity 代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:background="#fff"
    android:layout_height="match_parent" >
    <com.cy.tablayoutniubility.TabLayoutScroll
        android:layout_width="match_parent"
        android:background="#fff"
        android:layout_height="40dp"
        android:id="@+id/tablayout">
        <com.cy.tablayoutniubility.IndicatorTriangleView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </com.cy.tablayoutniubility.TabLayoutScroll>
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#eee"/>
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:overScrollMode="never"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

tab item 布局:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:gravity="center"
    android:textSize="16sp"
    android:textColor="#444444"
    android:id="@+id/tv">

</TextView>

JAVA 代码:

   ViewPager2 viewPager2= findViewById(R.id.view_pager);
        TabLayoutScroll tabLayoutNiubility= findViewById(R.id.tablayout);

//        tabLayoutTriangle.setSpace_horizontal(dpAdapt(20)).setSpace_vertical(dpAdapt(8));
        FragPageAdapterVp2<String> fragmentPageAdapter = new FragPageAdapterVp2<String>(this) {

            @Override
            public Fragment createFragment(String bean, int position) {
                return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));
            }

            @Override
            public void bindDataToTab(TabViewHolder holder, int position, String bean, boolean isSelected) {
                TextView textView = holder.getView(R.id.tv);
                if (isSelected) {
                    textView.setTextColor(0xffe45540);
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
                } else {
                    textView.setTextColor(0xff444444);
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
                }
                textView.setText(bean);
            }

            @Override
            public int getTabLayoutID(int position, String bean) {
                return R.layout.item_tab;
            }
        };

        TabAdapter<String> tabAdapter = new TabMediatorVp2<String>(tabLayoutNiubility, viewPager2).setAdapter(fragmentPageAdapter);

        List<String> list = new ArrayList<>();
        list.add("关注");
        list.add("推荐");
        list.add("彩票");
        list.add("漫画");
        fragmentPageAdapter.add(list);
        tabAdapter.add(list);

ViewPager 双层嵌套(建议不要使用 ViewPager2 进行双层嵌套,ViewPager2 嵌套滑动冲突几乎无法处理,贼鸡儿坑)

在这里插入图片描述 activity 布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.cy.tablayoutniubility.TabLayoutScroll
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="#fff">

        <com.cy.tablayoutniubility.IndicatorLineView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </com.cy.tablayoutniubility.TabLayoutScroll>

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:overScrollMode="never" />
</LinearLayout>

activity 代码:

  ViewPager viewPager= findViewById(R.id.view_pager);
        TabLayoutScroll tabLayoutLine= findViewById(R.id.tablayout);

//        tabLayoutLine.setSpace_horizontal(dpAdapt(20)).setSpace_vertical(dpAdapt(8));
        FragPageAdapterVp<String> fragmentPageAdapter = new FragPageAdapterVp<String>(getSupportFragmentManager(),
                FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            @Override
            public Fragment createFragment(String bean, int position) {
                return FragmentTab1.newInstance(FragmentTab1.TAB_NAME1, getList_bean().get(position));
            }

            @Override
            public void bindDataToTab(TabViewHolder holder, int position, String bean, boolean isSelected) {
                TextView textView = holder.getView(R.id.tv);
                if (isSelected) {
                    textView.setTextColor(0xffe45540);
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
                } else {
                    textView.setTextColor(0xff444444);
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
                }
                textView.setText(bean);
            }

            @Override
            public int getTabLayoutID(int position, String bean) {
                if (position == 0) {
                    return R.layout.item_tab_msg;
                }
                return R.layout.item_tab;
            }
        };

        TabAdapter<String> tabAdapter = new TabMediatorVp<String>(tabLayoutLine, viewPager).setAdapter(fragmentPageAdapter);

        List<String> list = new ArrayList<>();
        list.add("关注");
        list.add("推荐");
        list.add("酷玩");
        list.add("彩票");
        list.add("漫画");
        fragmentPageAdapter.add(list);
        tabAdapter.add(list);

Fragment 代码:

     viewPager = view.findViewById(R.id.view_pager);
        tabLayoutLine = view.findViewById(R.id.tablayout);
        FragPageAdapterVp<String> fragmentPageAdapter = new FragPageAdapterVp<String>(getChildFragmentManager(),
                FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            @Override
            public Fragment createFragment(String bean, int position) {
                return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));
            }

            @Override
            public void bindDataToTab(TabViewHolder holder, int position, String bean, boolean isSelected) {
                TextView textView = holder.getView(R.id.tv);
                if (isSelected) {
                    LogUtils.log("bindDataToTabisSelected",position);
                    textView.setTextColor(0xffe45540);
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
                } else {
                    LogUtils.log("bindDataToTab",position);
                    textView.setTextColor(0xff444444);
                    textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
                }
                textView.setText(bean);
            }

            @Override
            public int getTabLayoutID(int position, String bean) {
                return R.layout.item_tab;
            }
        };
        TabAdapter<String> tabAdapter = new TabMediatorVp<String>(tabLayoutLine, viewPager).setAdapter(fragmentPageAdapter);
        if (getArguments() != null) {
            String tab_name1 = getArguments().getString(TAB_NAME1);
            List<String> list = new ArrayList<>();
            list.add(tab_name1 + "0");
            list.add(tab_name1 + "1");
            list.add(tab_name1 + "2");
            list.add(tab_name1 + "3");
            list.add(tab_name1 + "4");
            list.add(tab_name1 + "5");
            list.add(tab_name1 + "6");
            list.add(tab_name1 + "7");
            list.add(tab_name1 + "8");
            list.add(tab_name1 + "9");
            list.add(tab_name1 + "10");
            list.add(tab_name1 + "11");
            list.add(tab_name1 + "12");
            list.add(tab_name1 + "13");
            fragmentPageAdapter.add(list);
            tabAdapter.add(list);
        }

仿微信主页 Tab

在这里插入图片描述

activity 布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:overScrollMode="never" />

    <com.cy.tablayoutniubility.TabLayoutNoScroll
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="#fff">
        <com.cy.tablayoutniubility.IndicatorNullView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </com.cy.tablayoutniubility.TabLayoutNoScroll>


</LinearLayout>

item 布局:

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scaleType="centerInside" />

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:gravity="center"
        android:textSize="12sp"></TextView>

</LinearLayout>

JAVA 代码:

 ViewPager2 viewPager2= findViewById(R.id.view_pager);
        TabLayoutNoScroll tabLayoutNoScroll= findViewById(R.id.tablayout);
        FragPageAdapterVp2NoScroll<TabBean> fragmentPageAdapter = new FragPageAdapterVp2NoScroll<TabBean>(this) {

            @Override
            public Fragment createFragment(TabBean bean, int position) {
                return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position).getText());
            }

            @Override
            public void bindDataToTab(TabNoScrollViewHolder holder, int position, TabBean bean, boolean isSelected) {
                TextView textView = holder.getView(R.id.tv);
                if (isSelected) {
                    textView.setTextColor(0xff00ff00);
                    holder.setImageResource(R.id.iv,bean.getResID_selected());
                } else {
                    textView.setTextColor(0xff444444);
                    holder.setImageResource(R.id.iv,bean.getResID_normal());
                }
                textView.setText(bean.getText());
            }

            @Override
            public int getTabLayoutID(int position, TabBean bean) {
                if (position == 2) {
                    return R.layout.item_tab_main_circle;
                }
                return R.layout.item_tab_main;
            }
        };

        TabAdapterNoScroll<TabBean> tabAdapter = new TabMediatorVp2NoScroll<TabBean>(tabLayoutNoScroll, viewPager2).setAdapter(fragmentPageAdapter);

        List<TabBean> list = new ArrayList<>();
        list.add(new TabBean("消息",R.drawable.msg,R.drawable.msg_selected));
        list.add(new TabBean("通讯录",R.drawable.friends,R.drawable.friends_selected));
        list.add(new TabBean("朋友圈",R.drawable.circle,R.drawable.circle_selected));
        list.add(new TabBean("我",R.drawable.my,R.drawable.my_selected));
        fragmentPageAdapter.add(list);
        tabAdapter.add(list);

相关 API

TabMediator

TabMediatorVp

TabMediatorVp2

TabMediatorVp2NoScroll(不可滚动)

TabMediatorVpNoScroll(不可滚动)

TabMediatorMulti(可用于 ViewPager 和 ViewPager2,可根据 item 个数动态设置是否滚动)

FragmentPageAdapter

拥有一系列addremove函数

FragmentPageAdapterVp2(Tab 可滑动,ViewPager2 使用)

FragmentPageAdapterVp(Tab 可滑动,ViewPager 使用)

FragmentPageAdapterVp2NoScroll(Tab 不可滑动,ViewPager2 使用)

FragmentPageAdapterVpNoScroll(Tab 不可滑动,ViewPager 使用)

TabAdapter

TabAdapter(Tab 的 Adapter,继承自 RecyclerView 的 Adapter) 拥有一系列addremove函数

TabAdapterNoScroll(Tab 的 Adapter,不能滚动)

TabLayoutScroll、TabLayoutNoScroll、TabLayoutMulti、IndicatorLineView 、 IndicatorTriangleView

TabLayoutScroll是可滚动 tab,TabLayoutNoScroll是不可滚动 tab,里面需要嵌套indicatorview,可以选择IndicatorLineView线条 indicator、IndicatorTriangleView三角形 indicator, TabLayoutMulti用于需要根据 item 个数动态设置是否滚动

<com.cy.tablayoutniubility.TabLayoutNiubility
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="#fff">

        <com.cy.tablayoutniubility.IndicatorLineView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </com.cy.tablayoutniubility.TabLayoutNiubility>

TabLayoutScroll 和 indicator style 设置

TabLayoutNiubility可设置space

indicator可设置颜色、选中长度、最大长度、高度、radius 等

可在布局中使用 比如:

<com.cy.tablayoutniubility.IndicatorLineView
            android:layout_width="match_parent"
            app:width_indicator_max="80dp"
            app:width_indicator_selected="30dp"
            android:layout_height="wrap_content" />
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="height_indicator" format="dimension|reference" />
    <attr name="width_indicator_selected" format="dimension|reference"/>
    <attr name="width_indicator_max" format="dimension|reference"/>
    <attr name="color_indicator" format="color|reference"/>

    <declare-styleable name="TabGradientTextView">
        <attr name="textColorNormal" format="color|reference" />
        <attr name="textColorSelected" format="color|reference"/>
    </declare-styleable>
    <declare-styleable name="TabLayoutNiubility">
        <attr name="space_vertical" format="dimension|reference"/>
        <attr name="space_horizontal" format="dimension|reference"/>
    </declare-styleable>
    <declare-styleable name="IndicatorLineView">
        <attr name="height_indicator" />
        <attr name="width_indicator_selected"/>
        <attr name="width_indicator_max"/>
        <attr name="radius_indicator" format="dimension|reference"/>
        <attr name="color_indicator" />
    </declare-styleable>
    <declare-styleable name="IndicatorTriangleView">
        <attr name="height_indicator" />
        <attr name="width_indicator_selected"/>
        <attr name="width_indicator_max"/>
        <attr name="color_indicator" />
    </declare-styleable>
</resources>

可在代码中使用 比如:

tabLayoutScroll.getIndicatorView().getIndicator().setColor_indicator();
/**
     * 设置 indicator 进度
     * @param progress
     * @return
     */
    public Indicator setProgress(int progress) {
        this.progress = progress;
        viewIndicator.invalidate();
        return  this;
    }

    public int getProgress() {
        return progress;
    }

    public int getWidth_indicator_max() {
        return width_indicator_max;
    }

    /**
     * 设置 indicator 最大长度
     * @param width_indicator_max
     * @return
     */
    public Indicator setWidth_indicator_max(int width_indicator_max) {
        this.width_indicator_max = width_indicator_max;
        return  this;
    }

    /**
     * 设置 indicator 颜色
     * @param color_indicator
     * @return
     */
    public Indicator setColor_indicator(int color_indicator) {
        paint_indicator.setColor(color_indicator);
        return  this;
    }

    /**
     * 设置 indicator 高度
     * @param height_indicator
     * @return
     */
    public Indicator setHeight_indicator(int height_indicator) {
        this.height_indicator = height_indicator;
        return  this;
    }

    public int getHeight_indicator() {
        return height_indicator;
    }

    public Paint getPaint_indicator() {
        return paint_indicator;
    }

    /**
     * 设置 indicator 选中时的长度
     * @param width_indicator_selected
     * @return
     */
    public Indicator setWidth_indicator_selected(int width_indicator_selected) {
        this.width_indicator_selected = width_indicator_selected;
        return  this;
    }

    public int getWidth_indicator_selected() {
        return width_indicator_selected;
    }

    public int getWidth_indicator() {
        return width_indicator;
    }

    /**
     * 设置 indicator 当前长度
     * @param width_indicator
     * @return
     */
    public Indicator setWidth_indicator(int width_indicator) {
        this.width_indicator = Math.min(width_indicator_max, width_indicator);
        return  this;
    }

自定义 indicator

1.实现IIndicatorView 接口 2.创建Indicator 对象、设置基本默认参数 3.实现draw方法,根据progresswidth_indicator绘制自己想要的样式

比如库里提供的 IndicatorTriangleView

public class IndicatorTriangleView extends View implements IIndicatorView {
    private Path path;
    private Indicator indicator;
    private int height;
    public IndicatorTriangleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        indicator=new Indicator(this);
        //实例化路径
        path = new Path();

        indicator=new Indicator(this);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicatorTriangleView);

        indicator.setWidth_indicator_selected(typedArray.getDimensionPixelSize(R.styleable.IndicatorTriangleView_width_indicator_selected, ScreenUtils.dpAdapt(context,12)));
        indicator.setWidth_indicator_max(typedArray.getDimensionPixelSize(R.styleable.IndicatorTriangleView_width_indicator_max, ScreenUtils.dpAdapt(context,48)));
        indicator.setHeight_indicator(typedArray.getDimensionPixelSize(R.styleable.IndicatorTriangleView_height_indicator, ScreenUtils.dpAdapt(context,6)));
        indicator.setColor_indicator(typedArray.getColor(R.styleable.IndicatorTriangleView_color_indicator, 0xffe45540));

        indicator.setWidth_indicator(ScreenUtils.dpAdapt(context,30));
        typedArray.recycle();

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        height=getHeight();
    }

    @Override
    public <T extends View> T getView() {
        return (T) this;
    }

    @Override
    public Indicator getIndicator() {
        return indicator;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        path.reset();
        path.moveTo(indicator.getProgress(), height);// 此点为多边形的起点
        path.lineTo(indicator.getProgress() + indicator.getWidth_indicator() * 1f / 2, height - indicator.getHeight_indicator());
        path.lineTo(indicator.getProgress() + indicator.getWidth_indicator(), height);
        path.close(); // 使这些点构成封闭的多边形
        indicator.getPaint_indicator().setStyle(Paint.Style.FILL);
        canvas.drawPath(path, indicator.getPaint_indicator());
    }
}

实现原理剖析

说真的,这自定义控件还真不简单

在这里插入图片描述

涉及到的难点场景

搞清楚 ViewPager 监听的 onPageSelected、onPageScrolled 和 onPageScrollStateChanged 回调执行特点

   /**
         * This method will be invoked when the current page is scrolled, either as part
         * of a programmatically initiated smooth scroll or a user initiated touch scroll.
         *
         * @param position Position index of the first page currently being displayed.
         *                 Page position+1 will be visible if positionOffset is nonzero.
         * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
         * @param positionOffsetPixels Value in pixels indicating the offset from position.
         */
        public void onPageScrolled(int position, float positionOffset,
                @Px int positionOffsetPixels) {
        }

        /**
         * This method will be invoked when a new page becomes selected. Animation is not
         * necessarily complete.
         *
         * @param position Position index of the new selected page.
         */
        public void onPageSelected(int position) {
        }

        /**
         * Called when the scroll state changes. Useful for discovering when the user begins
         * dragging, when a fake drag is started, when the pager is automatically settling to the
         * current page, or when it is fully stopped/idle. {@code state} can be one of {@link
         * #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
         */
        public void onPageScrollStateChanged(@ScrollState int state) {
        }

首次进入 viewPager,回调如下:

onPageSelected: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0

手指向左拖动,viewapger 从 index 切换到第 index+1,回调如下:

可以发现当手指松开,ViewPager 从SCROLL_STATE_DRAGGING到达SCROLL_STATE_SETTLING(自动滚动状态),onPageSelected先执行,onPageScrolled position 从index 慢慢到index+1

LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_DRAGGING
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_SETTLING
onPageSelected: ----------------------------------->>>>1
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>1
LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_IDLE

手指向右拖动,viewapger 从 index 切换到第 index-1,回调如下:

onPageScrolled: ----------------------------------->>>>1
LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_IDLE
LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_DRAGGING
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_SETTLING
onPageSelected: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
onPageScrolled: ----------------------------------->>>>0
LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_IDLE

可以发现当手指开始拖动,onPageScrolled position 从index 直接变成index-1, 当手指松开,ViewPager 从SCROLL_STATE_DRAGGING到达SCROLL_STATE_SETTLING(自动滚动状态),onPageSelected执行,onPageScrolled position 不再改变,直到 SCROLL_STATE_IDLE(自动滚动停止)

总结:onPageSelected 先执行(粗略来说),手指向左拖动,onPageScrolled position 是当前 item 的position+1,手指向右拖动,onPageScrolled position 是当前 item 的position-1,搞懂这点是关键。

自定义 HorizontalRecyclerView 实现 TabLayout

之所以选择 RecyclerView 做 tabLayout,是因为RecyclerView最适用于多 item 的布局,不仅因为它有缓存的功能、还因为使用起来极其方便简单,个人觉得,android 里recyclerView的设计是超级奶思的。

因为 TabLayout 需要根据 ViewPager 的滑动来滑动,但 RecyclerView 的scrollTo函数是空的,没有任何作用,这样滑动控制就会变得困难。不过我们可以 override,自己实现它,通过scrollBy函数滑动,设置滑动监听事件,记录偏移量offsetX,这样,我们就可以做到 scrollTo 是有作用的。

public class HorizontalRecyclerView extends RecyclerView {
    private LinearItemDecoration linearItemDecoration;
    //永远<=0
    private int offsetX = 0;
    public HorizontalRecyclerView(Context context) {
        this(context, null);
    }

    public HorizontalRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        addOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                offsetX -= dx;
            }

        });
        SimpleItemAnimator simpleItemAnimator = (SimpleItemAnimator) getItemAnimator();
        if (simpleItemAnimator != null) simpleItemAnimator.setSupportsChangeAnimations(false);

    }

    /**
     * x 为正,表示手指往左滑,x 为负,表示手指往右滑
     *
     * @param x
     * @param y
     */
    @Override
    public void scrollBy(int x, int y) {
        super.scrollBy(x, y);
    }

    /**
     * x<=0
     * 比如 x=0,表示滑动到 RecyclerView 最左边,完全显示第一个 item,
     * 比如 x=-100,表示 RecyclerView 左边 100 像素的界面被隐藏
     *
     * @param x
     * @param y
     */
    @Override
    public void scrollTo(int x, int y) {
        scrollBy(offsetX - x, y);
    }

    public int getOffsetX() {
        return offsetX;
    }


    @Override
    public void setAdapter(Adapter adapter) {
        setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
        super.setAdapter(adapter);
    }
}

手指滑动 ViewPager,保证选中的 TabLayout 的 item 在正中间

手指滑动 ViewPager,TabLayout 跟着滑动

手指点击 TabLayout,ViewPager 跟着滑动

手指滑动 TabLayout,再点击 TabLayout

手指滑动 TabLayout,再触摸 ViewPager

手指滑动 TabLayout,再滑动 ViewPager

今日头条存在一个体验不好的场景:快速滑动 TabLayout,ViewPager 在 TabLayout 停止滑动之前就停止了滑动,这时,将看不到 indicator,然而小编的 TabLayoutNiubility 解决了这个问题

源码如下

package com.cy.tablayoutniubility;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;

import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE;

/**
 * @Description:
 * @Author: cy
 * @CreateDate: 2020/7/29 18:42
 * @UpdateUser:
 * @UpdateDate: 2020/7/29 18:42
 * @UpdateRemark:
 * @Version:
 */
public class TabLayoutMediatorVp2<T> {
    private TabLayoutNiubility tabLayout;
    private ViewPager2 viewPager2;
    private TabAdapter<T> tabAdapter;
    private int position_scroll_last = 0;
    private int diff = 0;
    private int diff_click = 0;
    private int toScroll = 0;
    private int offsetX_last = 0;
    private int offsetX_last_click = 0;
    private int offsetX_touch = 0;
    private boolean rvScrolledByVp = false;
    private boolean rvScrolledByTouch = false;
    private boolean scrolledByClick = false;
    private int position_selected_last = 0;
    private boolean op_click_last = false;
    private int click_position_last = -1;

    public TabLayoutMediatorVp2(TabLayoutNiubility tabLayout, final ViewPager2 viewPager2) {
        this.tabLayout = tabLayout;
        this.viewPager2 = viewPager2;
    }

    public TabAdapter<T> setAdapter(final FragmentPageAdapterVp2<T> fragmentPageAdapter) {
        tabAdapter = new TabAdapter<T>() {
            @Override
            public void bindDataToView(TabViewHolder holder, int position, T bean, boolean isSelected) {
                fragmentPageAdapter.bindDataToTab( holder, position, bean, isSelected);
            }

            @Override
            public int getItemLayoutID(int position, T bean) {
                return fragmentPageAdapter.getTabLayoutID(position, bean);
            }

            @Override
            public void onItemClick(TabViewHolder holder, int position, T bean) {
                //点击 tabLayout 的 item,会先回调 onPageSelected,然后回调 onPageScrolled
                //标志复位
                rvScrolledByTouch = false;
                offsetX_touch = 0;
                //标志:tablayout 的滑动是由点击 item 触发的
                scrolledByClick = true;
                position_selected_last = viewPager2.getCurrentItem();
                viewPager2.setCurrentItem(position);
                //让 indicator 立马指向 currentItem
                RecyclerView.ViewHolder viewHolder = tabLayout.getHorizontalRecyclerView().findViewHolderForAdapterPosition(viewPager2.getCurrentItem());
                if (viewHolder != null) {

                    tabLayout.getIndicatorView().getIndicator().setWidth_indicator(tabLayout.getIndicatorView().getIndicator().getWidth_indicator_selected())
                            .setProgress((int) (viewHolder.itemView.getLeft()
                                    + viewHolder.itemView.getWidth() * 1f / 2
                                    - tabLayout.getIndicatorView().getIndicator().getWidth_indicator() / 2));

                } else {
                    //不可见,width_indicator 为 0
                    tabLayout.getIndicatorView().getIndicator().setWidth_indicator(0).invalidate();
                }
                fragmentPageAdapter.onTabClick( holder, position, bean);
            }
        };


        tabLayout.getHorizontalRecyclerView().addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                //如果是手指滑动 tabLayout,需要记录滑动的距离
                if (!rvScrolledByVp) {
                    rvScrolledByTouch = true;
                    offsetX_touch -= dx;
                }
                //indicator 需要跟着滑动
                RecyclerView.ViewHolder viewHolder = tabLayout.getHorizontalRecyclerView().findViewHolderForAdapterPosition(viewPager2.getCurrentItem());
                if (viewHolder != null) {
                    tabLayout.getIndicatorView().getIndicator().setWidth_indicator(tabLayout.getIndicatorView().getIndicator().getWidth_indicator_selected())
                            .setProgress((int) (viewHolder.itemView.getLeft()
                                    + viewHolder.itemView.getWidth() * 1f / 2
                                    - tabLayout.getIndicatorView().getIndicator().getWidth_indicator() / 2));
                } else {
                    //不可见,width_indicator 为 0
                    tabLayout.getIndicatorView().getIndicator().setWidth_indicator(0).invalidate();
                }

            }
        });

        viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                //通知 tabAdapter 更新选中项
                tabAdapter.setPositionSelected(viewPager2.getCurrentItem());
            }

            /**注意:滑动很快的时候,即使到了另外的 page,positionOffsetPixels 不一定会出现 0
             * @param position
             * @param positionOffset
             * @param positionOffsetPixels
             */
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                super.onPageScrolled(position,positionOffset,positionOffsetPixels);
                int centerX = (int) (tabLayout.getWidth() * 1f / 2);
                //说明上次手指滑动了 tabLayout,现在手指滑动 viewpager,需要将 tablayout 复位
                if (rvScrolledByTouch && offsetX_touch != 0) {
                    tabLayout.getHorizontalRecyclerView().stopScroll();
                    //标志不是由手指滑动 tablayout
                    rvScrolledByVp = true;
                    tabLayout.getHorizontalRecyclerView().scrollBy(offsetX_touch, 0);
                    rvScrolledByVp = false;
                    //立刻复位
                    rvScrolledByTouch = false;
                    offsetX_touch = 0;
                    //这里不能修改 position_scroll_last,因为只要上次手指滑动了 tablayout,然后手指滑动 viewapger,onPageScrolled 会被回调多次
                    //在后面去修改 position_scroll_last 即可
//                    position_scroll_last = position;
                    return;
                }
                //点击 item 后,onPageSelected 先回调,然后还会继续回调 onPageScrolled,直到 onPageScrolled=position_selected,从 page index 滑动到 page index+1,
                //position == viewPager2.getCurrentItem() - 1 说明点击的 item 在当前 position 之后
                //position == viewPager2.getCurrentItem()说明点击的 item 在当前 position 之前
                //viewpager 滑动中,才处理,
                if (scrolledByClick) {
                    if ((position == viewPager2.getCurrentItem() - 1 || position == viewPager2.getCurrentItem())) {
                        RecyclerView.ViewHolder viewHolder = tabLayout.getHorizontalRecyclerView().findViewHolderForAdapterPosition(viewPager2.getCurrentItem());
                        if (viewHolder != null) {
                            //indicator 想要指向正中间,计算 TabLayout 需要滑动的距离
                            if (diff_click == 0)
                                diff_click = (int) (viewHolder.itemView.getLeft() + viewHolder.itemView.getWidth() * 1f / 2 - centerX);
                            //获取 tablayout 的偏移量,永远<=0
                            if (offsetX_last_click == 0)
                                offsetX_last_click = tabLayout.getHorizontalRecyclerView().getOffsetX();
                            if (positionOffset != 0) {
                                //scrollBy 调用一次,onScrolled 回调一次
                                //标志不是由手指滑动 tablayout
                                rvScrolledByVp = true;
                                //往右滑
                                if (position_selected_last < viewPager2.getCurrentItem()) {
                                    tabLayout.getHorizontalRecyclerView().scrollTo((int) (offsetX_last_click - (diff_click * positionOffset)), 0);
                                } else {
                                    //往左滑
                                    tabLayout.getHorizontalRecyclerView().scrollTo((int) (offsetX_last_click - (diff_click * (1 - positionOffset))), 0);
                                }
                                rvScrolledByVp = false;
                            }

                        } else {
                            //不可见,width_indicator 为 0
                            tabLayout.getIndicatorView().getIndicator().setWidth_indicator(0).invalidate();
                        }

                    }
                    position_scroll_last = position;
                    return;
                }
                /**
                 * 手指左右滑动 Viewpager,触发下面所有代码
                 */
                TabViewHolder viewHolder = (TabViewHolder) tabLayout.getHorizontalRecyclerView().findViewHolderForAdapterPosition(position);
                if (viewHolder != null) {
                    int width_half = (int) (viewHolder.itemView.getWidth() * 1f / 2);
                    int left = viewHolder.itemView.getLeft();
                    int space = tabLayout.getHorizontalRecyclerView().getItemDecoration().getSpace_horizontal();
                    TabViewHolder viewHolder_behind = (TabViewHolder) tabLayout.getHorizontalRecyclerView().findViewHolderForAdapterPosition(position + 1);
                    if (position == 0) {
                        //TabLayout 刚显示,indicator 会指向第 0 个 item
                        diff = 0;
                        offsetX_last = 0;
                        if (viewHolder_behind != null)
                            //计算 indicator 指向下一个 item 需要滑动的距离
                            toScroll = (int) (width_half
                                    + tabLayout.getHorizontalRecyclerView().getItemDecoration().getSpace_horizontal()
                                    + viewHolder_behind.itemView.getWidth() * 1f / 2);
                    } else if (position_scroll_last < position) {
                        //说明从 page index 滑动到了 page index+1,
                        if (viewHolder_behind != null) {
                            //indicator 想要指向正中间,计算 TabLayout 需要滑动的距离
                            diff = (int) (viewHolder_behind.itemView.getLeft() + viewHolder_behind.itemView.getWidth() * 1f / 2 - centerX);
                            //下一个 item 都在正中间的前面,无需滑动,而且可以避免出现负数导致 recyclerView 抖动
                            if (diff < 0) diff = 0;
                            //获取上次 tablayout 的偏移量,永远<=0
                            offsetX_last = tabLayout.getHorizontalRecyclerView().getOffsetX();
                            //计算 indicator 指向下一个 item 需要滑动的距离
                            toScroll = (int) (width_half
                                    + tabLayout.getHorizontalRecyclerView().getItemDecoration().getSpace_horizontal()
                                    + viewHolder_behind.itemView.getWidth() * 1f / 2);
                        }

                    } else if (position_scroll_last > position) {
                        //说明从 page index 滑动到了 page index-1
                        //indicator 想要指向正中间,计算 TabLayout 需要滑动的距离
                        diff = (int) (left + width_half - centerX);
                        //position 的 item 在正中间的后面,无需滑动,而且可以避免出现正数导致 recyclerView 抖动
                        if (diff > 0) diff = 0;
                        //获取上次 tablayout 的偏移量,永远<=0
                        offsetX_last = tabLayout.getHorizontalRecyclerView().getOffsetX();
                        if (viewHolder_behind != null)
                            //计算 indicator 指向 position 的 item 需要滑动的距离
                            toScroll = (int) (width_half
                                    + tabLayout.getHorizontalRecyclerView().getItemDecoration().getSpace_horizontal()
                                    + viewHolder_behind.itemView.getWidth() * 1f / 2);
                    } else if (op_click_last) {
                        //如果 position_scroll_last==position,并且上次操作是点击 item,
                        if (position == click_position_last) {
                            //说明现在是正要从 page index 滑动到 page index+1
                            if (viewHolder_behind != null) {
                                //indicator 想要指向正中间,计算 TabLayout 需要滑动的距离
                                diff = (int) (viewHolder_behind.itemView.getLeft() + viewHolder_behind.itemView.getWidth() * 1f / 2 - centerX);
                                //获取上次 tablayout 的偏移量,永远<=0
                                offsetX_last = tabLayout.getHorizontalRecyclerView().getOffsetX();
                                //计算 indicator 指向 position 的 item 需要滑动的距离
                                toScroll = (int) (width_half
                                        + tabLayout.getHorizontalRecyclerView().getItemDecoration().getSpace_horizontal()
                                        + viewHolder_behind.itemView.getWidth() * 1f / 2);
                            }
                        }
                        op_click_last = false;
                    }
                    //diff==0,无需滑动,positionOffset==0,无需滑动,当前 position 和上次滑动的 position 相等,才执行滑动操作
                    if (diff != 0 && positionOffset != 0 && position_scroll_last == position) {
                        //标志,tabLayout 滑动,不是因为手指滑动 tablayout 导致的
                        rvScrolledByVp = true;
                        if (diff > 0) {
                            //scrollBy 调用一次,onScrolled 回调一次
                            //手指往左滑动,positionOffset 由小变大
                            tabLayout.getHorizontalRecyclerView().scrollTo((int) (offsetX_last - (diff * positionOffset)), 0);
                        } else {
                            //手指往右滑动,positionOffset 由大变小
                            tabLayout.getHorizontalRecyclerView().scrollTo((int) (offsetX_last - (diff * (1 - positionOffset))), 0);
                        }
                        //标志复位
                        rvScrolledByVp = false;
                    }
                    //计算 Width_indicator,Width_indicator 由小变大再变小,2 个 item 中间时最大
                    tabLayout.getIndicatorView().getIndicator().setWidth_indicator(Math.max(tabLayout.getIndicatorView().getIndicator().getWidth_indicator_selected(),
                            (int) (tabLayout.getIndicatorView().getIndicator().getWidth_indicator_selected() +
                                    (positionOffset == 0 ? 0 : tabLayout.getIndicatorView().getIndicator().getWidth_indicator_max() * (0.5 - Math.abs(0.5 - positionOffset))))))
                            .setProgress((int) (left
                                    + width_half
                                    - tabLayout.getIndicatorView().getIndicator().getWidth_indicator() / 2
                                    + (toScroll * positionOffset)));

                    if (toScroll != 0)
                        //手指往左滑动,positionOffset 由小变大
                        //手指往右滑动,positionOffset 由大变小
                        if (viewHolder_behind != null)
                            fragmentPageAdapter.onTabScrolled(viewHolder, position, false, 1 - positionOffset,
                                    viewHolder_behind, position + 1, true, positionOffset);

                } else {
                    //viewpager 嵌套 viewpager 的时候,内层 viewpager 向右滑动了以后又向左滑动,会导致 tablayout
                    //position 对应的 item 不可见,所以要滑动到对应的 position
                    tabLayout.getHorizontalRecyclerView().scrollToPosition(position);
                    viewHolder = (TabViewHolder) tabLayout.getHorizontalRecyclerView().findViewHolderForAdapterPosition(position);
                    //scrollToPosition 一调用,不会立马滑动完毕,所以还会有存在 null 的时候,
                    if(viewHolder!=null){
                        int width_half = (int) (viewHolder.itemView.getWidth() * 1f / 2);
                        int left = viewHolder.itemView.getLeft();
                        tabLayout.getIndicatorView().getIndicator().setWidth_indicator(tabLayout.getIndicatorView().getIndicator().getWidth_indicator_selected())
                                .setProgress((int) (left
                                        + width_half
                                        - tabLayout.getIndicatorView().getIndicator().getWidth_indicator() / 2));
                    }else {
                        tabLayout.getIndicatorView().getIndicator().setWidth_indicator(0).invalidate();
                    }

                }

                position_scroll_last = position;
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                switch (state) {
                    case SCROLL_STATE_IDLE:
                        //记录上次操作的是点击 item
                        if (scrolledByClick) {
                            click_position_last = viewPager2.getCurrentItem();
                            op_click_last = true;
                        }
                        //标志复位
                        scrolledByClick = false;
                        diff_click = 0;
                        offsetX_last_click = 0;
                        break;
                }
            }
        });

        tabLayout.setAdapter(tabAdapter);

        viewPager2.setAdapter(fragmentPageAdapter);

        return tabAdapter;
    }

}

处理过程超鸡儿复杂,千万别想着能看懂,要是能看懂,只能说明你是万中无一的绝世高手,能知道大概干了些什么就可以了。

TabLayout 的 item 宽度均分

用 LinearLayout 做 tablayout,每个 item 的weight设置为 1

        removeAllViews();
        for (int i = 0; i < tabNoScrollAdapter.getItemCount(); i++) {
            TabNoScrollViewHolder tabNoScrollViewHolder = tabNoScrollAdapter.onCreateViewHolder(i, tabNoScrollAdapter.getList_bean().get(i), this);
            tabNoScrollViewHolder.setPositionAdapter(i);
            sparseArrayViewHolder.put(i, tabNoScrollViewHolder);
            addView(tabNoScrollViewHolder.itemView, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1));
            ItemChanged(i);
        }

RecyclerView 的 item 刷新如何做到不闪烁

禁用默认的刷新动画

 SimpleItemAnimator simpleItemAnimator = (SimpleItemAnimator) getItemAnimator();
 if (simpleItemAnimator != null) simpleItemAnimator.setSupportsChangeAnimations(false);

UML 类图如下

在这里插入图片描述 为了更清晰易懂,小编画得不正规,比较随意,该 UML 是老的,不是最新版本,最新版本,名字 有改动。

面向接口编程(面向多态编程)的思想

小编特别喜欢 JAVA 这门语言,小编个人认为 JAVA 将面向对象编程的思想展现的淋漓尽致。

整个轮子用得最多的编程思想就是多态,多态是设计模式和框架的基础。

接口泛型是实现多态的 2 把利器。

编程思想暂且稍微透露,后面小编会专门出一个 SDK 开发入门教程,详细讲述设计模式和多态,敬请关注。

Github:https://github.com/AnJiaoDe

CSDN:https://blog.csdn.net/confusing_awakening

OpenCV 入门教程:https://blog.csdn.net/confusing_awakening/article/details/113372425

ffmpeg 入门教程:https://blog.csdn.net/confusing_awakening/article/details/102007792

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools