首先需要在 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:
<style name="TabLayoutTextStyle">
<item name="android:textSize">16sp</item>
</style>
然后在 TabLayout 中这样设置就可以达到修改字体大小的效果了:
app:tabTextAppearance="@style/TabLayoutTextStyle"
默认选择第一个
tabLayout.getTabAt(0).select();
<?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">
<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>
public class MainActivity extends AppCompatActivity {
TabLayout tabLayout;
ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
tabLayout = (TabLayout) findViewById(R.id.tab_layout);
viewPager = (ViewPager) findViewById(R.id.view_pager);
List<Fragment> list = new ArrayList<>();
list.add(new MyFragment("标题一"));// 就是一个普通的 Fragment,里面有一个 TextView 显示第几个
list.add(new MyFragment("标题二"));
list.add(new MyFragment("标题三"));
//正常设置 Adapter
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), list));
tabLayout.setupWithViewPager(viewPager);// 官方推荐
for (int i = 0; i < list.size(); i++) {// 这里的坑稍后解释
tabLayout.getTabAt(i).setText(((MyFragment)list.get(i)).getTitle());
}
}
}
有时候我们不知道 TabItem 的个数,而是从服务器等其他途径获得,这个时候我们就需要动态来添加了,xml 的代码比较简单:
<?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">
<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>
public class MainActivity extends AppCompatActivity {
TabLayout tabLayout;
ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
tabLayout = (TabLayout) findViewById(R.id.tab_layout);
viewPager = (ViewPager) findViewById(R.id.view_pager);
List<MyFragment> list = new ArrayList<>();// 装载的viewpager的数据
List<String> tabList = getTab();
for (int i = 0; i < tabList.size(); i++) {
tabLayout.addTab(tabLayout.newTab().setText(tabList.get(i)));// 给 tabLayout 添加 Tab
list.add(new MyFragment(tabList.get(i)));
}
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), list));
tabLayout.setupWithViewPager(viewPager);
}
// 假如这是从服务器获取的数据
private List<String> getTab(){
List<String> list = new ArrayList<>();
list.add("新闻");
list.add("体育");
list.add("科技");
list.add("直播");
list.add("汽车");
list.add("公益");
list.add("娱乐");
list.add("财经");
list.add("时尚");
list.add("房产");
list.add("旅游");
list.add("艺术");
return list;
}
}
然后我们看看实际效果
标题呢?我的标题呢?这里就有一个坑了,看看 setupWithViewPager() 的源码怎么回事?
private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
boolean implicitSetup) {
// 省略代码 ...
if (viewPager != null) {
// 省略代码 ...
if (adapter != null) {
// Now we'll populate ourselves from the pager adapter, adding an observer if
// autoRefresh is enabled
setPagerAdapter(adapter, autoRefresh);
}
// 省略代码 ...
} else {
// 省略代码 ...
}
mSetupViewPagerImplicitly = implicitSetup;
}
然后我们看到了这么一段:
final PagerAdapter adapter = viewPager.getAdapter();
if (adapter != null) {
// Now we'll populate ourselves from the pager adapter, adding an observer if
// autoRefresh is enabled
setPagerAdapter(adapter, autoRefresh);
}
继续看 setPagerAdapter 方法里面调用了 populateFromPagerAdapter():
void populateFromPagerAdapter() {
removeAllTabs();
if (mPagerAdapter != null) {
final int adapterCount = mPagerAdapter.getCount();
for (int i = 0; i < adapterCount; i++) {
addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false);
}
// Make sure we reflect the currently set ViewPager item
if (mViewPager != null && adapterCount > 0) {
final int curItem = 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:
public class ViewPagerBean implements Serializable {
public MyFragment fragment;
public String title;
public ViewPagerBean(MyFragment fragment, String title) {
this.fragment = fragment;
this.title = title;
}
}
然后修改一下 Adapter :
public class ViewPagerAdapter extends FragmentPagerAdapter {
List<ViewPagerBean> list = new ArrayList<>();
public ViewPagerAdapter(FragmentManager fm,List<ViewPagerBean> list) {
super(fm);
this.list = list;
}
@Override
public MyFragment getItem(int position) {
return list.get(position).fragment;// 这里返回 MyFragment
}
@Override
public int getCount() {
return list.size();
}
@Override
public CharSequence getPageTitle(int position) {
return list.get(position).title;// 这里返回 title
}
}
最后在 MainActivity 中这样写 :
private void init() {
...
List<String> tabList = getTab();// 还是之前的数据
List<ViewPagerBean> list = new ArrayList<>();
for (int i = 0; i < tabList.size(); i++) {
ViewPagerBean bean = new ViewPagerBean(new MyFragment(tabList.get(i)),tabList.get(i));
list.add(bean);// 组合新数据
}
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), list));// 将我们的新数据传给 Adapter
tabLayout.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"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/iv"
android:layout_width="24dp"
android:layout_height="24dp"/>
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
很简单,然后在Activity中:
private void init() {
tabLayout = (TabLayout) findViewById(R.id.tab_layout);
viewPager = (ViewPager) findViewById(R.id.view_pager);
List<String> tabList = getTab();
List<ViewPagerBean> list = new ArrayList<>();
for (int i = 0; i < tabList.size(); i++) {
ViewPagerBean bean = new ViewPagerBean(new MyFragment(tabList.get(i)),tabList.get(i));
list.add(bean);
}
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), list));
tabLayout.setupWithViewPager(viewPager);
tabLayout.removeAllTabs();// 呸!就你会 remove 是吧?(ps:在adapter中因为getTitle()方法的存在会多添加一次,所以删除掉)
for (int i = 0; i < tabList.size(); i++) {
View view = 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,自定义布局完全自由,想怎么布局就怎么布局,然而,是不是发现指示器长度有点长?怎么改?比较坑的是系统没有这样的方法!我们能修改吗?答案是肯定的!
这里有点麻烦,需要用到反射来修改,不多说,直接看方法注释:
public void setIndicator(TabLayout tabs, int leftDip, int rightDip) {
Class<?> tabLayout = tabs.getClass();
Field tabStrip = null;
try {
tabStrip = tabLayout.getDeclaredField("mTabStrip");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
tabStrip.setAccessible(true);
LinearLayout llTab = null;
try {
llTab = (LinearLayout) tabStrip.get(tabs);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
int left = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, leftDip, Resources.getSystem().getDisplayMetrics());
int right = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, rightDip, Resources.getSystem().getDisplayMetrics());
// 这里是为了通过 TextView 的 Paint 来测量文字所占的宽度
TextView tv = new TextView(this);
// 必须设置和tab文字一样的大小,因为不同大小字所占宽度不同
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP,14);
for (int i = 0; i < llTab.getChildCount(); i++) {
View child = llTab.getChildAt(i);
child.setPadding(0, 0, 0, 0);
// 当前TAB上的文字
String str = tabs.getTabAt(i).getText().toString();
// 所占的宽度
int width = (int) tv.getPaint().measureText(str);
// 这里设置宽度,要稍微多一点,否则丑死了!
LinearLayout.LayoutParams params = new LinearLayout.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{*;}
效果就不贴了,