1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > Android自定义控件之RecyclerView打造万能ViewPager TabLayout(仿今日头条Tab滑动 T

Android自定义控件之RecyclerView打造万能ViewPager TabLayout(仿今日头条Tab滑动 T

时间:2019-05-23 05:56:43

相关推荐

Android自定义控件之RecyclerView打造万能ViewPager TabLayout(仿今日头条Tab滑动 T

文章目录

GitHub:/AnJiaoDe/TabLayoutNiubility该轮子特异功能如下:使用方法注意:该轮子适用于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千古BUG:Activity销毁重启,Fragment恢复问题AndroidX ViewPager中的FragmentStatePagerAdapter存在的问题AndroidX ViewPager2中的FragmentStateAdapter存在的问题自定义fragment自定义Fragment PageContainer 双层嵌套(ViewPager和ViewPager2均适用)相关APITabMediatorFragmentPageAdapterTabAdapterTabLayoutScroll、TabLayoutNoScroll、TabLayoutMulti、IndicatorLineView 、 IndicatorTriangleViewTabLayoutScroll和 indicator style设置自定义indicator实现原理剖析说真的,这自定义控件还真不简单涉及到的难点场景搞清楚ViewPager监听的onPageSelected、onPageScrolled和onPageScrollStateChanged回调执行特点自定义HorizontalRecyclerView实现TabLayout源码如下TabLayout的item宽度均分RecyclerView的item刷新如何做到不闪烁UML类图如下面向接口编程(面向多态编程)的思想欢迎联系、指正、批评

GitHub:/AnJiaoDe/TabLayoutNiubility

该轮子特异功能如下:

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

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

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

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

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

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

仿微信主页Tab

自定义fragment

使用方法

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

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

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

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

12345

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

dependencies {api 'com.github.AnJiaoDe:TabLayoutNiubility:V1.2.6'api 'androidx.recyclerview:recyclerview:1.1.0'//版本必须>=1.1.0}

1234

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

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

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

1

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

详细使用如下

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

activity布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.cy.tablayoutniubility.TabLayoutNoScrollandroid:id="@+id/tablayout"android:layout_width="match_parent"android:layout_height="40dp"app:space_horizontal="0dp"android:background="#fff"><com.cy.tablayoutniubility.IndicatorLineViewandroid: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.ViewPager2android:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="match_parent"android:overScrollMode="never" /></LinearLayout>

12345678910111213141516171819222324252627

tab_item布局:

<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="/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>

123456789

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) {@Overridepublic Fragment createFragment(String bean, int position) {return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));}@Overridepublic 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);}@Overridepublic 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);

1234567891011121314151617181922232425262728293031323334353637

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

多布局:

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

1234567

activity布局:

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

12345678910111213141516171819222324

tab item布局:

<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="/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>

12345678910

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) {@Overridepublic Fragment createFragment(String bean, int position) {return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));}@Overridepublic 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);}@Overridepublic 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);

1234567891011121314151617181922232425262728293031323334353637383940414243

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

使用TabLayoutMulti

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.cy.tablayoutniubility.TabLayoutMultiandroid:id="@+id/tablayout"android:layout_width="match_parent"android:layout_height="40dp"app:space_horizontal="0dp"android:background="#fff"><com.cy.tablayoutniubility.IndicatorLineViewandroid: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.ViewPager2android:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="match_parent"android:overScrollMode="never" /></LinearLayout>

123456789101112131415161718192223242526272829

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) {@Overridepublic Fragment createFragment(String bean, int position) {return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));}@Overridepublic 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);}@Overridepublic int getTabLayoutID(int position, String bean) {return R.layout.item_tab_center;}};}else {fragmentPageAdapter = new FragPageAdapterVp2NoScroll<String>(this) {@Overridepublic Fragment createFragment(String bean, int position) {return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));}@Overridepublic 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);}@Overridepublic 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);// }

1234567891011121314151617181922232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182

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

activity代码:

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

123456789101112131415161718192223242526

tab item布局:

<?xml version="1.0" encoding="utf-8"?><com.cy.tablayoutniubility.TabGradientTextView xmlns:android="/apk/res/android"xmlns:app="/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>

123456789101112

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) {@Overridepublic Fragment createFragment(String bean, int position) {return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));}@Overridepublic 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);}@Overridepublic int getTabLayoutID(int position, String bean) {return R.layout.item_tab_gradient;}@Overridepublic 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);

1234567891011121314151617181922232425262728293031323334353637383940414243444546474849505152535455565758596061

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

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

activity代码:

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

12345678910111213141516171819222324252627

tab item布局:

<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="/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>

12345678910

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) {@Overridepublic Fragment createFragment(String bean, int position) {return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));}@Overridepublic 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);}@Overridepublic 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);

12345678910111213141516171819222324252627282930313233343536373839

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

activity布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.cy.tablayoutniubility.TabLayoutScrollandroid:id="@+id/tablayout"android:layout_width="match_parent"android:layout_height="40dp"android:background="#fff"><com.cy.tablayoutniubility.IndicatorLineViewandroid:layout_width="match_parent"android:layout_height="wrap_content" /></com.cy.tablayoutniubility.TabLayoutScroll><androidx.viewpager.widget.ViewPagerandroid:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="match_parent"android:overScrollMode="never" /></LinearLayout>

123456789101112131415161718192223242526

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) {@Overridepublic Fragment createFragment(String bean, int position) {return FragmentTab1.newInstance(FragmentTab1.TAB_NAME1, getList_bean().get(position));}@Overridepublic 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);}@Overridepublic 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);

1234567891011121314151617181922232425262728293031323334353637383940414243

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) {@Overridepublic Fragment createFragment(String bean, int position) {return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position));}@Overridepublic 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);}@Overridepublic 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);}

123456789101112131415161718192223242526272829303132333435363738394041424344454647484950

仿微信主页Tab

activity布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><androidx.viewpager2.widget.ViewPager2android:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:overScrollMode="never" /><com.cy.tablayoutniubility.TabLayoutNoScrollandroid:id="@+id/tablayout"android:layout_width="match_parent"android:layout_height="60dp"android:background="#fff"><com.cy.tablayoutniubility.IndicatorNullViewandroid:layout_width="match_parent"android:layout_height="wrap_content" /></com.cy.tablayoutniubility.TabLayoutNoScroll></LinearLayout>

1234567891011121314151617181922232425262728

item布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"android:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><ImageViewandroid:id="@+id/iv"android:layout_width="match_parent"android:layout_height="wrap_content"android:scaleType="centerInside" /><TextViewandroid: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>

12345678910111213141516171819222324

JAVA代码:

ViewPager2 viewPager2= findViewById(R.id.view_pager);TabLayoutNoScroll tabLayoutNoScroll= findViewById(R.id.tablayout);FragPageAdapterVp2NoScroll<TabBean> fragmentPageAdapter = new FragPageAdapterVp2NoScroll<TabBean>(this) {@Overridepublic Fragment createFragment(TabBean bean, int position) {return FragmentTab2.newInstance(FragmentTab2.TAB_NAME2, getList_bean().get(position).getText());}@Overridepublic 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());}@Overridepublic 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);

1234567891011121314151617181922232425262728293031323334353637383940

千古BUG:Activity销毁重启,Fragment恢复问题

当用户修改了手机字体大小,语言等,Activity会被销毁并重启,其中的Fragment会在Activity的super.onCreate(savedInstanceState);中恢复,这样就出了个严重的BUG:Activity被回收,Activity持有的所有数据和对象均被回收,假如Fragment持有了Activity的某些数据或者对象,在Acitivty尚未来得及初始化这些数据和对象的时候,去恢复Fragment,这时候必然报空指针异常,这种现象尤其是在ViewPager和Fragment双层嵌套使用时尤为造孽。

虽然有很直接的办法:就是判断NULL,但这样写代码未免极其不爽,而且还不保证绝对不出问题。

也有人用这种办法:

onSaveInstanceState中做手脚,然而小编试了,并不灵,而且小编不推荐这样做,因为这样做,代码肯定不健壮。

FragmentActivity重启时Fragment状态异常的问题解决办法

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);

1234

AndroidX ViewPager中的FragmentStatePagerAdapter存在的问题

@Overridepublic Object instantiateItem(@NonNull ViewGroup container, int position) {// If we already have this item instantiated, there is nothing// to do. This can happen when we are restoring the entire pager// from its saved state, where the fragment manager has already// taken care of restoring the fragments we previously had instantiated.if (mFragments.size() > position) {Fragment f = mFragments.get(position);if (f != null) {return f;}}if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}Fragment fragment = getItem(position);if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);if (mSavedState.size() > position) {Fragment.SavedState fss = mSavedState.get(position);if (fss != null) {fragment.setInitialSavedState(fss);}}while (mFragments.size() <= position) {mFragments.add(null);}fragment.setMenuVisibility(false);if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {fragment.setUserVisibleHint(false);}mFragments.set(position, fragment);mCurTransaction.add(container.getId(), fragment);if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);}return fragment;}

12345678910111213141516171819222324252627282930313233343536373839404142

看到一处关键代码:

if (mFragments.size() > position) {Fragment f = mFragments.get(position);if (f != null) {return f;}}

1234567

Fragment被缓存起来了,虽说节约内存,但是也容易出现身体不适,因为可能开发过程中,重新设置了adapter或者多种原因,这时,我们不希望缓存fragment了。

所以小编开发的轮子中,把这段使用缓存的代码删除了。

AndroidX ViewPager2中的FragmentStateAdapter存在的问题

private void ensureFragment(int position) {long itemId = getItemId(position);// if (!mFragments.containsKey(itemId)) {// TODO(133419201): check if a Fragment provided here is a new FragmentFragment newFragment = createFragment(position);newFragment.setInitialSavedState(mSavedStates.get(itemId));mFragments.put(itemId, newFragment);// }}

123456789

可以看到和Androidx中的ViewPagerFragmentStatePagerAdapter一样缓存了Fragment,

小编在轮子中也把这段使用缓存的代码删除了。

自定义fragment

小编有超强的代码洁癖,所以不喜欢用一堆代码去解决API的某些身体不适,干脆自己自定义fragment

做了基本的生命周期控制,但是生命周期肯定不如Fragment的强大,不过依然能满足基本需求,特别适用于ViewPgaerFragmnt双层嵌套使用的时候。

public abstract class PageContainer {protected View view;protected Context context;private PageContainerChildManager pageContainerChildManager=new PageContainerChildManager();private PageContainer pageContainerParent;public PageContainer(PageContainer pageContainerParent) {this.pageContainerParent = pageContainerParent;}public Context getContext() {return context;}public View getView() {return view;}public abstract View onCreateView(LayoutInflater layoutInflater, ViewGroup container);public void onResume(boolean isFirstResume){}public void onStop(){}public void onDestroyView(){}public final PageContainerChildManager getPageContainerChildManager() {return pageContainerChildManager;}public final PageContainer getPageContainerParent() {return pageContainerParent;}}

1234567891011121314151617181922232425262728293031323334

自定义Fragment PageContainer 双层嵌套(ViewPager和ViewPager2均适用)

Activity代码:

ViewPager viewPager = findViewById(R.id.view_pager);TabLayoutScroll tabLayoutLine = findViewById(R.id.tablayout);// tabLayoutLine.setSpace_horizontal(dpAdapt(20)).setSpace_vertical(dpAdapt(8));ContainerPageAdapterVp<String> containerPageAdapterVp = new ContainerPageAdapterVp<String>(viewPager) {@Overridepublic PageContainer onCreatePageContainer(ViewGroup container, int position, String bean) {LogUtils.log("onCreatePageContainer", position);//该PageContainer属于第一层ViewPager,它的父PageContainer传null即可return new PageContainerTab1(null, bean);}@Overridepublic void bindDataToTab(TabViewHolder holder, int position, String bean, boolean isSelected) {LogUtils.log("createFragmentbindDataToTab", position);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);}@Overridepublic 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(containerPageAdapterVp);List<String> list = new ArrayList<>();list.add("关注");list.add("推荐");list.add("视频");list.add("漫画");containerPageAdapterVp.add(list);tabAdapter.add(list);

1234567891011121314151617181922232425262728293031323334353637383940414243

PageContainerTab1 代码:

public class PageContainerTab1 extends PageContainer {private String bean;public PageContainerTab1(PageContainer pageContainerParent, String bean) {super(pageContainerParent);this.bean = bean;}@Overridepublic View onCreateView(LayoutInflater layoutInflater, ViewGroup container) {view = (ViewGroup) layoutInflater.inflate(R.layout.fragment_tab1, container, false);LogUtils.log("onCreateView");ViewPager viewPager = view.findViewById(R.id.view_pager);TabLayoutScroll tabLayoutLine = view.findViewById(R.id.tablayout);ContainerPageAdapterVp<String> containerPageAdapterVp = new ContainerPageAdapterVp<String>(viewPager) {@Overridepublic PageContainer onCreatePageContainer(ViewGroup container, int position, String bean) {LogUtils.log("onCreatePageContainer", position);//该PageContainer属于第2层ViewPager,它的父PageContainer传PageContainerTab1.this即可return new PageContainerTab2(PageContainerTab1.this, bean);}@Overridepublic 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);}@Overridepublic int getTabLayoutID(int position, String bean) {return R.layout.item_tab;}};TabAdapter<String> tabAdapter = new TabMediatorVp<String>(tabLayoutLine, viewPager).setAdapter(containerPageAdapterVp);List<String> list = new ArrayList<>();if (bean.equals("关注")) {list.add(bean + "0");list.add(bean + "1");} else if (bean.equals("推荐")) {list.add(bean + "0");} else {list.add(bean + "0");list.add(bean + "1");list.add(bean + "2");list.add(bean + "3");list.add(bean + "4");list.add(bean + "5");list.add(bean + "6");list.add(bean + "7");list.add(bean + "8");list.add(bean + "9");list.add(bean + "10");list.add(bean + "11");list.add(bean + "12");list.add(bean + "13");}containerPageAdapterVp.add(list);tabAdapter.add(list);return view;}@Overridepublic void onResume(boolean isFirstResume) {super.onResume(isFirstResume);LogUtils.log("onResumetab1", bean + isFirstResume);}@Overridepublic void onStop() {super.onStop();LogUtils.log("onStop", bean);}@Overridepublic void onDestroyView() {super.onDestroyView();LogUtils.log("onDestroyView", bean);}}

123456789101112131415161718192223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889

PageContainerTab2 代码:

public class PageContainerTab2 extends PageContainer {private View view;private String bean;public PageContainerTab2(PageContainer pageContainerParent,String bean) {super(pageContainerParent);this.bean = bean;}@Overridepublic View onCreateView(LayoutInflater layoutInflater, ViewGroup container) {view=layoutInflater.inflate(R.layout.fragment_tab2, container, false);LogUtils.log("onCreateView",bean);TextView textView = view.findViewById(R.id.tv);textView.setText(bean);return view;}@Overridepublic void onResume(boolean isFirstResume) {super.onResume(isFirstResume);LogUtils.log("onResumetab2",bean+isFirstResume);}@Overridepublic void onStop() {super.onStop();LogUtils.log("onStop",bean);}@Overridepublic void onDestroyView() {super.onDestroyView();LogUtils.log("onDestroyViewPageContainerTab2",bean);}}

12345678910111213141516171819222324252627282930313233343536373839

相关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.TabLayoutNiubilityandroid:id="@+id/tablayout"android:layout_width="match_parent"android:layout_height="40dp"android:background="#fff"><com.cy.tablayoutniubility.IndicatorLineViewandroid:layout_width="match_parent"android:layout_height="wrap_content" /></com.cy.tablayoutniubility.TabLayoutNiubility>

12345678910

TabLayoutScroll和 indicator style设置

TabLayoutNiubility可设置space

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

可在布局中使用

比如:

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

12345

<?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>

123456789101112131415161718192223242526272829

可在代码中使用

比如:

tabLayoutScroll.getIndicatorView().getIndicator().setColor_indicator();

1

/*** 设置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;}

12345678910111213141516171819222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384

自定义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();}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);height=getHeight();}@Overridepublic <T extends View> T getView() {return (T) this;}@Overridepublic Indicator getIndicator() {return indicator;}@Overrideprotected 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());}}

1234567891011121314151617181922232425262728293031323334353637383940414243444546474849505152

实现原理剖析

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

涉及到的难点场景

搞清楚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) {}

12345678910111213141516171819222324252627282930

首次进入viewPager,回调如下:

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

12

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

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

LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_DRAGGINGonPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_SETTLINGonPageSelected: ----------------------------------->>>>1onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>1LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_IDLE

12345678910111213141516171819

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

onPageScrolled: ----------------------------------->>>>1LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_IDLELOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_DRAGGINGonPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_SETTLINGonPageSelected: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0onPageScrolled: ----------------------------------->>>>0LOG_E: ----------------------------------->>>>onPageScrollStateChangedSCROLL_STATE_IDLE

1234567891011121314151617181922232425

可以发现当手指开始拖动,onPageScrolledposition从index直接变成index-1,

当手指松开,ViewPager从SCROLL_STATE_DRAGGING到达SCROLL_STATE_SETTLING(自动滚动状态),onPageSelected执行,onPageScrolledposition不再改变,直到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;//永远<=0private int offsetX = 0;public HorizontalRecyclerView(Context context) {this(context, null);}public HorizontalRecyclerView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);addOnScrollListener(new OnScrollListener() {@Overridepublic 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*/@Overridepublic void scrollBy(int x, int y) {super.scrollBy(x, y);}/*** x<=0* 比如 x=0,表示滑动到RecyclerView最左边,完全显示第一个item,* 比如 x=-100,表示RecyclerView左边100像素的界面被隐藏** @param x* @param y*/@Overridepublic void scrollTo(int x, int y) {scrollBy(offsetX - x, y);}public int getOffsetX() {return offsetX;}@Overridepublic void setAdapter(Adapter adapter) {setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));super.setAdapter(adapter);}}

123456789101112131415161718192223242526272829303132333435363738394041424344454647484950515253545556575859

手指滑动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: /7/29 18:42* @UpdateUser:* @UpdateDate: /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>() {@Overridepublic void bindDataToView(TabViewHolder holder, int position, T bean, boolean isSelected) {fragmentPageAdapter.bindDataToTab( holder, position, bean, isSelected);}@Overridepublic int getItemLayoutID(int position, T bean) {return fragmentPageAdapter.getTabLayoutID(position, bean);}@Overridepublic 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立马指向currentItemRecyclerView.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为0tabLayout.getIndicatorView().getIndicator().setWidth_indicator(0).invalidate();}fragmentPageAdapter.onTabClick( holder, position, bean);}};tabLayout.getHorizontalRecyclerView().addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic 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为0tabLayout.getIndicatorView().getIndicator().setWidth_indicator(0).invalidate();}}});viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {@Overridepublic void onPageSelected(int position) {super.onPageSelected(position);//通知tabAdapter更新选中项tabAdapter.setPositionSelected(viewPager2.getCurrentItem());}/**注意:滑动很快的时候,即使到了另外的page,positionOffsetPixels不一定会出现0* @param position* @param positionOffset* @param positionOffsetPixels*/@Overridepublic 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();//标志不是由手指滑动tablayoutrvScrolledByVp = 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的偏移量,永远<=0if (offsetX_last_click == 0)offsetX_last_click = tabLayout.getHorizontalRecyclerView().getOffsetX();if (positionOffset != 0) {//scrollBy调用一次,onScrolled回调一次//标志不是由手指滑动tablayoutrvScrolledByVp = 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为0tabLayout.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个itemdiff = 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的偏移量,永远<=0offsetX_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的偏移量,永远<=0offsetX_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+1if (viewHolder_behind != null) {//indicator想要指向正中间,计算TabLayout需要滑动的距离diff = (int) (viewHolder_behind.itemView.getLeft() + viewHolder_behind.itemView.getWidth() * 1f / 2 - centerX);//获取上次tablayout的偏移量,永远<=0offsetX_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不可见,所以要滑动到对应的positiontabLayout.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;}@Overridepublic void onPageScrollStateChanged(int state) {switch (state) {case SCROLL_STATE_IDLE://记录上次操作的是点击itemif (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;}}

12345678910111213141516171819222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119111221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992000220320420520620720820921021121221321421521621721821921222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316

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

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);}

12345678

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

禁用默认的刷新动画

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

12

UML类图如下

为了更清晰易懂,小编画得不正规,比较随意,该UML是老的,不是最新版本,最新版本,名字 有改动。

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

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

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

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

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

欢迎联系、指正、批评

Github:/AnJiaoDe

简书:/u/b8159d455c69

CSDN:/confusing_awakening

ffmpeg入门教程:/p/042c7847bd8a

Android自定义控件之RecyclerView打造万能ViewPager TabLayout(仿今日头条Tab滑动 Tab多布局 indicator蠕动 自定义indicator 文字颜色渐变)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。