5.Android support design TabLayout

5.Android support design TabLayout


TabLayout介绍

Google 官方的 Android support design Library 也推出了 滑动标签页。它的名字叫TabLayout ,其实还是有些地方有Bug。

如果涉及到 icon + text 的滑动标签页,建议不要用TabLayout 。推荐用我的 EasySlidingTabs ,哈哈。


gradle配置

compile 'com.android.support:design:23.0.1' 或者更高版本

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:design:23.0.1'
}

TabLayout属性

我们可以找到sdk/extras/android/support/design/res/values/attrs.xml,打开可以看到关于TabLayout的属性

    <declare-styleable name="TabLayout">
        <attr name="tabIndicatorColor" format="color"/>
        <attr name="tabIndicatorHeight" format="dimension"/>
        <attr name="tabContentStart" format="dimension"/>

        <attr name="tabBackground" format="reference"/>

        <attr name="tabMode">
            <enum name="scrollable" value="0"/>
            <enum name="fixed" value="1"/>
        </attr>

        <!-- Standard gravity constant that a child supplies to its parent.
             Defines how the child view should be positioned, on both the X and Y axes,
             within its enclosing layout. -->
        <attr name="tabGravity">
            <enum name="fill" value="0"/>
            <enum name="center" value="1"/>
        </attr>

        <attr name="tabMinWidth" format="dimension"/>
        <attr name="tabMaxWidth" format="dimension"/>

        <attr name="tabTextAppearance" format="reference"/>
        <attr name="tabTextColor" format="color"/>
        <attr name="tabSelectedTextColor" format="color"/>

        <attr name="tabPaddingStart" format="dimension"/>
        <attr name="tabPaddingTop" format="dimension"/>
        <attr name="tabPaddingEnd" format="dimension"/>
        <attr name="tabPaddingBottom" format="dimension"/>
        <attr name="tabPadding" format="dimension"/>
    </declare-styleable>

如果实现不行,直接打开TabLayout的源码:

    public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mTabs = new ArrayList();
        this.mTabMaxWidth = 2147483647;
        this.setHorizontalScrollBarEnabled(false);
        this.setFillViewport(true);
        this.mTabStrip = new TabLayout.SlidingTabStrip(context);
        this.addView(this.mTabStrip, -2, -1);
        TypedArray a = context.obtainStyledAttributes(attrs, styleable.TabLayout, defStyleAttr, style.Widget_Design_TabLayout);
        this.mTabStrip.setSelectedIndicatorHeight(a.getDimensionPixelSize(styleable.TabLayout_tabIndicatorHeight, 0));
        this.mTabStrip.setSelectedIndicatorColor(a.getColor(styleable.TabLayout_tabIndicatorColor, 0));
        this.mTabTextAppearance = a.getResourceId(styleable.TabLayout_tabTextAppearance, style.TextAppearance_Design_Tab);
        this.mTabPaddingStart = this.mTabPaddingTop = this.mTabPaddingEnd = this.mTabPaddingBottom = a.getDimensionPixelSize(styleable.TabLayout_tabPadding, 0);
        this.mTabPaddingStart = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingStart, this.mTabPaddingStart);
        this.mTabPaddingTop = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingTop, this.mTabPaddingTop);
        this.mTabPaddingEnd = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingEnd, this.mTabPaddingEnd);
        this.mTabPaddingBottom = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingBottom, this.mTabPaddingBottom);
        this.mTabTextColors = this.loadTextColorFromTextAppearance(this.mTabTextAppearance);
        if(a.hasValue(styleable.TabLayout_tabTextColor)) {
            this.mTabTextColors = a.getColorStateList(styleable.TabLayout_tabTextColor);
        }

        if(a.hasValue(styleable.TabLayout_tabSelectedTextColor)) {
            int selected = a.getColor(styleable.TabLayout_tabSelectedTextColor, 0);
            this.mTabTextColors = createColorStateList(this.mTabTextColors.getDefaultColor(), selected);
        }

        this.mTabMinWidth = a.getDimensionPixelSize(styleable.TabLayout_tabMinWidth, 0);
        this.mRequestedTabMaxWidth = a.getDimensionPixelSize(styleable.TabLayout_tabMaxWidth, 0);
        this.mTabBackgroundResId = a.getResourceId(styleable.TabLayout_tabBackground, 0);
        this.mContentInsetStart = a.getDimensionPixelSize(styleable.TabLayout_tabContentStart, 0);
        this.mMode = a.getInt(styleable.TabLayout_tabMode, 1);
        this.mTabGravity = a.getInt(styleable.TabLayout_tabGravity, 0);
        a.recycle();
        this.applyModeAndGravity();
    }

反正都是Easy的英文,慢慢看,不难,真的!


TabLayout布局

<?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">

    <!--
        文字未选中的颜色 app:tabTextColor
        文字选中的颜色 app:tabSelectedTextColor
        指示器的颜色 app:tabIndicatorColor
     -->
    <android.support.design.widget.TabLayout
        android:id="@+id/tab_layout_tl"
        app:tabBackground="@color/tabLayoutBackground"
        app:tabTextColor="@color/black"
        app:tabSelectedTextColor="@color/red"
        app:tabIndicatorColor="@color/yellow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager_vp"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

正常版TabLayout FragmentPagerAdapter

public class NormalTabLayoutFragmentAdapter extends FragmentPagerAdapter {

    private String[] tabTitles;
    private Fragment[] fragments;

    public NormalTabLayoutFragmentAdapter(FragmentManager fm, Fragment[] fragments, String[] tabTitles) {
        super(fm);
        this.fragments = fragments;
        this.tabTitles = tabTitles;
    }

    /**
     * Return the Fragment associated with a specified position.
     *
     * @param position
     */
    @Override
    public Fragment getItem(int position) {
        return this.fragments[position];
    }

    /**
     * Return the number of views available.
     */
    @Override
    public int getCount() {
        return this.fragments.length;
    }

    /**
     * This method may be called by the ViewPager to obtain a title string
     * to describe the specified page. This method may return null
     * indicating no title for this page. The default implementation returns
     * null.
     *
     * @param position The position of the title requested
     * @return A title for the requested page
     */
    @Override
    public CharSequence getPageTitle(int position) {
        return this.tabTitles[position];
    }

}

正常版TabLayout Activity

public class NormalTabLayoutActivity extends AppCompatActivity {

    private TabLayout tabLayout;
    private ViewPager viewPager;

    private NormalTabLayoutFragmentAdapter fragmentAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.tablayout_normal_activity);

        this.viewPager = (ViewPager) this.findViewById(R.id.view_pager_vp);
        this.tabLayout = (TabLayout) this.findViewById(R.id.tab_layout_tl);
        this.initData();
    }

    private void initData() {
        String[] tabTitles = {"一次元", "二次元", "三次元", "四次元"};
        Fragment[] fragments = {
                TabLayoutFirstFragment.getInstance(),
                TabLayoutSecondFragment.getInstance(),
                TabLayoutThirdFragment.getInstance(),
                TabLayoutFourthFragment.getInstance()
        };
        this.fragmentAdapter = new NormalTabLayoutFragmentAdapter(this.getSupportFragmentManager(), fragments, tabTitles);
        this.viewPager.setAdapter(this.fragmentAdapter);
        this.tabLayout.setupWithViewPager(this.viewPager);
    }

}

正常版TabLayout 效果图

NormalTabLayout


正常版TabLayout 总结

正常版呢,纯String的title,说实话,是无与伦比的选择,确实要比第三方库要好。

但是有想过吗????

带icon会怎样?那么来看不正常版!一路解析到底


ImageSpan版TabLayout FragmentPagerAdapter

public class ImageSpanTabLayoutFragmentAdapter extends FragmentPagerAdapter {

    private Context context;
    private int[] icons;
    private String[] tabTitles;
    private Fragment[] fragments;

    public ImageSpanTabLayoutFragmentAdapter(Context context, FragmentManager fm, Fragment[] fragments, String[] tabTitles, int[] icons) {
        super(fm);
        this.context = context;
        this.icons = icons;
        this.fragments = fragments;
        this.tabTitles = tabTitles;
    }

    /**
     * Return the Fragment associated with a specified position.
     *
     * @param position
     */
    @Override
    public Fragment getItem(int position) {
        return this.fragments[position];
    }

    /**
     * Return the number of views available.
     */
    @Override
    public int getCount() {
        return this.fragments.length;
    }

    /**
     * This method may be called by the ViewPager to obtain a title string
     * to describe the specified page. This method may return null
     * indicating no title for this page. The default implementation returns
     * null.
     *
     * @param position The position of the title requested
     * @return A title for the requested page
     */
    @Override
    public CharSequence getPageTitle(int position) {
        Drawable drawable;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            drawable = this.context.getResources().getDrawable(this.icons[position], null);
        } else {
            drawable = this.context.getResources().getDrawable(this.icons[position]);
        }
        if (drawable == null) return "";
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        // 这里多设置5个空格
        SpannableString spannableString = new SpannableString("     " + this.tabTitles[position]);
        ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
        //这里图片的开始和结束设置为0-3,根据上述的5个空格减去3个,然后有2个空格之间距离
        spannableString.setSpan(imageSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        return spannableString;
    }

}

getPageTitle里,实例了对应的Drawable,然后通过Drawable再实例ImageSpan,最后放入到SpannableString里。在实例SpannableString的时候,多加了几个空格,作为paddingLeft的作用。


ImageSpan版TabLayout Activity

public class ImageSpanTabLayoutActivity extends AppCompatActivity {

    private TabLayout tabLayout;
    private ViewPager viewPager;

    private ImageSpanTabLayoutFragmentAdapter fragmentAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.tablayout_image_span_activity);

        this.viewPager = (ViewPager) this.findViewById(R.id.view_pager_vp);
        this.tabLayout = (TabLayout) this.findViewById(R.id.tab_layout_tl);
        this.initData();
    }

    private void initData() {
        int[] icons = {R.mipmap.icon_msg_unread, R.mipmap.icon_remark, R.mipmap.icon_time, R.mipmap.icon_feedback};
        String[] tabTitles = {"一次元", "二次元", "三次元", "四次元"};
        Fragment[] fragments = {
                TabLayoutFirstFragment.getInstance(),
                TabLayoutSecondFragment.getInstance(),
                TabLayoutThirdFragment.getInstance(),
                TabLayoutFourthFragment.getInstance()
        };
        this.fragmentAdapter = new ImageSpanTabLayoutFragmentAdapter(this, this.getSupportFragmentManager(), fragments, tabTitles, icons);
        this.viewPager.setAdapter(this.fragmentAdapter);
        this.tabLayout.setupWithViewPager(this.viewPager);
    }

}

ImageSpan版TabLayout 效果图

ImageSpanTabLayout


ImageSpan版TabLayout 总结

细心的同学都能发现,图片是不对齐的。

其实这就是这种方法实现的坑处。

哎,这涉及到ImageSpan实例化时的

ImageSpan构造方法

    /**
     * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
     * {@link DynamicDrawableSpan#ALIGN_BASELINE}.
     */
    public ImageSpan(Drawable d, int verticalAlignment) {
        super(verticalAlignment);
        mDrawable = d;
    }

int verticalAlignment涉及到DynamicDrawableSpan里的两个常量

    /**
     * A constant indicating that the bottom of this span should be aligned
     * with the bottom of the surrounding text, i.e., at the same level as the
     * lowest descender in the text.
     */
    public static final int ALIGN_BOTTOM = 0;

    /**
     * A constant indicating that the bottom of this span should be aligned
     * with the baseline of the surrounding text.
     */
    public static final int ALIGN_BASELINE = 1;

两种常量设置后,要么偏高要么偏低。总之,有居中强迫症的可以Over了。


SetIcon版TabLayout FragmentPagerAdapter

public class SetIconTabLayoutFragmentAdapter extends FragmentPagerAdapter {

    private Context context;
    private int[] icons;
    private String[] tabTitles;
    private Fragment[] fragments;

    public SetIconTabLayoutFragmentAdapter(Context context, FragmentManager fm, Fragment[] fragments, String[] tabTitles, int[] icons) {
        super(fm);
        this.context = context;
        this.icons = icons;
        this.fragments = fragments;
        this.tabTitles = tabTitles;
    }

    /**
     * Return the Fragment associated with a specified position.
     *
     * @param position
     */
    @Override
    public Fragment getItem(int position) {
        return this.fragments[position];
    }

    /**
     * Return the number of views available.
     */
    @Override
    public int getCount() {
        return this.fragments.length;
    }

    /**
     * This method may be called by the ViewPager to obtain a title string
     * to describe the specified page. This method may return null
     * indicating no title for this page. The default implementation returns
     * null.
     *
     * @param position The position of the title requested
     * @return A title for the requested page
     */
    @Override
    public CharSequence getPageTitle(int position) {
        // 多返回三个空格,作为padding的作用,挤开图片
        return "  "+this.tabTitles[position];
    }

}

getPageTitle里面也多比正常版多了几个空格,作为paddingLeft的作用。


SetIcon版TabLayout Activity

public class SetIconTabLayoutActivity extends AppCompatActivity {

    private TabLayout tabLayout;
    private ViewPager viewPager;

    private SetIconTabLayoutFragmentAdapter fragmentAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.tablayout_set_icon_activity);

        this.viewPager = (ViewPager) this.findViewById(R.id.view_pager_vp);
        this.tabLayout = (TabLayout) this.findViewById(R.id.tab_layout_tl);
        this.initData();
    }

    private void initData() {
        int[] icons = {R.mipmap.icon_msg_unread, R.mipmap.icon_remark, R.mipmap.icon_time, R.mipmap.icon_feedback};
        String[] tabTitles = {"一次元", "二次元", "三次元", "四次元"};
        Fragment[] fragments = {
                TabLayoutFirstFragment.getInstance(),
                TabLayoutSecondFragment.getInstance(),
                TabLayoutThirdFragment.getInstance(),
                TabLayoutFourthFragment.getInstance()
        };
        this.fragmentAdapter = new SetIconTabLayoutFragmentAdapter(this, this.getSupportFragmentManager(), fragments, tabTitles, icons);
        this.viewPager.setAdapter(this.fragmentAdapter);
        this.tabLayout.setupWithViewPager(this.viewPager);
        for (int i = 0; i < tabLayout.getTabCount(); i++) {
            TabLayout.Tab tab = tabLayout.getTabAt(i);
            if (tab == null) continue;
            tab.setIcon(icons[i]);
        }
    }

}

Google官方的TabLayout封装是很有意思的,每个标签页就是一个TabView,TabView的逐个生成是依赖于TabLayout.Tab这个类的所有数据,有兴趣的可以看看,解耦了数据与View之间的过度依赖。所以我们可以通过TabLayout.Tab拿到数据,并改变数据


SetIcon版TabLayout 效果图

SetIconTabLayout


SetIcon版TabLayout 总结

我个人认为,通过设置TabLayout.Tab.icon去实现 icon + text 的标签页是 TabLayout最合理的办法。只需要偏历每个TabLayout.Tab,设置icon就好了。

主要,还是居中对齐的。


CustomView版TabLayout 介绍

如果觉得 纯text 或者 icon + text 无法满足要求,那么可以自定义View作为标签页的标签

TabLayout.Tab是提供setCustomView(@Nullable View view)这个方法的。


CustomView版TabLayout FragmentPagerAdapter

public class CustomViewTabLayoutFragmentAdapter extends FragmentPagerAdapter {

    private Fragment[] fragments;

    public CustomViewTabLayoutFragmentAdapter(FragmentManager fm, Fragment[] fragments) {
        super(fm);
        this.fragments = fragments;
    }

    /**
     * Return the Fragment associated with a specified position.
     *
     * @param position
     */
    @Override
    public Fragment getItem(int position) {
        return this.fragments[position];
    }

    /**
     * Return the number of views available.
     */
    @Override
    public int getCount() {
        return this.fragments.length;
    }

}

CustomView版TabLayout的FragmentPagerAdapter连getPageTitle方法也不用覆写。


CustomView版TabLayout TabItem布局

item_icon_tab_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/tab_layout_title_left_iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tab_layout_title_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/color_icon_tab_layout"
        android:layout_marginRight="5dp"
        android:layout_marginEnd="5dp"
        android:layout_marginStart="5dp"
        android:layout_marginLeft="5dp" />

    <ImageView
        android:id="@+id/tab_layout_title_right_iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />


</LinearLayout>

自定义View的话,TextView的颜色还要自己设置 T T。

color_icon_tab_layout.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true" android:color="#ffFF4081" />
    <item android:state_focused="true" android:color="#ffFF4081" />
    <item android:state_pressed="true" android:color="#ffFF4081" />

    <item android:color="#FF000000" />
</selector>

CustomView版TabLayout Activity

public class CustomViewTabLayoutActivity extends AppCompatActivity {

    private TabLayout tabLayout;
    private ViewPager viewPager;

    private CustomViewTabLayoutFragmentAdapter fragmentAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.tablayout_image_span_activity);

        this.viewPager = (ViewPager) this.findViewById(R.id.view_pager_vp);
        this.tabLayout = (TabLayout) this.findViewById(R.id.tab_layout_tl);
        this.initData();
    }

    private void initData() {
        int[] icons = {R.mipmap.icon_clean, R.mipmap.icon_remark, R.mipmap.icon_time, R.mipmap.icon_feedback};
        String[] tabTitles = {"一次元", "二次元", "三次元", "四次元"};
        Fragment[] fragments = {
                TabLayoutFirstFragment.getInstance(),
                TabLayoutSecondFragment.getInstance(),
                TabLayoutThirdFragment.getInstance(),
                TabLayoutFourthFragment.getInstance()
        };
        this.fragmentAdapter = new CustomViewTabLayoutFragmentAdapter(this.getSupportFragmentManager(), fragments);
        this.viewPager.setAdapter(this.fragmentAdapter);
        this.tabLayout.setupWithViewPager(this.viewPager);
        for (int i = 0; i < tabLayout.getTabCount(); i++) {
            View view = LayoutInflater.from(this).inflate(R.layout.item_icon_tab_layout, null);
            TextView tabTV = (TextView) view.findViewById(R.id.tab_layout_title_tv);
            ImageView tabLeftIV = (ImageView) view.findViewById(R.id.tab_layout_title_left_iv);
            ImageView tabRightIV = (ImageView) view.findViewById(R.id.tab_layout_title_right_iv);
            tabTV.setText(tabTitles[i]);
            tabLeftIV.setImageResource(icons[i]);
            tabRightIV.setImageResource(icons[i]);
            TabLayout.Tab tab = tabLayout.getTabAt(i);
            if (tab == null) continue;
            tab.setCustomView(view);
        }
    }

}

CustomView版TabLayout 效果图

这样的话,一开始没滑动的时候,没触发事件是没有颜色,是没颜色。如果要改进的话,肯定是在事件出发后,设置当前选中标签为选中颜色,同时,还要设置其他没选中的标签为没选中的颜色,这就有点恶心了。

刚进Activity,什么都没做,第一个是没颜色的

CustomViewTabLayout_1

滑动后开始有颜色

CustomViewTabLayout_2


CustomView版TabLayout 总结

CustomView版TabLayout,建议还是不要使用了

可以使用EasySlidingTabs


全部版本总结

1.如果是纯Text,可以选择正常版。
2.如果是 icon + text ,可以选择SetIcon版。
3.再复杂版本,请选择EasySlidingTabs

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值