Android UI进阶之旅8 Material Design之TabLayout

###TabLayout的基本使用

以前我们是用TabHost来实现Tab切换的效果。现在谷歌推荐使用TabLayout。

下面以TabLayout+ViewPager+Fragment为例,讲述TabLayout的基本使用。

布局文件如下面所示:

<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/tablayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="center"
        app:tabIndicatorColor="#4ce91c"
        app:tabMode="scrollable"
        app:tabSelectedTextColor="#4ce91c"
        app:tabTextColor="#ccc"
        app:tabIndicatorHeight="5dp"
        />

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

</LinearLayout>
复制代码

其中,需要关注的属性有:

app:tabIndicatorColor="@color/colorPrimary_pink"//指示器的颜色
app:tabTextColor="@color/colorPrimary_pink"//tab的文字颜色
app:tabSelectedTextColor="@color/colorPrimary_pinkDark"//选中的tab的文字颜色
app:tabMode="fixed"//scrollable:可滑动;fixed:不能滑动,平分tabLayout宽度
app:tabGravity="center"// fill:tab平均填充整个宽度;center:tab居中显示
复制代码

需要切换的Fragment,为了方便,我们重用一个Fragment:

public class NewsDetailFragment extends Fragment {
	
	@Override
	@Nullable
	public View onCreateView(LayoutInflater inflater,
			@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
		TextView tv = new TextView(getContext());
		Bundle bundle = getArguments();
		String title = bundle.getString("title");
		tv.setBackgroundColor(Color.rgb((int)(Math.random()*255), (int)(Math.random()*255), (int)(Math.random()*255)));
		tv.setText(title);
		return tv;
	}

}
复制代码

Activity的代码:

public class TabLayoutActivity extends AppCompatActivity {

    private TabLayout tabLayout;
    private String[] title = {
            "头条",
            "新闻",
            "娱乐",
            "体育",
            "科技",
            "美女",
            "财经",
            "汽车",
            "房子",
            "头条"
    };

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

        final ViewPager viewPager = (ViewPager) findViewById(R.id.vp);
        tabLayout = (TabLayout) findViewById(R.id.tablayout);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());


        //1.TabLayout和Viewpager关联
//        tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
//
//            @Override
//            public void onTabUnselected(TabLayout.Tab arg0) {
//
//            }
//
//            @Override
//            public void onTabSelected(TabLayout.Tab tab) {
//                // 被选中的时候回调
//                viewPager.setCurrentItem(tab.getPosition(), true);
//            }
//
//            @Override
//            public void onTabReselected(TabLayout.Tab tab) {
//
//            }
//        });
        //2.ViewPager滑动关联tabLayout
//        viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
        //设置tabLayout的标签来自于PagerAdapter
//        tabLayout.setTabsFromPagerAdapter(adapter);


        //设置tabLayout的标签来自于PagerAdapter
        tabLayout.setupWithViewPager(viewPager);

        viewPager.setAdapter(adapter);

        //设置Indicator的左右间距(Indicator的宽度)
        setIndicator(this, tabLayout, 15, 15);
    }

    class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager fm) {
            super(fm);
        }

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

        @Override
        public Fragment getItem(int position) {
            Fragment f = new NewsDetailFragment();
            Bundle bundle = new Bundle();
            bundle.putString("title", title[position]);
            f.setArguments(bundle);
            return f;
        }

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

    }

	//下面三个方法是设置Indicator

    public static void setIndicator(Context context, 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 ll_tab = null;
        try {
            ll_tab = (LinearLayout) tabStrip.get(tabs);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        int left = (int) (getDisplayMetrics(context).density * leftDip);
        int right = (int) (getDisplayMetrics(context).density * rightDip);

        for (int i = 0; i < ll_tab.getChildCount(); i++) {
            View child = ll_tab.getChildAt(i);
            child.setPadding(0, 0, 0, 0);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
            params.leftMargin = left;
            params.rightMargin = right;
            child.setLayoutParams(params);
            child.invalidate();
        }
    }

    public static DisplayMetrics getDisplayMetrics(Context context) {
        DisplayMetrics metric = new DisplayMetrics();
        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metric);
        return metric;
    }

    public static float getPXfromDP(float value, Context context) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
                context.getResources().getDisplayMetrics());
    }
}
复制代码

代码比较简单,需要注意的是,新提供的tabLayout.setupWithViewPager(viewPager);方法代替了注释中的3个方法了,其实内部做的事都是一样的。TabLayout默认没有提供修改Indicator宽度的函数,需要我们通过反射的方式去设置。

###用TabLayout实现底部导航(相对于传统的TabHost,它是可滑动的)

只需要三个步骤:

  1. 在布局中就把TabLayout放在布局底部
  2. 去掉底部的indicator,app:tabIndicatorHeight="0dp"
  3. 实现自己的效果,自定义的标签布局

代码如下:

for (int i = 0; i < tabLayout.getTabCount(); i++) {
    TabLayout.Tab tab = tabLayout.getTabAt(i);
    tab.setCustomView(view);
}
复制代码

###TabLayout源码分析

TabLayout是可以水平滑动的,因此继承了HorizontalScrollView

public class TabLayout extends HorizontalScrollView {

}
复制代码

TabLayout里面有那么多小tab,tab有分为是否填充,是否可滑动,那么测量的时候就会很复杂:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

	//推荐的高度		
    final int idealHeight = dpToPx(getDefaultHeight()) + getPaddingTop() + getPaddingBottom();
    switch (MeasureSpec.getMode(heightMeasureSpec)) {
        case MeasureSpec.AT_MOST:
			//最大值模式下,最终的高度是推荐的高度与所给的高度的最小值
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                    Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec)),
                    MeasureSpec.EXACTLY);
            break;
        case MeasureSpec.UNSPECIFIED:
			//没有指定的模式下,最终的高度就是推荐的高度
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY);
            break;
    }

    final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
    if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
        // If we don't have an unspecified width spec, use the given size to calculate
        // the max tab width
        mTabMaxWidth = mRequestedTabMaxWidth > 0
                ? mRequestedTabMaxWidth
                : specWidth - dpToPx(TAB_MIN_WIDTH_MARGIN);
    }

    // 父容器(TabLayout)测量自身
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    if (getChildCount() == 1) {
        // so we don't scroll
        final View child = getChildAt(0);
        boolean remeasure = false;

        switch (mMode) {
            case MODE_SCROLLABLE:
                // 如果Tab有多个,那么需要重新测量
                remeasure = child.getMeasuredWidth() < getMeasuredWidth();
                break;
            case MODE_FIXED:
                // 固定模式下,每一个Tab都是不可滑动的,只有一个的情况下需要重新测量
                remeasure = child.getMeasuredWidth() != getMeasuredWidth();
                break;
        }

        if (remeasure) {
            // 重新测量每一个子View(Tab)
            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop()
                    + getPaddingBottom(), child.getLayoutParams().height);
            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                    getMeasuredWidth(), MeasureSpec.EXACTLY);
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}
复制代码

Tab是如何添加进来:

public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
    if (tab.mParent != this) {
        throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
    }
    configureTab(tab, position);
    addTabView(tab);

    if (setSelected) {
        tab.select();
    }
}

private void addTabView(Tab tab) {
    final TabView tabView = tab.mView;
    mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs());
}
复制代码

可以看见最终是通过addView添加进来的,其中,每一个TabView又是一个LinearLayout:

class TabView extends LinearLayout implements OnLongClickListener {

    private Tab mTab;
    private TextView mTextView;
    private ImageView mIconView;

    private View mCustomView;
    private TextView mCustomTextView;
    private ImageView mCustomIconView;

	//...
}
复制代码

细心的你会发现TabView本来就支持Icon的设置,并且提供我们添加自定义Tab的View的接口。

mTabStrip也是一个LinearLayout:

private class SlidingTabStrip extends LinearLayout {
}
复制代码

mTabStrip是在TabLayout构造的时候创建的,作为根布局:

public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    ThemeUtils.checkAppCompatTheme(context);

    // Disable the Scroll Bar
    setHorizontalScrollBarEnabled(false);

    // Add the TabStrip
    mTabStrip = new SlidingTabStrip(context);
    super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(
            LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));

	// ...
}
复制代码

如果觉得我的文字对你有所帮助的话,欢迎关注我的公众号:

我的群欢迎大家进来探讨各种技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值