TabLayout+viewPager+FragmentPagerAdapter和FragmentStatePagerAdapter+修改指示器长度字体大小

TabLayout是属于容器控件, 提供水平显示Tab的效果. 常常和ViewPager配合使用.

添加依赖
这是Android Design 包下的类, 该包是Android5.0 引入的UI包
compile 'com.android.support:design:25.2.0'

布局
<android.support.design.widget.TabLayout
    android:id="@+id/tab_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

</android.support.design.widget.TabLayout>

代码
public classMainActivityextendsAppCompatActivity{

    @BindView(R.id.tab_layout)
    TabLayout mTabLayout;

    @Override
    protectedvoidonCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        mTabLayout.addTab(mTabLayout.newTab().setText("首页"));
        mTabLayout.addTab(mTabLayout.newTab().setText("分类"));
        mTabLayout.addTab(mTabLayout.newTab().setText("设置"));
    }
}

第二种方式
完全通过布局创建
<android.support.design.widget.TabLayout
    android:id="@+id/tab_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.design.widget.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="首页"
        />

    <android.support.design.widget.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="分类"
        />

    <android.support.design.widget.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="设置"
        />

</android.support.design.widget.TabLayout>

可滑动

app:tabMode="scrollable"

固定

app:tabMode="fixed"

指示器选项

app:tabIndicatorHeight="10dp"   //指示器高度
app:tabIndicatorColor="@color/colorPrimary" // 指示器颜色

文字选项

app:tabSelectedTextColor="#ffffff"  // 选择的Tab的文字颜色
app:tabTextColor="#000000"  // 未选择的Tab文字颜色
app:tabTextAppearance="@style/Base.TextAppearance.AppCompat.Large" // 文字样式

背景设置
两者没什么区别

android:background="@color/colorAccent" // 背景
app:tabBackground="@color/colorPrimary" //背景

标签距离

app:tabPaddingStart="10dp"
app:tabPaddingBottom="10dp"
app:tabPadding="10dp"
app:tabPaddingEnd="10dp"
app:tabPaddingTop="10dp"

设置字体大小

app:tabTextAppearance 属性:此处要写一个style
<style name="TabLayoutTextStyle">
        <item name="android:textSize">@dimen/text_16_sp</item>
</style>

对齐方式
居中显示

app:tabGravity="center"

填充

app:tabGravity="fill"

偏移
从左边开始偏移距离, 必须是可滑动的模式 scrollable

app:tabContentStart="200dp"

标签宽度限制
最大宽度

app:tabMaxWidth="50dp"

最小宽度

app:tabMinWidth="100dp"

代码

TabLayout提供的方法
标签
创建标签
TabLayout.TabnewTab()

添加标签, 只有添加后才能显示
voidaddTab(TabLayout.Tab tab)

void addTab (TabLayout.Tab tab,
                int position)
                
void addTab (TabLayout.Tab tab,
                boolean setSelected)

void addTab (TabLayout.Tab tab,
                int position, 
                boolean setSelected)

删除标签
voidremoveTab(TabLayout.Tab tab)

通过索引删除标签
voidremoveTabAt(intposition)

删除全部标签
voidremoveAllTabs()

得到标签
TabLayout.TabgetTabAt(intindex)

得到标签总数
intgetTabCount()

设置样式
指示器
voidsetSelectedTabIndicatorColor(intcolor)// 指示器颜色
void setSelectedTabIndicatorHeight (intheight) // 指示器高度

标签文本
voidsetTabTextColors(intnormalColor, // 正常颜色
                int selectedColor) // 选择状态颜色

void setTabTextColors (ColorStateList textColor) // 状态颜色

显示模式
这个之前属性里面介绍过了
intgetTabMode()
void setTabMode (intmode)

mode:
  1. MODE_SCROLLABLE
  2. MODE_FIXED
对齐方式
voidsetTabGravity(intgravity)
int getTabGravity ()

添加View
不止是添加标签Tab还可以直接添加View
voidaddView(View child)

void addView (View child,
                int index)
                
void addView (View child,
                ViewGroup.LayoutParams params)

void addView (View child, // View对象
                int index, // 位置索引
                ViewGroup.LayoutParams params) // 布局参数

得到当前选择的位置
intgetSelectedTabPosition()

监听器
选择监听器
该方法已经被废弃, 不推荐使用.
voidsetOnTabSelectedListener(TabLayout.OnTabSelectedListener listener)

替代的方法是
voidaddOnTabSelectedListener(TabLayout.OnTabSelectedListener listener)

该监听器用完后需要删除
voidremoveOnTabSelectedListener(TabLayout.OnTabSelectedListener listener)

一次性删除所有添加的选择监听器
voidclearOnTabSelectedListeners()

Tab
该类是TabLayout的内部类, 表示TabLayout中的每一个标签. 我将介绍这个类的所有方法
判断是否被选择
booleanisSelected()

设置为被选择状态
voidselect()

描述内容
如果你没用设置描述内容, 默认的是标签的标题
TabLayout.TabsetContentDescription(intresId)// 用strings id的
TabLayout.Tab setContentDescription (CharSequence contentDesc)
CharSequence getContentDescription ()

自定义标签的内容
每个标签可以尽情的自定义视图
TabLayout.TabsetCustomView(intresId)
TabLayout.Tab setCustomView (View view)

标签的标签
给Tab设置tag, 然后就可以通过tag得到Tab
TabLayout.TabsetTag(Object tag)
Object getTag ()

添加图标
TabLayout.TabsetIcon(Drawable icon)
TabLayout.Tab setIcon (intresId)
Drawable getIcon ()

标题的文字
TabLayout.TabsetText(intresId)
TabLayout.Tab setText (CharSequence text)
CharSequence getText ()

当前标签位置
intgetPosition()

关联ViewPager
03152741_941.gif

TabLayout和ViewPager配合使用是最常见的运用方式, 可以说量身打造. 这里我将介绍两种方式.
两者配合使用后TabLayout就不能通过自己创建Tab了, 需要PagerAdapter中实现 getPagerTitle() 方法返回标签的文字. 标签的数量有ViewPager的分页数量决定.
布局中嵌套
布局

<android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</android.support.v4.view.ViewPager>

public classMainActivityextendsAppCompatActivity{

    @BindView(R.id.tab_layout)
    TabLayout mTabLayout;
    @BindView(R.id.viewpager)
    ViewPager mViewpager;
    private ArrayList<View> mList;
    private String[] mTitle;

    @Override
    protectedvoidonCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        initData();

        mViewpager.setAdapter(new PagerAdapter() {
            @Override
            publicintgetCount(){
                return mList.size();
            }

            @Override
            publicbooleanisViewFromObject(View view, Object object){
                return view == object;
            }

            @Override
            publicObjectinstantiateItem(ViewGroup container,intposition){
                View view = mList.get(position);
                container.addView(view);
                return view;
            }

            @Override
            publicvoiddestroyItem(ViewGroup container,intposition, Object object){
                container.removeView((View) object);
            }

            @Override
            publicCharSequencegetPageTitle(intposition){
                return mTitle[position];
            }
        });
    }

    privatevoidinitData(){
        View viewpagerA = getLayoutInflater().inflate(R.layout.viewpager_a, null);
        View viewpagerB = getLayoutInflater().inflate(R.layout.viewpager_b, null);
        View viewpagerC = getLayoutInflater().inflate(R.layout.viewpager_c, null);

        mList = new ArrayList<>();
        mList.add(viewpagerA);
        mList.add(viewpagerB);
        mList.add(viewpagerC);

        mTitle = new String[]{"首页", "分类", "设置"};
    }

布局中关联
如果布局没有嵌套

<android.support.design.widget.TabLayout
    android:id="@+id/tab_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

<android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

就需要在ViewPager设置PagerAdapter之前关联两者

mTabLayout.setupWithViewPager(mViewpager);

虽然配合ViewPager后TabLayout创建的Tab并不能正常显示, 因为setupWithViewPager内部方法是先删除所有的标签再添加.
但是还是可以通过 getTabAt() 得到标签之后进行修改.

FragmentPagerAdapter和FragmentStatePagerAdapter的区别
FragmentPagerAdapter
● 该类内的每一个生成的 Fragment 都将保存在内存之中,因此适用于那些相对静态的页,数量也比较少的那种;如果需要处理有很多页,并且数据动态性较大、占用内存较多的情况,应该使用FragmentStatePagerAdapter
● 现象:每个位置getItem(position)只走一次
● 只继承这个方法并没效果,要设置如下属性

mVpMoneyRecord.setOffscreenPageLimit(list.size());

FragmentStatePagerAdapter
● 当页面离开视线后,就会被消除,释放其资源;而在页面需要显示时,生成新的页面。这么实现的好处就是当拥有大量的页面时,不必消耗大量的内存。
● 现象:每个位置getItem(position)可能走多次

mViewPager.setAdapter(new MainAdapter(mTitles, getSupportFragmentManager()));
//关联TabLayout和ViewPager
mTabLayout.setupWithViewPager(mViewPager);


public class MainAdapter extends FragmentPagerAdapter {

    private static final String TAG = "MainAdapter";

    private String[] dataList;

    public MainAdapter(String[] dataList, FragmentManager fm) {
        super(fm);
        this.dataList = dataList;
    }

    /**
     * 返回对应位置的Fragment
     * @param position
     * @return
     */
    @Override
    public Fragment getItem(int position) {
        Log.d(TAG, "getItem: " + position);
//        return FragmentFactory.getFragment(position);
        return FragmentFactory.getInstance().getFragment(position);
    }

    @Override
    public int getCount() {
        return dataList.length;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return dataList[position];
    }
}


public class FragmentFactory {

    private static final int FRAGMENT_HOME = 0;
    private static final int FRAGMENT_APP = 1;
    private static final int FRAGMENT_GAME = 2;
    private static final int FRAGMENT_SUBJECT = 3;
    private static final int FRAGMENT_RECOMMEND = 4;
    private static final int FRAGMENT_CATEGORY = 5;
    private static final int FRAGMENT_HOT = 6;

    //单例模式,一个app中只存在一个FragmentFactory实例
    private static FragmentFactory sFragmentFactory;


    /**
     * 两个非空判断一个锁
     * @return
     */
    public static FragmentFactory getInstance() {
        if (sFragmentFactory == null) {
            //只需要在sFragmentFactory为空时候才加锁创建就可以
            synchronized (FragmentFactory.class) {
                if (sFragmentFactory == null) {//还是要去判断是否为空
                    sFragmentFactory = new FragmentFactory();
                }
            }
        }
        //如果对象已经创建好了,就不要加锁,直接返回
        return sFragmentFactory;
    }


    /**
     * 根据不同的位置生产出不同的Fragment
     * @param position fragment位置
     */
    public Fragment getFragment(int position) {
        switch (position) {
            case FRAGMENT_HOME:
                return new HomeFragment();
            case FRAGMENT_APP:
                return new AppFragment();
            case FRAGMENT_GAME:
                return new GameFragment();
            case FRAGMENT_SUBJECT:
                return new SubjectFragment();
            case FRAGMENT_RECOMMEND:
                return new RecommendFragment();
            case FRAGMENT_CATEGORY:
                return new CategoryFragment();
            case FRAGMENT_HOT:
                return new HotFragment();
        }
        return null;
    }
}

修改字体大小

app:tabTextAppearance="@style/TabLayoutTextStyle"
<style name="TabLayoutTextStyle">
        <item name="android:textSize">@dimen/text_16_sp</item>
</style>

一般使用

<android.support.design.widget.TabLayout
    android:layout_marginTop="48dp"
    android:id="@+id/tl_myZhongChuang"
    android:layout_width="match_parent"
    android:layout_height="43dp"
    app:tabBackground="@color/white"
    app:tabSelectedTextColor="@color/colorPrimary"
    app:tabTextColor="@color/indicatorColor_gray"
    app:tabIndicatorColor="@color/colorPrimary"
    app:tabIndicatorHeight="1dp"
    app:tabMode="fixed">
</android.support.design.widget.TabLayout>

XML中没设置字体颜色会报不能inflate的异常

Fragment虽然有onResume和onPause的,但是这两个方法是Activity的方法,调用时机也是与Activity相同,和ViewPager搭配使用这个方法就很鸡肋了,根本不是你想要的效果,这里介绍一种方法。

@Override  
   public void setUserVisibleHint(boolean isVisibleToUser) {  
       super.setUserVisibleHint(isVisibleToUser);  
       if (isVisibleToUser) {  
           //相当于Fragment的onResume  
       } else {  
           //相当于Fragment的onPause  
       }  
   } 
修改指示器长度

在设置adapter之后调用此方法
混淆可能会挂要配置
缺点:同时修改了字体的间距,当tab较少时可以设置大点margin

public static void alterTabIndicatorWidth(TabLayout tabLayout, Context context, float marginValue){
    try {
        Class<?> tablayout = tabLayout.getClass();
        Field tabStrip = tablayout.getDeclaredField("mTabStrip");
        tabStrip.setAccessible(true);
        LinearLayout ll_tab= (LinearLayout) tabStrip.get(tabLayout);
        for (int i = 0; i < ll_tab.getChildCount(); i++) {
            View child = ll_tab.getChildAt(i);
            child.setPadding(0,0,0,0);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {

                LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT,1);
                params.setMarginStart(DensityUtil.dip2px(context,marginValue));
                params.setMarginEnd(DensityUtil.dip2px(context,marginValue));
                child.setLayoutParams(params);
                child.invalidate();
            }
        }
    }catch (Exception e){

    }
}

public class DensityUtil {

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }
}
禁止ViewPager左右滑动

自定义viewpager

public class MyViewPager extends ViewPager{
    /**1 默认true 可以滑动;
     * 2 只需要将返回值改为false,那么ViewPager就不会消耗掉手指滑动的事件了,转而传递给上层View去处理或者该事件就直接终止了。*/
    private boolean isScrollable = false;

    public MyViewPager (Context context) {
        super(context);
    }

    public MyViewPager (Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setScrollable(boolean isScrollable) {
        this.isScrollable = isScrollable;
    }

    @Override
    public void scrollTo(int x, int y) {
        super.scrollTo(x, y);
    }

    @Override
    public boolean onTouchEvent(MotionEvent arg0) {
        /* return false;//super.onTouchEvent(arg0); */
        if (isScrollable)
            return super.onTouchEvent(arg0);
        else
            return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent arg0) {
        if (isScrollable)
            return super.onInterceptTouchEvent(arg0);
        else
            return false;
    }

    @Override
    public void setCurrentItem(int item, boolean smoothScroll) {
        super.setCurrentItem(item, smoothScroll);
    }

    @Override
    public void setCurrentItem(int item) {
        super.setCurrentItem(item);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354