Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

sing1/TabLayout

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

7 Commits

Repository files navigation

TabLayout 的使用

首先需要在 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 中的字体大小呢?

TabLayout 中有个属性 tabTextAppearance,这里可以指定一个 style,所以我们定义一个 style:

<style name="TabLayoutTextStyle">
 <item name="android:textSize">16sp</item>
</style>

然后在 TabLayout 中这样设置就可以达到修改字体大小的效果了:

app:tabTextAppearance="@style/TabLayoutTextStyle"

默认选择第一个

tabLayout.getTabAt(0).select();

情形一(已知 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="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>

MainActivity

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 的个数等相关信息,动态添加)

有时候我们不知道 TabItem 的个数,而是从服务器等其他途径获得,这个时候我们就需要动态来添加了,xml 的代码比较简单:

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>

MainActivity

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

再来看效果:

OK,完美解决了。

如果要显示文字+图片等效果呢?

这个更简单了,同样的两种方法: 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的大小了,

这里还有一个问题,如果还是用的之前的 Adapter 的话会发现之前添加的Tab也在,所以不显示标题的两种解决方案不要同时使用!

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{*;}

效果就不贴了,

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

AltStyle によって変換されたページ (->オリジナル) /