首先需要在 build.gradle 中添加依赖才可以:
compile 'com.android.support:design:25.3.1'然后要让 Activity 继承自 AppCompatActivity,如果因某些原因无法这样做的话可以指定当前 Activity 的 Theme:
android:theme="@style/Theme.AppCompat"首先介绍他的几个属性:
| 名称 | 介绍 |
|---|---|
| tabIndicatorColor | 指示器的颜色 |
| tabIndicatorHeight | 指示器的高度 |
| tabMode | 模式 |
| tabSelectedTextColor | 选中的字体颜色 |
| tabTextColor | 未选中的字体颜色 |
tabMode属性的取值有两个:scrollable 和 fixed
- scrollable:TabLayout认为TabItem总数宽度大于屏幕宽度,会自动成为水平滚动模式
- fixed: TabLayout会按TabItem的个数将屏幕平均分割宽度
1.把指示条高度设为0:
app:tabIndicatorHeight="0dp"2.把指示条的颜色设为透明
app:tabIndicatorColor="@color/transparent" TabLayout 中有个属性 tabTextAppearance,这里可以指定一个 style,所以我们定义一个 style:
<stylename="TabLayoutTextStyle"> <itemname="android:textSize">16sp</item> </style>然后在 TabLayout 中这样设置就可以达到修改字体大小的效果了:
app:tabTextAppearance="@style/TabLayoutTextStyle"默认选择第一个
tabLayout.getTabAt(0).select();<?xml version="1.0" encoding="utf-8"?> <LinearLayoutxmlns: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"> <android.support.design.widget.TabLayout android:id="@+id/tab_layout"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="#FFFFFF"android:fillViewport="false"app:layout_scrollFlags="scroll"app:tabIndicatorColor="@color/colorAccent"app:tabIndicatorHeight="2dp"app:tabMode="fixed"app:tabSelectedTextColor="@color/colorAccent"app:tabTextColor="#000000"> <android.support.design.widget.TabItem android:layout_width="match_parent"android:layout_height="match_parent"android:text="标题一"/> <android.support.design.widget.TabItem android:layout_width="match_parent"android:layout_height="match_parent"android:text="标题二"/> <android.support.design.widget.TabItem android:layout_width="match_parent"android:layout_height="match_parent"android:text="标题三"/> </android.support.design.widget.TabLayout> <android.support.v4.view.ViewPager android:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="match_parent"/> </LinearLayout>publicclassMainActivityextendsAppCompatActivity{TabLayouttabLayout; ViewPagerviewPager; @OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init()} privatevoidinit(){tabLayout = (TabLayout) findViewById(R.id.tab_layout); viewPager = (ViewPager) findViewById(R.id.view_pager); List<Fragment> list = newArrayList<>(); list.add(newMyFragment("标题一"));// 就是一个普通的 Fragment,里面有一个 TextView 显示第几个list.add(newMyFragment("标题二")); list.add(newMyFragment("标题三")); //正常设置 AdapterviewPager.setAdapter(newViewPagerAdapter(getSupportFragmentManager(), list)); tabLayout.setupWithViewPager(viewPager);// 官方推荐for (inti = 0; i < list.size(); i++){// 这里的坑稍后解释tabLayout.getTabAt(i).setText(((MyFragment)list.get(i)).getTitle())} } }有时候我们不知道 TabItem 的个数,而是从服务器等其他途径获得,这个时候我们就需要动态来添加了,xml 的代码比较简单:
<?xml version="1.0" encoding="utf-8"?> <LinearLayoutxmlns: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"> <android.support.design.widget.TabLayout android:id="@+id/tab_layout"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="#FFFFFF"android:fillViewport="false"app:layout_scrollFlags="scroll"app:tabIndicatorColor="@color/colorAccent"app:tabIndicatorHeight="2dp"app:tabMode="scrollable"app:tabSelectedTextColor="@color/colorAccent"app:tabTextAppearance="@style/TabLayoutTextStyle"app:tabTextColor="#000000"/> <android.support.v4.view.ViewPager android:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="match_parent"/> </LinearLayout>publicclassMainActivityextendsAppCompatActivity{TabLayouttabLayout; ViewPagerviewPager; @OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init()} privatevoidinit(){tabLayout = (TabLayout) findViewById(R.id.tab_layout); viewPager = (ViewPager) findViewById(R.id.view_pager); List<MyFragment> list = newArrayList<>();// 装载的viewpager的数据List<String> tabList = getTab(); for (inti = 0; i < tabList.size(); i++){tabLayout.addTab(tabLayout.newTab().setText(tabList.get(i)));// 给 tabLayout 添加 Tablist.add(newMyFragment(tabList.get(i)))} viewPager.setAdapter(newViewPagerAdapter(getSupportFragmentManager(), list)); tabLayout.setupWithViewPager(viewPager)} // 假如这是从服务器获取的数据privateList<String> getTab(){List<String> list = newArrayList<>(); list.add("新闻"); list.add("体育"); list.add("科技"); list.add("直播"); list.add("汽车"); list.add("公益"); list.add("娱乐"); list.add("财经"); list.add("时尚"); list.add("房产"); list.add("旅游"); list.add("艺术"); returnlist} }然后我们看看实际效果
标题呢?我的标题呢?这里就有一个坑了,看看 setupWithViewPager() 的源码怎么回事?
privatevoidsetupWithViewPager(@NullablefinalViewPagerviewPager, booleanautoRefresh, booleanimplicitSetup){// 省略代码 ...if (viewPager != null){// 省略代码 ...if (adapter != null){// Now we'll populate ourselves from the pager adapter, adding an observer if// autoRefresh is enabledsetPagerAdapter(adapter, autoRefresh)} // 省略代码 ... } else{// 省略代码 ... } mSetupViewPagerImplicitly = implicitSetup}然后我们看到了这么一段:
finalPagerAdapteradapter = viewPager.getAdapter(); if (adapter != null){// Now we'll populate ourselves from the pager adapter, adding an observer if// autoRefresh is enabledsetPagerAdapter(adapter, autoRefresh)}继续看 setPagerAdapter 方法里面调用了 populateFromPagerAdapter():
voidpopulateFromPagerAdapter(){removeAllTabs(); if (mPagerAdapter != null){finalintadapterCount = mPagerAdapter.getCount(); for (inti = 0; i < adapterCount; i++){addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false)} // Make sure we reflect the currently set ViewPager itemif (mViewPager != null && adapterCount > 0){finalintcurItem = mViewPager.getCurrentItem(); if (curItem != getSelectedTabPosition() && curItem < getTabCount()){selectTab(getTabAt(curItem))} } } }方法里的第一行!瞬间脑海中一万只草泥马奔腾而过,why? why? why? 好吧你赢了,看到这里我们应该想到了2种解决办法:
1.既然我绑定后你全给我 remove掉了,那就先不绑定了,你丑你先 remove 我后绑定还不行吗?
2.在它 remove 完之后我们看到它重新添加Tab的时候是通过adapter中的getPageTitle()方法来做的:
addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false);因此,我们重写一下viewpager的adapter中的getPageTitle()方法即可,也推荐这种方法
首先我们定义一个 ViewPagerBean 来存放 Fragment 和 title:
publicclassViewPagerBeanimplementsSerializable{publicMyFragmentfragment; publicStringtitle; publicViewPagerBean(MyFragmentfragment, Stringtitle){this.fragment = fragment; this.title = title} }然后修改一下 Adapter :
publicclassViewPagerAdapterextendsFragmentPagerAdapter{List<ViewPagerBean> list = newArrayList<>(); publicViewPagerAdapter(FragmentManagerfm,List<ViewPagerBean> list){super(fm); this.list = list} @OverridepublicMyFragmentgetItem(intposition){returnlist.get(position).fragment;// 这里返回 MyFragment } @OverridepublicintgetCount(){returnlist.size()} @OverridepublicCharSequencegetPageTitle(intposition){returnlist.get(position).title;// 这里返回 title } }最后在 MainActivity 中这样写 :
privatevoidinit(){... List<String> tabList = getTab();// 还是之前的数据List<ViewPagerBean> list = newArrayList<>(); for (inti = 0; i < tabList.size(); i++){ViewPagerBeanbean = newViewPagerBean(newMyFragment(tabList.get(i)),tabList.get(i)); list.add(bean);// 组合新数据 } viewPager.setAdapter(newViewPagerAdapter(getSupportFragmentManager(), list));// 将我们的新数据传给 AdaptertabLayout.setupWithViewPager(viewPager)}这个更简单了,同样的两种方法: 1.系统已经提供了这个API
<android.support.design.widget.TabItem android:layout_width="wrap_content"android:layout_height="wrap_content"item:icon="@mipmap/ic_launcher"item:text="标题一"/>或者在代码里设置:
tabLayout.addTab(tabLayout.newTab().setText("标题一").setIcon(R.mipmap.ic_launcher)); tabLayout.addTab(tabLayout.newTab().setText("标题二").setIcon(R.mipmap.ic_launcher));至于设置的图片的大小...在 android.support.design.widget.TabLayout 的1699行可以debug搞到,设置成48*48的大小了,
2.自定义TabItem的布局 tab.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:orientation="vertical"> <ImageViewandroid:id="@+id/iv"android:layout_width="24dp"android:layout_height="24dp"/> <TextViewandroid:id="@+id/tv"android:layout_width="wrap_content"android:layout_height="wrap_content"/> </LinearLayout>很简单,然后在Activity中:
privatevoidinit(){tabLayout = (TabLayout) findViewById(R.id.tab_layout); viewPager = (ViewPager) findViewById(R.id.view_pager); List<String> tabList = getTab(); List<ViewPagerBean> list = newArrayList<>(); for (inti = 0; i < tabList.size(); i++){ViewPagerBeanbean = newViewPagerBean(newMyFragment(tabList.get(i)),tabList.get(i)); list.add(bean)} viewPager.setAdapter(newViewPagerAdapter(getSupportFragmentManager(), list)); tabLayout.setupWithViewPager(viewPager); tabLayout.removeAllTabs();// 呸!就你会 remove 是吧?(ps:在adapter中因为getTitle()方法的存在会多添加一次,所以删除掉)for (inti = 0; i < tabList.size(); i++){Viewview = LayoutInflater.from(this).inflate(R.layout.tab,null); ((ImageView)view.findViewById(R.id.iv)).setImageDrawable(ContextCompat.getDrawable(this,R.mipmap.ic_launcher)); ((TextView)view.findViewById(R.id.tv)).setText(tabList.get(i)); tabLayout.addTab(tabLayout.newTab().setCustomView(view))} }再看效果
OK,自定义布局完全自由,想怎么布局就怎么布局,然而,是不是发现指示器长度有点长?怎么改?比较坑的是系统没有这样的方法!我们能修改吗?答案是肯定的!
这里有点麻烦,需要用到反射来修改,不多说,直接看方法注释:
publicvoidsetIndicator(TabLayouttabs, intleftDip, intrightDip){Class<?> tabLayout = tabs.getClass(); FieldtabStrip = null; try{tabStrip = tabLayout.getDeclaredField("mTabStrip")} catch (NoSuchFieldExceptione){e.printStackTrace()} tabStrip.setAccessible(true); LinearLayoutllTab = null; try{llTab = (LinearLayout) tabStrip.get(tabs)} catch (IllegalAccessExceptione){e.printStackTrace()} intleft = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, leftDip, Resources.getSystem().getDisplayMetrics()); intright = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, rightDip, Resources.getSystem().getDisplayMetrics()); // 这里是为了通过 TextView 的 Paint 来测量文字所占的宽度TextViewtv = newTextView(this); // 必须设置和tab文字一样的大小,因为不同大小字所占宽度不同tv.setTextSize(TypedValue.COMPLEX_UNIT_SP,14); for (inti = 0; i < llTab.getChildCount(); i++){Viewchild = llTab.getChildAt(i); child.setPadding(0, 0, 0, 0); // 当前TAB上的文字Stringstr = tabs.getTabAt(i).getText().toString(); // 所占的宽度intwidth = (int) tv.getPaint().measureText(str); // 这里设置宽度,要稍微多一点,否则丑死了!LinearLayout.LayoutParamsparams = newLinearLayout.LayoutParams(width+20, LinearLayout.LayoutParams.MATCH_PARENT); // params.leftMargin = left;//莫名的会卡顿// params.rightMargin = right;//莫名的会卡顿child.setLayoutParams(params); child.invalidate()} }我们只需要这样调就可以,传入左右两边的间距:
setIndicator(tabLayout, 10, 10);此方法是通过反射获取tablayout私有属性进修改属性值,如果app启动混淆 会报NoSuchFieldException;
解决方案:在混淆文件中添加:
-keep class Android.support.design.widget.TabLayout{*}效果就不贴了,

