/** * Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab * labels and a larger number of tabs. They are best used for browsing contexts in touch * interfaces when users don’t need to directly compare the tab labels. * * @see #setTabMode(int) * @see #getTabMode() */ public static final int MODE_SCROLLABLE = 0;
/** * Fixed tabs display all tabs concurrently and are best used with content that benefits from * quick pivots between tabs. The maximum number of tabs is limited by the view’s width. * Fixed tabs have equal width, based on the widest tab label. * * @see #setTabMode(int) * @see #getTabMode() */ public static final int MODE_FIXED = 1;
/** * Gravity used to fill the {@link TabLayout} as much as possible. This option only takes effect * when used with {@link #MODE_FIXED}. * * @see #setTabGravity(int) * @see #getTabGravity() */ public static final int GRAVITY_FILL = 0;
/** * Gravity used to lay out the tabs in the center of the {@link TabLayout}. * * @see #setTabGravity(int) * @see #getTabGravity() */ public static final int GRAVITY_CENTER = 1;
/** * @hide */ @RestrictTo(LIBRARY_GROUP) @IntDef(flag = true, value = {GRAVITY_FILL, GRAVITY_CENTER}) @Retention(RetentionPolicy.SOURCE) public @interface TabGravity {}
居中模式
创建 Tab
使用代码创建 Tab
1 2 3 4 5 6 7 8 9
public Tab newTab() { Tab tab = sTabPool.acquire(); if (tab == null) { tab = new Tab(); } tab.mParent = this; tab.mView = createTabView(tab); return tab; }
Tab 还使用了 Pool,还是挺细心的
1
private static final Pools.Pool<Tab> sTabPool = new Pools.SynchronizedPool<>(16);
可滑动的指示条形图
自定义 ViewGroup
1
private class SlidingTabStrip extends LinearLayout
@Override protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) { // HorizontalScrollView will first measure use with UNSPECIFIED, and then with // EXACTLY. Ignore the first call since anything we do will be overwritten anyway return; } // 重新测量 if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) { final int count = getChildCount();
// First we'll find the widest tab int largestTabWidth = 0; for (int i = 0, z = count; i < z; i++) { View child = getChildAt(i); if (child.getVisibility() == VISIBLE) { largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth()); } }
if (largestTabWidth <= 0) { // If we don't have a largest child yet, skip until the next measure pass return; } // 间隔 final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN); boolean remeasure = false; // 一屏放得下 if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) { // If the tabs fit within our width minus gutters, we will set all tabs to have // the same width for (int i = 0; i < count; i++) { final LinearLayout.LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); if (lp.width != largestTabWidth || lp.weight != 0) { lp.width = largestTabWidth; lp.weight = 0; remeasure = true; } } } else { // If the tabs will wrap to be larger than the width minus gutters, we need // to switch to GRAVITY_FILL mTabGravity = GRAVITY_FILL; updateTabViews(false); remeasure = true; }
if (remeasure) { // Now re-measure after our changes super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } }
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { // If we're currently running an animation, lets cancel it and start a // new animation with the remaining duration mIndicatorAnimator.cancel(); final long duration = mIndicatorAnimator.getDuration(); animateIndicatorToPosition(mSelectedPosition, Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration)); } else { // If we've been layed out, update the indicator position updateIndicatorPosition(); } }
@Override public boolean performClick() { final boolean handled = super.performClick(); if (mTab != null) { if (!handled) { playSoundEffect(SoundEffectConstants.CLICK); } mTab.select(); return true; } else { return handled; } } /** * Select this tab. Only valid if the tab has been added to the action bar. */ public void select() { if (mParent == null) { throw new IllegalArgumentException("Tab not attached to a TabLayout"); } mParent.selectTab(this); }
private void animateToTab(int newPosition) { if (newPosition == Tab.INVALID_POSITION) { return; } if (getWindowToken() == null || !ViewCompat.isLaidOut(this) || mTabStrip.childrenNeedLayout()) { // If we don't have a window token, or we haven't been laid out yet just dra // position now setScrollPosition(newPosition, 0f, true); return; } final int startScrollX = getScrollX(); final int targetScrollX = calculateScrollXForTab(newPosition, 0); if (startScrollX != targetScrollX) { ensureScrollAnimator(); mScrollAnimator.setIntValues(startScrollX, targetScrollX); mScrollAnimator.start(); } // Now animate the indicator mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION); }
public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener { private final WeakReference<TabLayout> mTabLayoutRef; private int mPreviousScrollState; private int mScrollState;
public TabLayoutOnPageChangeListener(TabLayout tabLayout) { mTabLayoutRef = new WeakReference<>(tabLayout); }
@Override public void onPageScrollStateChanged(final int state) { mPreviousScrollState = mScrollState; mScrollState = state; }
@Override public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) { final TabLayout tabLayout = mTabLayoutRef.get(); if (tabLayout != null) { // Only update the text selection if we're not settling, or we are settling after // being dragged final boolean updateText = mScrollState != SCROLL_STATE_SETTLING || mPreviousScrollState == SCROLL_STATE_DRAGGING; // Update the indicator if we're not settling after being idle. This is caused // from a setCurrentItem() call and will be handled by an animation from // onPageSelected() instead. final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING && mPreviousScrollState == SCROLL_STATE_IDLE); tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator); } }
@Override public void onPageSelected(final int position) { final TabLayout tabLayout = mTabLayoutRef.get(); if (tabLayout != null && tabLayout.getSelectedTabPosition() != position && position < tabLayout.getTabCount()) { // Select the tab, only updating the indicator if we're not being dragged/settled // (since onPageScrolled will handle that). final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE || (mScrollState == SCROLL_STATE_SETTLING && mPreviousScrollState == SCROLL_STATE_IDLE); tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator); } }