资讯专栏INFORMATION COLUMN

TabLayout的简单运用和若干问题的解决

PiscesYE / 1294人阅读

摘要:初步实现之前在知乎上看到有人对微信的设计改动将使用频率高的朋友圈消息提醒和公众号这三个功能独立出来放在首页。

1、介绍和准备

我们在使用手机App时不难会看到这样的页面上面是一组起导航作用的标签,点击标签就会切换到相应的页面;在不同的页面中滑动时,标签的样式(文字大小或者颜色)也会发生变化。这样你任何时候都能一眼看出自己停留在哪个页面。这个布局出镜率实在太高了,我甚至敢说每个学Android的人都写过这样的布局(下面就是知乎中的页面)。

好了,废话少说,我们照例先来分析一下这个布局的组成。标签下面的页面比较容易想到:整体是一个左右滑动的ViewPager,每一页则可以用Fragment填充,也就是ViewPager+Fragment。但上面的标签部分就有点头大了,之前我们都是使用第三方的项目(如PagerSlidingTabStrip),高手的话也可以自定义一个控件。但是这样并非长久之计,所以谷歌后来人性化地推出了自家的标签控件TabLayout(注意可不要跟TableLayout搞混了,后者是Android的基本布局之一,而前者是一个控件)。TabLayout顾名思义就是包含Tab的布局,它包含在Design support library库中,要使用它,你需要在先添加依赖库:

我导入的是最新的26.0.0版本:

compile "com.android.support:design:26.0.0-alpha1"

准备完这些,我们可以开始写代码了。

2、初步实现

之前在知乎上看到有人对微信的设计改动:将使用频率高的朋友圈、消息提醒和公众号这三个功能独立出来放在首页。我很赞同这样的设计思路,所以今天就来弄一个简陋版的吧。大体效果如下:

2.1 页面布局


    

    

    

上面的TabLayout的高度固定为60dp,然后让ViewPager占据剩余的空间即可。现在我来介绍一下用到的TabLayout的属性:

app:tabIndicatorColor:标签下面移动的横线的颜色。

app:tabTextColor:标签文字的颜色

app:tabSelectedTextColor :标签被选中后的文字颜色

TabLayout还有很多其他的属性,比如你要是不想要下面的移动横线的话,可以调用属性app:tabIndicatorHeight ,将高度设置为0dp即可。关于TabLayout的其他属性,大家可以看看这篇博客,动手练习一下:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0731/3247.html

2.2 创建Viewpager页面(Fragment)

为了能够识别我们切换到的是ViewPager的哪个页面,我们在Fragment中创建一个带参数的构造函数,动态添加一个TextView,它的文本内容跟标签的一致就好。

public class TabFragment extends Fragment {
    private Context context;
    private String content; //Fragment的显示内容
    public TabFragment() {

    }

    public TabFragment(Context context,String content){
        this.context = context;
        this.content = content;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        TextView textView = new TextView(context);
        textView.setText(content);
        textView.setTextSize(30);
        textView.setGravity(Gravity.CENTER);
        return textView;
    }
}
2.3 MainActivity代码

先来看代码:

public class MainActivity extends AppCompatActivity {
    private ViewPager viewPager;
    private TabLayout tabLayout;
    private List fragments = new ArrayList<>();
    private List tabs = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
        initView();
    }

    private void initData() {
        tabs.add("新消息");
        tabs.add("朋友圈");
        tabs.add("公众号");
        fragments.add(new TabFragment(this,tabs.get(0)));
        fragments.add(new TabFragment(this,tabs.get(1)));
        fragments.add(new TabFragment(this,tabs.get(2)));
    }

    private void initView() {
        tabLayout = (TabLayout) findViewById(R.id.tayLayout);
        viewPager = (ViewPager) findViewById(R.id.viewPager);
        //设置TabLayout的模式
        tabLayout.setTabMode(TabLayout.MODE_FIXED);
        viewPager.setAdapter(new TabAdapter(getSupportFragmentManager()));
        //关联ViewPager和TabLayout
        tabLayout.setupWithViewPager(viewPager);
    }

    class TabAdapter extends FragmentPagerAdapter{
        public TabAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            return fragments.get(position);
        }

        @Override
        public int getCount() {
            return fragments.size();
        }
        
        //显示标签上的文字
        @Override
        public CharSequence getPageTitle(int position) {
            return tabs.get(position);
        }
    }
}

代码不长,initData方法中添加数据,initView方法初始化控件,跟我们平时使用ViewPager的写法差别不大。要注意的是TabLayout需要设置模式(即setTabMode方法),一共有两种:

TabLayout.MODE_FIXED :当Tab较少,且占满整个屏幕时可以使用这种模式;

TabLayout.MODE_SCROLLABLE :当Tab数量较多,屏幕宽度不够时使用该模式,整个TabLayout是可以左右滑动的。

除此之外,我们需要让Tab显示文字,要重写FragmentPagerAdapter的getPageTitle方法,返回每一个Tab的文字内容。最后可别忘了最关键的一步:使用setupWithViewPager方法关联Viewpager和TabLayout,这样两者才会联动。

运行一下,就可以看到动态图中的效果了。

3、进阶 3.1 修改标签字体大小

默认的Tab字体大小有点小,看起来不太舒服,当我们去修改字体大小时却发现,TabLayout居然没有提供跟TextSize相关的属性。不过不用急,TabLayout其实提供了一个更灵活的属性app:tabTextAppearance ,它可以修改字体的样式,从而间接修改字体的大小。

我们在style.xml中自定义一个样式,继承于TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse ,在属性android:textSize中设置我们想要的字体大小,这里我设为20sp。

      

接下来在布局文件中使用就可以了。

运行一下,这下看起来清楚多了。

3.2 添加分割线

TabLayout的标签之间是默认没有分割线的,如果我们想添加分割线,让标签之间更有层次感的话,可以添加以下的代码:

        //设置分割线
        LinearLayout linearLayout = (LinearLayout) tabLayout.getChildAt(0);
        linearLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
        linearLayout.setDividerDrawable(ContextCompat.getDrawable(this,
                R.drawable.divider)); //设置分割线的样式
        linearLayout.setDividerPadding(dip2px(10)); //设置分割线间隔

自定义的分割线样式:



    
    

setDividerPadding方法中输入的参数是单位是px,我们需要转换成像素:

    //像素单位转换
    public int dip2px(int dip) {
        float density = getResources().getDisplayMetrics().density;
        return (int) (dip * density + 0.5);
    }
3.2 显示信息数目

现在我们来尝试一下这样的效果:将tab的文字分为两行,第二行显示信息的数目,当然,我们并没有真的信息,所以直接输入一些假数据就可以。为了让文字变为两行,我们可以加入换行符。

        tabs.add("新消息"+"
"+999);
        tabs.add("朋友圈"+"
"+99);
        tabs.add("公众号"+"
"+9);

运行,发现文字确实分成了两行。但是等等,怎么文字大小小了那么多?

如果你查一下TabLayout的源码,就会发现这一切早就命中注定了。源码中有这么一段:

               if (mIconView != null && mIconView.getVisibility() == VISIBLE) {
                    // If the icon view is being displayed, we limit the text to 1 line
                    maxLines = 1;
                } else if (mTextView != null && mTextView.getLineCount() > 1) {
                    // Otherwise when we have text which wraps we reduce the text size
                    textSize = mTabTextMultiLineSize;
                }

这里是设置Tab的icon和字体大小的,在else if代码块中,我们发现了,当TextView的文本大于一行时,就会强制使用特定的字体(textSize = mTabTextMultiLineSize),这就解释了为什么我们的字体设置不奏效了。

那么,我们该怎么办呢?

3.3 自定义标签

条条大路通罗马,TabLayout早就给我们准备了另一条路了,那就是自定义标签布局。(温馨提示:下面的代码对之前的改动较大,大家可能会觉得之前做的都是无用功,但是凡事总是循序渐进的,请不必灰心。)

自定义标签布局



    

    

这里我们用到了一个选择器,代码如下:

标签颜色选择器


    
    
在代码中实现
    /**
     * 设置Tab的样式
     */
        private void setTabView() {
        holder = null;
        for (int i = 0; i < tabs.size(); i++) {
            //依次获取标签
            TabLayout.Tab tab = tabLayout.getTabAt(i);
            //为每个标签设置布局
            tab.setCustomView(R.layout.tab_item);
            holder = new ViewHolder(tab.getCustomView());
            //为标签填充数据
            holder.tvTabName.setText(tabs.get(i));
            holder.tvTabNumber.setText(String.valueOf(tabNumbers.get(i)));
            //默认选择第一项
            if (i == 0){
                holder.tvTabName.setSelected(true);
                holder.tvTabNumber.setSelected(true);
                holder.tvTabName.setTextSize(18);
                holder.tvTabNumber.setTextSize(18);
            }
        }

        //tab选中的监听事件
        tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                holder = new ViewHolder(tab.getCustomView());
                holder.tvTabName.setSelected(true);
                holder.tvTabNumber.setSelected(true);
                //选中后字体变大
                holder.tvTabName.setTextSize(18);
                holder.tvTabNumber.setTextSize(18);
                //让Viewpager跟随TabLayout的标签切换
                viewPager.setCurrentItem(tab.getPosition());

            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
                holder = new ViewHolder(tab.getCustomView());
                holder.tvTabName.setSelected(false);
                holder.tvTabNumber.setSelected(false);
                //恢复为默认字体大小
                holder.tvTabName.setTextSize(16);
                holder.tvTabNumber.setTextSize(16);
            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });
    }

    class ViewHolder{
        TextView tvTabName;
        TextView tvTabNumber;

        public ViewHolder(View tabView) {
            tvTabName = (TextView) tabView.findViewById(R.id.tv_tab_name);
            tvTabNumber = (TextView) tabView.findViewById(R.id.tv_tab_number);
        }
    }

创建一个setTabView方法来设置Tab的样式,在for循环中为每一个标签创建布局。setCustomView方法可以设置Tab的布局,getCustomView则可以获取当前Tab的布局。

我们既然使用的是自定义的布局,那么选中时的样式也要手动设置了。跟ViewPager类似,TabLayout也有自己的选中监听事件(addOnTabSelectedListener)。在标签被选中时将状态设置为选中,并切换到相应的ViewPager页面,未选中的页面则将选中状态设为false即可。

补充一点,选中Tab后字体变大这一功能是我后面加上去的,所以代码只在GitHub中更新了。由于属性android:textSize 不支持drawable文件,所以这里不能用状态选择器,但好在代码里实现也不复杂,就不必过多解释了。

4、开拓思维

既然TabLayout如此贴心地给我们提供了自定义标签布局的方法,那么我们就要好好利用它,比如除了文字之外,我们还可以添加图片,让标签页的内容更加丰富。另外,TabLayout不一定非要放在顶部,也可以放在底部,去掉下划线之后就可以实现与RadioGroup一样的效果。

最后,是说好的源码了:
CSDN
GitHub

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67124.html

相关文章

  • TabLayout使用遇到坑及方案

    摘要:但对于我们的对于界面还原度要求较高,对于之间的间距也有一些要求,所以也要处理,对于间距部分的处理可以按照之前的方式通过反射来完成。注意,这种方式因为需要计算的文字宽度,所以要放到设置完所有的后调用。 修改下划线宽度的坑 效果如下: showImg(https://s2.ax1x.com/2019/04/18/ES2KYV.png); 代码实现方式: 如果想要实现这种效果,最主要控制的就...

    baishancloud 评论0 收藏0

发表评论

0条评论

PiscesYE

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<