新的需求需要使用 tablayout 这里看一下源码,熟悉一下 tablayout 的使用
首先看一下内部类的情况
1、AdapterChangeListener
private class AdapterChangeListener implements OnAdapterChangeListener {
private boolean autoRefresh;
AdapterChangeListener() {}
public void onAdapterChanged(@NonNull ViewPager viewPager, @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter) {
if (TabLayout.this.viewPager == viewPager) {
TabLayout.this.setPagerAdapter(newAdapter, this.autoRefresh);
}
}
void setAutoRefresh(boolean autoRefresh) {
this.autoRefresh = autoRefresh;
}
}
2、PagerAdapterObserver
private class PagerAdapterObserver extends DataSetObserver {
PagerAdapterObserver() {}
public void onChanged() {
TabLayout.this.populateFromPagerAdapter();
}
public void onInvalidated() {
TabLayout.this.populateFromPagerAdapter();
}
}
3、ViewPagerOnTabSelectedListener
public static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener {
private final ViewPager viewPager;
public ViewPagerOnTabSelectedListener(ViewPager viewPager) {
this.viewPager = viewPager;
}
public void onTabSelected(TabLayout.Tab tab) {
this.viewPager.setCurrentItem(tab.getPosition());
}
public void onTabUnselected(TabLayout.Tab tab) {
}
public void onTabReselected(TabLayout.Tab tab) {
}
}
4、TabLayoutOnPageChangeListener
public static class TabLayoutOnPageChangeListener implements OnPageChangeListener {
private final WeakReference<TabLayout> tabLayoutRef;
private int previousScrollState;
private int scrollState;
public TabLayoutOnPageChangeListener(TabLayout tabLayout) {
this.tabLayoutRef = new WeakReference(tabLayout);
}
public void onPageScrollStateChanged(int state) {
this.previousScrollState = this.scrollState;
this.scrollState = state;
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
TabLayout tabLayout = (TabLayout)this.tabLayoutRef.get();
if (tabLayout != null) {
boolean updateText = this.scrollState != 2 || this.previousScrollState == 1;
boolean updateIndicator = this.scrollState != 2 || this.previousScrollState != 0;
tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
}
}
public void onPageSelected(int position) {
TabLayout tabLayout = (TabLayout)this.tabLayoutRef.get();
if (tabLayout != null && tabLayout.getSelectedTabPosition() != position && position < tabLayout.getTabCount()) {
boolean updateIndicator = this.scrollState == 0 || this.scrollState == 2 && this.previousScrollState == 0;
tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator);
}
}
void reset() {
this.previousScrollState = this.scrollState = 0;
}
}
5、SlidingTabIndicator
6、TabView
7、Tab
(代码太长就不贴了)
按照常规使用流程看一下
首先是 tablayout 的初始化
public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.tabs = new ArrayList();
this.tabViewContentBounds = new RectF();
this.tabMaxWidth = 2147483647;
this.selectedListeners = new ArrayList();
this.tabViewPool = new SimplePool(12);
this.setHorizontalScrollBarEnabled(false);
this.slidingTabIndicator = new TabLayout.SlidingTabIndicator(context);
super.addView(this.slidingTabIndicator, 0, new LayoutParams(-2, -1));
TypedArray a = ThemeEnforcement.obtainStyledAttributes(context, attrs, styleable.TabLayout, defStyleAttr, style.Widget_Design_TabLayout, new int[]{styleable.TabLayout_tabTextAppearance});
this.slidingTabIndicator.setSelectedIndicatorHeight(a.getDimensionPixelSize(styleable.TabLayout_tabIndicatorHeight, -1));
this.slidingTabIndicator.setSelectedIndicatorColor(a.getColor(styleable.TabLayout_tabIndicatorColor, 0));
this.setSelectedTabIndicator(MaterialResources.getDrawable(context, a, styleable.TabLayout_tabIndicator));
this.setSelectedTabIndicatorGravity(a.getInt(styleable.TabLayout_tabIndicatorGravity, 0));
this.setTabIndicatorFullWidth(a.getBoolean(styleable.TabLayout_tabIndicatorFullWidth, true));
this.tabPaddingStart = this.tabPaddingTop = this.tabPaddingEnd = this.tabPaddingBottom = a.getDimensionPixelSize(styleable.TabLayout_tabPadding, 0);
this.tabPaddingStart = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingStart, this.tabPaddingStart);
this.tabPaddingTop = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingTop, this.tabPaddingTop);
this.tabPaddingEnd = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingEnd, this.tabPaddingEnd);
this.tabPaddingBottom = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingBottom, this.tabPaddingBottom);
this.tabTextAppearance = a.getResourceId(styleable.TabLayout_tabTextAppearance, style.TextAppearance_Design_Tab);
TypedArray ta = context.obtainStyledAttributes(this.tabTextAppearance, android.support.v7.appcompat.R.styleable.TextAppearance);
try {
this.tabTextSize = (float)ta.getDimensionPixelSize(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, 0);
this.tabTextColors = MaterialResources.getColorStateList(context, ta, android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor);
} finally {
ta.recycle();
}
if (a.hasValue(styleable.TabLayout_tabTextColor)) {
this.tabTextColors = MaterialResources.getColorStateList(context, a, styleable.TabLayout_tabTextColor);
}
if (a.hasValue(styleable.TabLayout_tabSelectedTextColor)) {
int selected = a.getColor(styleable.TabLayout_tabSelectedTextColor, 0);
this.tabTextColors = createColorStateList(this.tabTextColors.getDefaultColor(), selected);
}
this.tabIconTint = MaterialResources.getColorStateList(context, a, styleable.TabLayout_tabIconTint);
this.tabIconTintMode = ViewUtils.parseTintMode(a.getInt(styleable.TabLayout_tabIconTintMode, -1), (android.graphics.PorterDuff.Mode)null);
this.tabRippleColorStateList = MaterialResources.getColorStateList(context, a, styleable.TabLayout_tabRippleColor);
this.tabIndicatorAnimationDuration = a.getInt(styleable.TabLayout_tabIndicatorAnimationDuration, 300);
this.requestedTabMinWidth = a.getDimensionPixelSize(styleable.TabLayout_tabMinWidth, -1);
this.requestedTabMaxWidth = a.getDimensionPixelSize(styleable.TabLayout_tabMaxWidth, -1);
this.tabBackgroundResId = a.getResourceId(styleable.TabLayout_tabBackground, 0);
this.contentInsetStart = a.getDimensionPixelSize(styleable.TabLayout_tabContentStart, 0);
this.mode = a.getInt(styleable.TabLayout_tabMode, 1);
this.tabGravity = a.getInt(styleable.TabLayout_tabGravity, 0);
this.inlineLabel = a.getBoolean(styleable.TabLayout_tabInlineLabel, false);
this.unboundedRipple = a.getBoolean(styleable.TabLayout_tabUnboundedRipple, false);
a.recycle();
Resources res = this.getResources();
this.tabTextMultiLineSize = (float)res.getDimensionPixelSize(dimen.design_tab_text_size_2line);
this.scrollableTabMinWidth = res.getDimensionPixelSize(dimen.design_tab_scrollable_min_width);
this.applyModeAndGravity();
}
先看初始化的第一部分
this.tabs = new ArrayList();
this.tabViewContentBounds = new RectF();
this.tabMaxWidth = 2147483647;
this.selectedListeners = new ArrayList();
this.tabViewPool = new SimplePool(12);
this.setHorizontalScrollBarEnabled(false);
this.slidingTabIndicator = new TabLayout.SlidingTabIndicator(context);
super.addView(this.slidingTabIndicator, 0, new LayoutParams(-2, -1));
创建了一个 tab 列表集合
创建了一个 tabView 的 RectF
设定了 tab 的最大宽度
创建监听集合
创建 tabview 对象池
创建一个 slidingTabIndicator 对象 并添加到当前布局中,new LayoutParams(WRAP_CONTENT,MATCH_PARENT)
/**
* Special value for the height or width requested by a View.
* MATCH_PARENT means that the view wants to be as big as its parent,
* minus the parent's padding, if any. Introduced in API Level 8.
*/
public static final int MATCH_PARENT = -1;
/**
* Special value for the height or width requested by a View.
* WRAP_CONTENT means that the view wants to be just large enough to fit
* its own internal content, taking its own padding into account.
*/
public static final int WRAP_CONTENT = -2;
初始化的第二部分
TypedArray a = ThemeEnforcement.obtainStyledAttributes(context, attrs, styleable.TabLayout, defStyleAttr, style.Widget_Design_TabLayout, new int[]{styleable.TabLayout_tabTextAppearance});
this.slidingTabIndicator.setSelectedIndicatorHeight(a.getDimensionPixelSize(styleable.TabLayout_tabIndicatorHeight, -1));
this.slidingTabIndicator.setSelectedIndicatorColor(a.getColor(styleable.TabLayout_tabIndicatorColor, 0));
this.setSelectedTabIndicator(MaterialResources.getDrawable(context, a, styleable.TabLayout_tabIndicator));
this.setSelectedTabIndicatorGravity(a.getInt(styleable.TabLayout_tabIndicatorGravity, 0));
this.setTabIndicatorFullWidth(a.getBoolean(styleable.TabLayout_tabIndicatorFullWidth, true));
this.tabPaddingStart = this.tabPaddingTop = this.tabPaddingEnd = this.tabPaddingBottom = a.getDimensionPixelSize(styleable.TabLayout_tabPadding, 0);
this.tabPaddingStart = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingStart, this.tabPaddingStart);
this.tabPaddingTop = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingTop, this.tabPaddingTop);
this.tabPaddingEnd = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingEnd, this.tabPaddingEnd);
this.tabPaddingBottom = a.getDimensionPixelSize(styleable.TabLayout_tabPaddingBottom, this.tabPaddingBottom);
this.tabTextAppearance = a.getResourceId(styleable.TabLayout_tabTextAppearance, style.TextAppearance_Design_Tab);
这里设置了一些样式,具体对应 view 的那个部分一会再看
第三部分
TypedArray ta = context.obtainStyledAttributes(this.tabTextAppearance, android.support.v7.appcompat.R.styleable.TextAppearance);
try {
this.tabTextSize = (float)ta.getDimensionPixelSize(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, 0);
this.tabTextColors = MaterialResources.getColorStateList(context, ta, android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor);
} finally {
ta.recycle();
}
if (a.hasValue(styleable.TabLayout_tabTextColor)) {
this.tabTextColors = MaterialResources.getColorStateList(context, a, styleable.TabLayout_tabTextColor);
}
if (a.hasValue(styleable.TabLayout_tabSelectedTextColor)) {
int selected = a.getColor(styleable.TabLayout_tabSelectedTextColor, 0);
this.tabTextColors = createColorStateList(this.tabTextColors.getDefaultColor(), selected);
}
this.tabIconTint = MaterialResources.getColorStateList(context, a, styleable.TabLayout_tabIconTint);
this.tabIconTintMode = ViewUtils.parseTintMode(a.getInt(styleable.TabLayout_tabIconTintMode, -1), (android.graphics.PorterDuff.Mode)null);
this.tabRippleColorStateList = MaterialResources.getColorStateList(context, a, styleable.TabLayout_tabRippleColor);
this.tabIndicatorAnimationDuration = a.getInt(styleable.TabLayout_tabIndicatorAnimationDuration, 300);
this.requestedTabMinWidth = a.getDimensionPixelSize(styleable.TabLayout_tabMinWidth, -1);
this.requestedTabMaxWidth = a.getDimensionPixelSize(styleable.TabLayout_tabMaxWidth, -1);
this.tabBackgroundResId = a.getResourceId(styleable.TabLayout_tabBackground, 0);
this.contentInsetStart = a.getDimensionPixelSize(styleable.TabLayout_tabContentStart, 0);
this.mode = a.getInt(styleable.TabLayout_tabMode, 1);
this.tabGravity = a.getInt(styleable.TabLayout_tabGravity, 0);
this.inlineLabel = a.getBoolean(styleable.TabLayout_tabInlineLabel, false);
this.unboundedRipple = a.getBoolean(styleable.TabLayout_tabUnboundedRipple, false);
这里设置了一些内容的特殊属性 比如 tab 文字的大小颜色 icon 的样式颜色 等交互性质的属性
然后我们看一般用法
TabLayout.addTab()
public void addTab(@NonNull TabLayout.Tab tab) {
this.addTab(tab, this.tabs.isEmpty());
}
public void addTab(@NonNull TabLayout.Tab tab, int position) {
this.addTab(tab, position, this.tabs.isEmpty());
}
public void addTab(@NonNull TabLayout.Tab tab, boolean setSelected) {
this.addTab(tab, this.tabs.size(), setSelected);
}
public void addTab(@NonNull TabLayout.Tab tab, int position, boolean setSelected) {
if (tab.parent != this) {
throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
} else {
this.configureTab(tab, position);
this.addTabView(tab);
if (setSelected) {
tab.select();
}
}
}
最终的执行方法为 addTab(tab,position,setSelected)
第一步 configureTab(tab,position)
private void configureTab(TabLayout.Tab tab, int position) {
tab.setPosition(position);
this.tabs.add(position, tab);
int count = this.tabs.size();
for(int i = position + 1; i < count; ++i) {
((TabLayout.Tab)this.tabs.get(i)).setPosition(i);
}
}
在列表tabs 中的指定位置插入行的 tab
然后刷新所有 tabs 中 tab 对象的 position 值
第二步 addTabView(tab)
private void addTabView(TabLayout.Tab tab) {
TabLayout.TabView tabView = tab.view;
this.slidingTabIndicator.addView(tabView, tab.getPosition(), this.createLayoutParamsForTabs());
}
//.......
//tab 里面的 属性
public static final int INVALID_POSITION = -1;
private Object tag;
private Drawable icon;
private CharSequence text;
private CharSequence contentDesc;
private int position = -1;
private View customView;
public TabLayout parent;
public TabLayout.TabView view;
addTabView 操作是将 Tab 对象中的 view 添加到了最开始看到的 slidingTabIndicator 线性布局中
再看一下params
private android.widget.LinearLayout.LayoutParams createLayoutParamsForTabs() {
android.widget.LinearLayout.LayoutParams lp = new android.widget.LinearLayout.LayoutParams(WRAP_CONTENT, MATCH_PARENT);
this.updateTabViewLayoutParams(lp);
return lp;
}
//根据当前 tablayout 设置的 gravity 类型来对 tabview 的 params 进行更新设置
private void updateTabViewLayoutParams(android.widget.LinearLayout.LayoutParams lp) {
if (this.mode == 1 && this.tabGravity == 0) {
lp.width = 0;
lp.weight = 1.0F;
} else {
lp.width = -2;
lp.weight = 0.0F;
}
}
到这完成了一个 tab 的添加,具体添加的 view 是什么样的呢?我们要看一下 这个Tab 和 TabView 了
我们添加 tab 时使用的是 Tablayout.newTab()
@NonNull
public TabLayout.Tab newTab() {
TabLayout.Tab tab = this.createTabFromPool();
tab.parent = this;
tab.view = this.createTabView(tab);
return tab;
}
protected TabLayout.Tab createTabFromPool() {
TabLayout.Tab tab = (TabLayout.Tab)tabPool.acquire();
if (tab == null) {
tab = new TabLayout.Tab();
}
return tab;
}
首先创建了一个 tab 对象 并设置这个 tab 的创建者 tablayout 为它的 parent
然后创建一个 TabView 作为 tab.view
private TabLayout.TabView createTabView(@NonNull TabLayout.Tab tab) {
TabLayout.TabView tabView = this.tabViewPool != null ? (TabLayout.TabView)this.tabViewPool.acquire() : null;
if (tabView == null) {
tabView = new TabLayout.TabView(this.getContext());
}
tabView.setTab(tab);
tabView.setFocusable(true);
tabView.setMinimumWidth(this.getTabMinWidth());
if (TextUtils.isEmpty(tab.contentDesc)) {
tabView.setContentDescription(tab.text);
} else {
tabView.setContentDescription(tab.contentDesc);
}
return tabView;
}
先检查对象池,然后绑定 tab 与 tabview
class TabView extends LinearLayout {
private TabLayout.Tab tab;
private TextView textView;
private ImageView iconView;
private View customView;
private TextView customTextView;
private ImageView customIconView;
@Nullable
private Drawable baseBackgroundDrawable;
private int defaultMaxLines = 2;
public TabView(Context context) {
super(context);
this.updateBackgroundDrawable(context);
ViewCompat.setPaddingRelative(this, TabLayout.this.tabPaddingStart, TabLayout.this.tabPaddingTop, TabLayout.this.tabPaddingEnd, TabLayout.this.tabPaddingBottom);
this.setGravity(17);
this.setOrientation(TabLayout.this.inlineLabel ? 0 : 1);
this.setClickable(true);
ViewCompat.setPointerIcon(this, PointerIconCompat.getSystemIcon(this.getContext(), 1002));
}
可以看到两套显示 view
1 textView iconView
2 customView customTextView customIconView
来看初始化过程
首先更新了 background
private void updateBackgroundDrawable(Context context) {
if (TabLayout.this.tabBackgroundResId != 0) {
this.baseBackgroundDrawable = AppCompatResources.getDrawable(context, TabLayout.this.tabBackgroundResId);
if (this.baseBackgroundDrawable != null && this.baseBackgroundDrawable.isStateful()) {
this.baseBackgroundDrawable.setState(this.getDrawableState());
}
} else {
this.baseBackgroundDrawable = null;
}
Drawable contentDrawable = new GradientDrawable();
((GradientDrawable)contentDrawable).setColor(0);
Object background;
if (TabLayout.this.tabRippleColorStateList != null) {
GradientDrawable maskDrawable = new GradientDrawable();
maskDrawable.setCornerRadius(1.0E-5F);
maskDrawable.setColor(-1);
ColorStateList rippleColor = RippleUtils.convertToRippleDrawableColor(TabLayout.this.tabRippleColorStateList);
if (VERSION.SDK_INT >= 21) {
background = new RippleDrawable(rippleColor, TabLayout.this.unboundedRipple ? null : contentDrawable, TabLayout.this.unboundedRipple ? null : maskDrawable);
} else {
Drawable rippleDrawable = DrawableCompat.wrap(maskDrawable);
DrawableCompat.setTintList(rippleDrawable, rippleColor);
background = new LayerDrawable(new Drawable[]{contentDrawable, rippleDrawable});
}
} else {
background = contentDrawable;
}
ViewCompat.setBackground(this, (Drawable)background);
TabLayout.this.invalidate();
}
drawable 资源来自 tablayout 的 tabBackgroundResId
然后是 contentDrawable 颜色为 0
点击反馈的 drawablelist
设置 TabView 的 background
请求重新 draw view
看看 onMeasure 方法
除了通常的计算过程 增加了对 textview 的处理过程
if (this.textView != null) {
float textSize = TabLayout.this.tabTextSize;
int maxLines = this.defaultMaxLines;
if (this.iconView != null && this.iconView.getVisibility() == 0) {
maxLines = 1;
} else if (this.textView != null && this.textView.getLineCount() > 1) {
textSize = TabLayout.this.tabTextMultiLineSize;
}
float curTextSize = this.textView.getTextSize();
int curLineCount = this.textView.getLineCount();
int curMaxLines = TextViewCompat.getMaxLines(this.textView);
if (textSize != curTextSize || curMaxLines >= 0 && maxLines != curMaxLines) {
boolean updateTextView = true;
if (TabLayout.this.mode == 1 && textSize > curTextSize && curLineCount == 1) {
Layout layout = this.textView.getLayout();
if (layout == null || this.approximateLineWidth(layout, 0, textSize) > (float)(this.getMeasuredWidth() - this.getPaddingLeft() - this.getPaddingRight())) {
updateTextView = false;
}
}
if (updateTextView) {
this.textView.setTextSize(0, textSize);
this.textView.setMaxLines(maxLines);
super.onMeasure(widthMeasureSpec, origHeightMeasureSpec);
}
}
主要是根据目前设置的 textSize 与 textview 的宽度情况来确定显示行数,如果行数不符合要求,需要对 textview 的 textsize 进行重设