TabLayout仍旧是移动端比较常用的一个控件,这里分析一下TabLayout,分别从下面几个方面进行解析:
先看下支持库指南。之前的TabLayout是在support中使用,新的引用全部放到老AndroidX中
使用老的库需要用
implementation 'com.android.support:design:28.0.0'
Android 支持库的最新版本是28.0.0,这是最后一个google发布的支持库版本,现在google已将所有support包下的库都迁移至androidx包下面,以后的更新都只会在androidx包中进行。具体对照表点击进入迁移说明。
这些变动是由于android的jetpack项目,意在帮助开发者快速实现应用开发,将一些常用的框架都整合进来了。
使用新的库需要引用:
implementation 'androidx.appcompat:appcompat:1.0.2'
TabLayout类的继承关系:
java.lang.Object | |||||
---|---|---|---|---|---|
↳ | android.view.View | ||||
↳ | android.view.ViewGroup | ||||
↳ | android.widget.FrameLayout | ||||
↳ | android.widget.HorizontalScrollView | ||||
↳ | com.google.android.material.tabs.TabLayout |
TabLayout继承自 HorizontalScrollView
TabLayout提供了用于显示选项卡的水平布局。要显示的选项卡的填充是通过TabLayout.Tab
实例完成的。可以通过创建标签 newTab()
。在此处,您可以分别通过setText(int)
和更改选项卡的标签或图标setIcon(int)
。要显示选项卡,需要通过一种addTab(Tab)
方法将其添加到布局中。例如:
TabLayout tabLayout = ...;
tabLayout.addTab(tabLayout.newTab()。setText(“ Tab 1”));
tabLayout.addTab(tabLayout.newTab()。setText(“ Tab 2”));
tabLayout.addTab(tabLayout.newTab()。setText(“ Tab 3”));
应该添加一个监听器,addOnTabSelectedListener(OnTabSelectedListener)
以在任何选项卡的选择状态更改时得到通知。
还可以通过使用将项目添加到布局中的TabLayout TabItem
。用法示例如下:
<com.google.android.material.tabs.TabLayout
android:layout_height =“ wrap_content”
android:layout_width =“ match_parent”>
<com.google.android.material.tabs.TabItem
android:text =“ @ string / tab_text” />
<com.google.android.material.tabs.TabItem
android:icon =“ @ drawable / ic_android” />
</com.google.android.material.tabs.TabLayout>
如果ViewPager
将此布局与一起使用,则可以调用setupWithViewPager(ViewPager)
将两者链接在一起。该版式将从PagerAdapter
的页面标题中自动填充。
此视图还支持用作ViewPager装饰的一部分,并且可以像这样在布局资源文件中直接添加到ViewPager:
<androidx.viewpager.widget.ViewPager
android:layout_width =“ match_parent”
android:layout_height =“ match_parent”>
<com.google.android.material.tabs.TabLayout
android:layout_width =“ match_parent”
android:layout_height =“ wrap_content”
android:layout_gravity =“ top” />
</androidx.viewpager.widget.ViewPager>
这里很多人都使用的都是design 28,主工程的gradle的配置根据不同情况改。
上面三种使用方法,我们使用新的库androidx看下使用的效果图:
这里列举一下主要使用到到属性,只列举几个,具体可看官方文档。
属性 | 值 | 说明 |
---|---|---|
tabMode | scrollable/fixed | tab是水平可滚动的还是固定宽,个数较少的时候可以使用fixed,如果标签超出了屏幕范围,设置为scrollable比较好 |
tabGravity | fill/center | tab的布局是布满空间还是居中 |
tabIndicatorHeight | (dp/pix) | 底部滑动线条的高度 |
tabMaxWidth | (dp/pix) | Tab的最大宽度 |
tabTextColor | 颜色值 | 默认样式未选中颜色 |
app:tabSelectedTextColor | 颜色值 | 选中颜色 |
//TabLayout的基本使用
for(int i=0;i<mTitles.length;i++){
TabLayout.Tab tab=mTabLayout.newTab();
tab.setTag(i);
tab.setText(mTitles[i]);
mTabLayout.addTab(tab);
}
属性设置
app:tabIndicatorHeight="0dp"
有时候想指示器的宽度小一些,可以参考文章Tablayout使用全解,一篇就够了 修改指示线长度(利用的反射,感觉不如自己基于源码封装一个,可以自定义长度)。
TabItem有个上下结构的默认布局,这里使用默认的
tabLayout1.addTab(tabLayout1.newTab().setText("Tab 4").setIcon(R.mipmap.ic_launcher));
设置Tab内部的子控件的Padding:
app:tabPadding="xxdp"
app:tabPaddingTop="xxdp"
app:tabPaddingStart="xxdp"
app:tabPaddingEnd="xxdp"
app:tabPaddingBottom="xxdp"
设置整个TabLayout的Padding:
app:paddingEnd="xxdp"
app:paddingStart="xxdp"
设置最大的tab宽度:
app:tabMaxWidth="xxdp"
设置最小的tab宽度:
app:tabMinWidth="xxdp"
TabLayout开始位置的偏移量:
app:tabContentStart="50dp"
如果你要设置默认选中第三项,你可以这样:
mTabLayout.getTabAt(2).select();
初始化进入的时候,监听事件的三个方法都不会执行
tabLayout1.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
//选中了tab的逻辑
Log.i(TAG, "======我选中了===="+tab.getTag());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
//未选中tab的逻辑
Log.i(TAG, "======我未被选中===="+tab.getTag());
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
//再次选中tab的逻辑
Log.i(TAG, "======我再次被选中===="+tab.getTag());
}
});
onTabReselected为已经选中的tab再次点击会走到。
mTabLayout.getTabAt(position).isSelected()
有时候要监听某个Tab的点击事件,可以参考TabLayout基本属性全解 或者 tablayout增加选择tab 的事件和重写tab点击事件
这里有两种方式添加TabItem的自定义布局,其一种方式是在TabItem的xml中定义
<com.google.android.material.tabs.TabItem
android:layout_width="match_parent"
android:layout_height="match_parent"
android:icon="@drawable/two"
android:layout="@layout/custom_indicator1"
android:text="娱乐" />
custom_indicator1.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
/>
<ImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</LinearLayout>
custom_indicator1.xml文件内容,值得注意的是这里的TextView的id必须是“@android:id/text1”,ImageView的id必须是“@android:id/icon”,原因来自于与TabLayout的源码中TabView的update方法。
这种方式只能事先确定有几个Tab的时候用到,当这个Tab个数需要动态的创建的时候不能使用此方法。
另外一种方式通过代码动态设置布局,布局的选中和未选中态的更新采用监听器动态修改的方式。
/**
* 设置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);
}
}
有一些写的比较好的文章分享了一些比较高级的功能。如,TabLayout的简单运用和若干问题的解决。
这篇中介绍了怎么加分割线,设置原有字体大小,自定义标签等。
在源码中可以看到再newTab中,customView的的创建。
@NonNull
public Tab newTab() {
Tab tab = sTabPool.acquire();
if (tab == null) {
tab = new Tab();
}
tab.mParent = this;
tab.mView = createTabView(tab);
if (mViewPagerTabEventListener != null) {
tab.setCustomView(mViewPagerTabEventListener.onCreateTab(tab.mView));
}
return tab;
}
最新的TabLayout源码可以通过下面的地址中看到,看到Google是由专门的material设计和工程团队负责此库。
https://github.com/material-components/material-components-android
先看下整体的类关系图
TabLayout继承HorizontalScrollView是一个横向滚动的ViewGroup,他包含一个子View(只能包含一个)SlidingTabStrip。
SlidingTabStrip为一个LinearLayout为TabLayout的内部类。所有的TabView都是它的子View.
TabView继承于LinearLayout,以Tab为数据源,来展示Tab的样式。最终用for循环被add进SlidingTabStrip
Tab是一个简单的View Model实体类,控制TabView的title, icon, custom layout id等属性。
TabItem继承于View. 用于在layout xml中来描述Tab. 需要注意的是,它不会add到SlidingTabStrip中去。它的作用是从xml中获取到text,icon,custom layout id等属性。TabLayout inflate到TabItem并获取属性到装配到Tab中,最终add到SlidingTabStrip中的还是TabView.
这里调newTab()
方法创建了一个tab对象,并且用对象池把创建的tab对象缓存起来。然后将TabItem
对象的属性都赋值给tab对象。在createTabView(Tab tab)
这个方法中,首先从TabView
池中获取TabView
对象,如果不存在,则实例化一个对象,并调用tabView.setTab(tab)
方法来进行了数据绑定。
private static final Pools.Pool<Tab> sTabPool = new Pools.SynchronizedPool<Tab>(16);
public Tab newTab() {
Tab tab = sTabPool.acquire();
if (tab == null) {
tab = new Tab();
}
tab.mParent = this;
tab.mView = createTabView(tab);
if (mViewPagerTabEventListener != null) {
tab.setCustomView(mViewPagerTabEventListener.onCreateTab(tab.mView));
}
return tab;
}
private TabView createTabView(@NonNull final Tab tab) {
TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null;
if (tabView == null) {
tabView = new TabView(getContext());
}
tabView.setTab(tab);
tabView.setFocusable(true);
tabView.setMinimumWidth(getTabMinWidth());
return tabView;
}
mTabStrip本身在HorizonScrollView中,所以直接通过scrollTo方法即可实现滚动的操作,这里只需要计算位置即可。
private int calculateScrollXForTab(int position, float positionOffset) {
if (mMode == MODE_SCROLLABLE) {
final View selectedChild = mTabStrip.getChildAt(position);
final View nextChild = position + 1 < mTabStrip.getChildCount()
? mTabStrip.getChildAt(position + 1)
: null;
final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
final int selectedLeft = selectedChild != null ? selectedChild.getLeft() : 0;
final int nextWidth = nextChild != null ? nextChild.getWidth() : 0;
// base scroll amount: places center of tab in center of parent
int scrollBase = selectedLeft + (selectedWidth / 2) - (getWidth() / 2);
// offset amount: fraction of the distance between centers of tabs
int scrollOffset = (int) ((selectedWidth + nextWidth) * 0.5f * positionOffset);
return (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR)
? scrollBase + scrollOffset
: scrollBase - scrollOffset;
}
return 0;
}
[1] 官方介绍
[2] MaterialDesign--(7)TabLayout的使用及其源码分析
[3] TabLayout基本属性全解
[6] MaterialDesign之对TabLayout的探索
[7] https://github.com/itgoyo/AndroidSource-Analysis/blob/master/chapter1/TabLayout%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.md
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。