史上最全的TabLayout源码分析

TabLayout在Android开发中十分重要,其关键元素如下所示:

Tab类和TabView类和SlidingTabStrip类为TabLayout提供了三个基本的元素。

对于Tab类:还有一个TagView的mView成员变量,可以通过setIcon()和setCustomView()方法设置tab的mView参数,

public static final class Tab {
    public static final int INVALID_POSITION = -1;
    private Object mTag;
    private Drawable mIcon;
    private CharSequence mText;
    private CharSequence mContentDesc;
    private int mPosition = INVALID_POSITION;
    private View mCustomView;
    TabLayout mParent;
    TabView mView;
    Tab() {
     }
    @Nullable
    public Object getTag() {
        return mTag;
    }
    @NonNull
    public Tab setTag(@Nullable Object tag) {
        mTag = tag;
        return this;
    }
    @Nullable
    public View getCustomView() {
        return mCustomView;
    }
    @NonNull
    public Tab setCustomView(@Nullable View view) {
        mCustomView = view;
        updateView();//更新TabView里的undateView()函数
        return this;
    }
    @NonNull
    public Tab setCustomView(@LayoutRes int resId) {
        final LayoutInflater inflater = LayoutInflater.from(mView.getContext());
        return setCustomView(inflater.inflate(resId, mView, false));
    }
    @Nullable
    public Drawable getIcon() {
        return mIcon;
    }
    public int getPosition() {
        return mPosition;
    }
    void setPosition(int position) {
        mPosition = position;
    }
    @Nullable
    public CharSequence getText() {
        return mText;
    }
    @NonNull
    public Tab setIcon(@Nullable Drawable icon) {
        mIcon = icon;
        updateView();
        return this;
    }
    @NonNull
    public Tab setIcon(@DrawableRes int resId) {
        if (mParent == null) {
            throw new IllegalArgumentException("Tab not attached to a TabLayout");
        }
        return setIcon(AppCompatResources.getDrawable(mParent.getContext(), resId));
    }
    @NonNull
    public Tab setText(@Nullable CharSequence text) {
        mText = text;
        updateView();
        return this;
    }
    @NonNull
    public Tab setText(@StringRes int resId) {
        if (mParent == null) {
            throw new IllegalArgumentException("Tab not attached to a TabLayout");
        }
        return setText(mParent.getResources().getText(resId));
    }
    public void select() {
        if (mParent == null) {
            throw new IllegalArgumentException("Tab not attached to a TabLayout");
        }
        mParent.selectTab(this);
    }
    public boolean isSelected() {
        if (mParent == null) {
            throw new IllegalArgumentException("Tab not attached to a TabLayout");
        }
        return mParent.getSelectedTabPosition() == mPosition;
    }
    @NonNull
    public Tab setContentDescription(@StringRes int resId) {
        if (mParent == null) {
            throw new IllegalArgumentException("Tab not attached to a TabLayout");
        }
        return setContentDescription(mParent.getResources().getText(resId));
    }
    @NonNull
    public Tab setContentDescription(@Nullable CharSequence contentDesc) {
        mContentDesc = contentDesc;
        updateView();
        return this;
    }
    @Nullable
    public CharSequence getContentDescription() {
        return mContentDesc;
    }
    void updateView() {
        if (mView != null) {
            mView.update();//这里调用TabView的update()函数
        }
    }
    void reset() {
        mParent = null;
        mView = null;
        mTag = null;
        mIcon = null;
        mText = null;
        mContentDesc = null;
        mPosition = INVALID_POSITION;
        mCustomView = null;
    }
}
接下来看下负责view更新的TabView类:其中通过createTabView建立Tab和TabView直接的联系(如下)。
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;
}
TabView类如下:其中updateTextAndIcon负责是否更新TextView和IconView的重绘操作
class TabView extends LinearLayout {
    private Tab mTab;
    private TextView mTextView;
    private ImageView mIconView;
    private View mCustomView;
    private TextView mCustomTextView;
    private ImageView mCustomIconView;
    private int mDefaultMaxLines = 2;
    public TabView(Context context) {
        super(context);
        if (mTabBackgroundResId != 0) {
            ViewCompat.setBackground(
                    this, AppCompatResources.getDrawable(context, mTabBackgroundResId));
        }
        ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop,
                mTabPaddingEnd, mTabPaddingBottom);
        setGravity(Gravity.CENTER);
        setOrientation(VERTICAL);
        setClickable(true);
        ViewCompat.setPointerIcon(this,
                PointerIconCompat.getSystemIcon(getContext(), PointerIconCompat.TYPE_HAND));
    }
    @Override
    public boolean performClick() {
        final boolean handled = super.performClick();
        if (mTab != null) {
            if (!handled) {
                playSoundEffect(SoundEffectConstants.CLICK);
            }
            mTab.select();
            return true;
        } else {
            return handled;
        }
    }
    @Override
    public void setSelected(final boolean selected) {
        final boolean changed = isSelected() != selected;
        super.setSelected(selected);
        if (changed && selected && Build.VERSION.SDK_INT < 16) {
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
        }
        if (mTextView != null) {
            mTextView.setSelected(selected);
        }
        if (mIconView != null) {
            mIconView.setSelected(selected);
        }
        if (mCustomView != null) {
            mCustomView.setSelected(selected);
        }
    }
    @Override
    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        event.setClassName(ActionBar.Tab.class.getName());
    }
    @Override
    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(ActionBar.Tab.class.getName());
    }
    @Override
    public void onMeasure(final int origWidthMeasureSpec, final int origHeightMeasureSpec) {
        final int specWidthSize = MeasureSpec.getSize(origWidthMeasureSpec);
        final int specWidthMode = MeasureSpec.getMode(origWidthMeasureSpec);
        final int maxWidth = getTabMaxWidth();

        final int widthMeasureSpec;
        final int heightMeasureSpec = origHeightMeasureSpec;
        if (maxWidth > 0 && (specWidthMode == MeasureSpec.UNSPECIFIED
                || specWidthSize > maxWidth)) {
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTabMaxWidth, MeasureSpec.AT_MOST);
        } else {
            widthMeasureSpec = origWidthMeasureSpec;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mTextView != null) {
            final Resources res = getResources();
            float textSize = mTabTextSize;
            int maxLines = mDefaultMaxLines;
            if (mIconView != null && mIconView.getVisibility() == VISIBLE) {
                maxLines = 1;
            } else if (mTextView != null && mTextView.getLineCount() > 1) {
                textSize = mTabTextMultiLineSize;
            }
            final float curTextSize = mTextView.getTextSize();
            final int curLineCount = mTextView.getLineCount();
            final int curMaxLines = TextViewCompat.getMaxLines(mTextView);
            if (textSize != curTextSize || (curMaxLines >= 0 && maxLines != curMaxLines)) {
                boolean updateTextView = true;
                if (mMode == MODE_FIXED && textSize > curTextSize && curLineCount == 1) {
                    final Layout layout = mTextView.getLayout();
                    if (layout == null || approximateLineWidth(layout, 0, textSize)
                            > getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) {
                        updateTextView = false;
                    }
                }
                if (updateTextView) {
                    mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
                    mTextView.setMaxLines(maxLines);
                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                }
            }
        }
    }
    void setTab(@Nullable final Tab tab) {//设置Tab联系
        if (tab != mTab) {
            mTab = tab;
            update();
        }
    }
    void reset() {
        setTab(null);
        setSelected(false);
    }
    final void update() {
        final Tab tab = mTab;
        final View custom = tab != null ? tab.getCustomView() : null;//先看getCustomView中有无View
        if (custom != null) {
            final ViewParent customParent = custom.getParent();
            if (customParent != this) {
                if (customParent != null) {
                    ((ViewGroup) customParent).removeView(custom);
                }
                addView(custom);
            }
            mCustomView = custom;
            if (mTextView != null) {
                mTextView.setVisibility(GONE);
            }
            if (mIconView != null) {
                mIconView.setVisibility(GONE);
                mIconView.setImageDrawable(null);
            }
            mCustomTextView = (TextView) custom.findViewById(android.R.id.text1);
            if (mCustomTextView != null) {
                mDefaultMaxLines = TextViewCompat.getMaxLines(mCustomTextView);
            }
            mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon);
        } else {
            if (mCustomView != null) {
                removeView(mCustomView);
                mCustomView = null;
            }
            mCustomTextView = null;
            mCustomIconView = null;
        }
        if (mCustomView == null) {//如果mCustomView没有,则通过mIconView更新
            if (mIconView == null) {
                ImageView iconView = (ImageView) LayoutInflater.from(getContext())
                        .inflate(R.layout.design_layout_tab_icon, this, false);
                addView(iconView, 0);
                mIconView = iconView;
            }
            if (mTextView == null) {
                TextView textView = (TextView) LayoutInflater.from(getContext())
                        .inflate(R.layout.design_layout_tab_text, this, false);
                addView(textView);
                mTextView = textView;
                mDefaultMaxLines = TextViewCompat.getMaxLines(mTextView);
            }
            TextViewCompat.setTextAppearance(mTextView, mTabTextAppearance);
            if (mTabTextColors != null) {
                mTextView.setTextColor(mTabTextColors);
            }
            updateTextAndIcon(mTextView, mIconView);
        } else {
            if (mCustomTextView != null || mCustomIconView != null) {
                updateTextAndIcon(mCustomTextView, mCustomIconView);
            }
        }
        setSelected(tab != null && tab.isSelected());
    }
    private void updateTextAndIcon(@Nullable final TextView textView,
            @Nullable final ImageView iconView) {
        final Drawable icon = mTab != null ? mTab.getIcon() : null;
        final CharSequence text = mTab != null ? mTab.getText() : null;
        final CharSequence contentDesc = mTab != null ? mTab.getContentDescription() : null;
        if (iconView != null) {
            if (icon != null) {
                iconView.setImageDrawable(icon);
                iconView.setVisibility(VISIBLE);
                setVisibility(VISIBLE);
            } else {
                iconView.setVisibility(GONE);
                iconView.setImageDrawable(null);
            }
            iconView.setContentDescription(contentDesc);
        }
        final boolean hasText = !TextUtils.isEmpty(text);
        if (textView != null) {
            if (hasText) {
                textView.setText(text);
                textView.setVisibility(VISIBLE);
                setVisibility(VISIBLE);
            } else {
                textView.setVisibility(GONE);
                textView.setText(null);
            }
            textView.setContentDescription(contentDesc);
        }
        if (iconView != null) {
            MarginLayout Params lp = ((MarginLayoutParams) iconView.getLayoutParams());
            int bottomMargin = 0;
            if (hasText && iconView.getVisibility() == VISIBLE) {
                bottomMargin = dpToPx(DEFAULT_GAP_TEXT_ICON);
            }
            if (bottomMargin != lp.bottomMargin) {
                lp.bottomMargin = bottomMargin;
                iconView.requestLayout();//判断图片是否要更新
            }
        }
        TooltipCompat.setTooltipText(this, hasText ? null : contentDesc);
    }
    public Tab getTab() {
        return mTab;
    }
    private float approximateLineWidth(Layout layout, int line, float textSize) {
        return layout.getLineWidth(line) * (textSize / layout.getPaint().getTextSize());
    }
}
 
 
 
 当TabLayout开始执行时,先是设置一些默认参数,然后定义两种Tab模式 
 
 
 
TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    ThemeUtils.checkAppCompatTheme(context);
    setHorizontalScrollBarEnabled(false);// 使Scroll Bar不可用
    mTabStrip = new SlidingTabStrip(context); //添加Tab下滑线
    super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(
            LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout,
            defStyleAttr, R.style.Widget_Design_TabLayout);
    mTabStrip.setSelectedIndicatorHeight(
            a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0));
    mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0));
    mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a
            .getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0);
    mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart,
            mTabPaddingStart);
    mTabPaddingTop = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingTop,
            mTabPaddingTop);
    mTabPaddingEnd = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd,
            mTabPaddingEnd);
    mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom,
            mTabPaddingBottom);
    mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance,
            R.style.TextAppearance_Design_Tab);
    final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance,
            android.support.v7.appcompat.R.styleable.TextAppearance);
    try {
        mTabTextSize = ta.getDimensionPixelSize(
                android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, 0);
        mTabTextColors = ta.getColorStateList(
                android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor);
    } finally {
        ta.recycle();
    }
    if (a.hasValue(R.styleable.TabLayout_tabTextColor)) {
                mTabTextColors = a.getColorStateList(R.styleable.TabLayout_tabTextColor);
    }
    if (a.hasValue(R.styleable.TabLayout_tabSelectedTextColor)) {
               final int selected = a.getColor(R.styleable.TabLayout_tabSelectedTextColor, 0);
        mTabTextColors = createColorStateList(mTabTextColors.getDefaultColor(), selected);
    }
    mRequestedTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth,
            INVALID_WIDTH);
    mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth,
            INVALID_WIDTH);
    mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0);
    mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0);
    mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED);
    mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL);
    a.recycle();
    final Resources res = getResources();
    mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line);
    mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width);
    applyModeAndGravity();// 加载tab mode and gravity
}
接下来是applyModeAndGravity()方法
private void applyModeAndGravity() {
    int paddingStart = 0;
    if (mMode == MODE_SCROLLABLE) {
        paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart);
    }
    ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0);
    switch (mMode) {
        case MODE_FIXED:
            mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL);
            break;
        case MODE_SCROLLABLE:
            mTabStrip.setGravity(GravityCompat.START);
            break;  //根据模式设置Gravity方式
    }
    updateTabViews(true);
}
调用updateTabViews方法
void updateTabViews(final boolean requestLayout) {
    for (int i = 0; i < mTabStrip.getChildCount(); i++) {
        View child = mTabStrip.getChildAt(i);
        child.setMinimumWidth(getTabMinWidth());
        updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams());
        if (requestLayout) {
            child.requestLayout();
        }
    }
}
 
 
 
 
默认情况下mTabStrip是没有东西的,得通过布局文件或者动态加载的方式为其添加内容。
如tabLayout.addTab(tabLayout.newTab()),该方法调用addTab函数
public void addTab(@NonNull Tab tab) {//其实是对mTabStrip的操作
    addTab(tab, mTabs.isEmpty());
}
public void addTab(@NonNull Tab tab, int position) {
    addTab(tab, position, mTabs.isEmpty());
}
public void addTab(@NonNull Tab tab, boolean setSelected) {
    addTab(tab, mTabs.size(), setSelected);
}
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();
    }
}
先调用configureTab(Tab tab, int position)函数:
private void configureTab(Tab tab, int position) {
    tab.setPosition(position);
    mTabs.add(position, tab);
    final int count = mTabs.size();
    for (int i = position + 1; i < count; i++) {
        mTabs.get(i).setPosition(i);
    }
}
然后调用addTabView(tab)函数:
private void addTabView(Tab tab) {
    final TabView tabView = tab.mView;//这个在Tab内部类中mView初始化时定义的
    mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs());//这里执行的mTabStrip同步加载对于Tabview的操作
}
当用户执行setupWithViewPager是便到了最关键的步骤,其做的工作是和ViewPager建立关系,
将PagerAdapter适配到tabLayout。
private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
        boolean implicitSetup) {
    if (mViewPager != null) {
               if (mPageChangeListener != null) {
            mViewPager.removeOnPageChangeListener(mPageChangeListener);//有mPageChangeListener监听,删除监听
        }
        if (mAdapterChangeListener != null) {
            mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener);//有适配改变监听,删除
        }
    }

    if (mCurrentVpSelectedListener != null) {
              removeOnTabSelectedListener(mCurrentVpSelectedListener); //有tab改变监听,删除
        mCurrentVpSelectedListener = null;
    }
    if (viewPager != null) {
        mViewPager = viewPager;
            if (mPageChangeListener == null) {
            mPageChangeListener = new TabLayoutOnPageChangeListener(this);
        }
        mPageChangeListener.reset();
        viewPager.addOnPageChangeListener(mPageChangeListener);//加入viewPager改变监听
        mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
        addOnTabSelectedListener(mCurrentVpSelectedListener); //加入tab改变监听
        final PagerAdapter adapter = viewPager.getAdapter();
        if (adapter != null) {
               setPagerAdapter(adapter, autoRefresh);
        }
        if (mAdapterChangeListener == null) {
            mAdapterChangeListener = new AdapterChangeListener();
        }
        mAdapterChangeListener.setAutoRefresh(autoRefresh);
        viewPager.addOnAdapterChangeListener(mAdapterChangeListener); //加入适配改变监听
        setScrollPosition(viewPager.getCurrentItem(), 0f, true);
    } else {
        ViewPager = null;
        setPagerAdapter(null, false);
    }

    mSetupViewPagerImplicitly = implicitSetup;
}
上述方法中setPagerAdapter()注册一个观察者,观察adapter的变化,然后根据变化映射到tab,
是tab也发生相应变化。
void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) {
    if (mPagerAdapter != null && mPagerAdapterObserver != null) {
        mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver);
    }
    mPagerAdapter = adapter;
    if (addObserver && adapter != null) {
              if (mPagerAdapterObserver == null) {
            mPagerAdapterObserver = new PagerAdapterObserver();
        }
        adapter.registerDataSetObserver(mPagerAdapterObserver);
    }
    populateFromPagerAdapter();
}
PopulateFromPagerAdapter()函数负责映射到了这个新的adapter
void populateFromPagerAdapter() {
    removeAllTabs();//清除所有的tab,所以你如果在设置适配器之前addTab就是多余的了
    if (mPagerAdapter != null) {
        final int adapterCount = mPagerAdapter.getCount();
        for (int i = 0; i < adapterCount; i++) {
            addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false);//重新添加tab通过PagerAdapter的getPageTitle方法设置
        }
        if (mViewPager != null && adapterCount > 0) {
            final int curItem = mViewPager.getCurrentItem();
            if (curItem != getSelectedTabPosition() && curItem < getTabCount()) {
                selectTab(getTabAt(curItem));
            }
        }
    }
}
主要的selectTab函数操作如下:
void selectTab(final Tab tab, boolean updateIndicator) {
    final Tab currentTab = mSelectedTab;

    if (currentTab == tab) {
        if (currentTab != null) {
            dispatchTabReselected(tab);
            animateToTab(tab.getPosition());
        }
    } else {
        final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION;
        if (updateIndicator) {
            if ((currentTab == null || currentTab.getPosition() == Tab.INVALID_POSITION)
                    && newPosition != Tab.INVALID_POSITION) {
                   setScrollPosition(newPosition, 0f, true);
            } else {
                animateToTab(newPosition);
            }
            if (newPosition != Tab.INVALID_POSITION) {
                setSelectedTabView(newPosition);
            }
        }
        if (currentTab != null) {
            dispatchTabUnselected(currentTab);
        }
        mSelectedTab = tab;
        if (tab != null) {
            dispatchTabSelected(tab);
        }
    }
}
至此,加载的任务基本完成了,接下来讨论一下Tab切换的时候,我们需要切换页面的内容,
这时候就需要为它设置一个监听器TabLayout.OnTabSelectedListener。
public static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener {
    private final ViewPager mViewPager;
    public ViewPagerOnTabSelectedListener(ViewPager viewPager) {
        mViewPager = viewPager;
    }
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        mViewPager.setCurrentItem(tab.getPosition());//设置更新的页面
    }
    @Override
    public void onTabUnselected(TabLayout.Tab tab) {
        // No-op
    }
    @Override
    public void onTabReselected(TabLayout.Tab tab) {
        // No-op
    }
}
TabLayoutOnPageChangeListener负责对page改变的监听
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) {
                    final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
                    mPreviousScrollState == SCROLL_STATE_DRAGGING;
        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()) {
                        final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE
                    || (mScrollState == SCROLL_STATE_SETTLING
                    && mPreviousScrollState == SCROLL_STATE_IDLE);
            tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator);
        }
    }
根据前面讨论的,SlidingTabStrip实现了tab的真正绘制实现操作,
下面看下这个类(主要执行onMeasure()->onLayout()->onDraw()的过程):
private class SlidingTabStrip extends LinearLayout {
    private int mSelectedIndicatorHeight;
    private final Paint mSelectedIndicatorPaint;
    int mSelectedPosition = -1;
    float mSelectionOffset;

    private int mLayoutDirection = -1;
    private int mIndicatorLeft = -1;
    private int mIndicatorRight = -1;
    private ValueAnimator mIndicatorAnimator;
    SlidingTabStrip(Context context) {
        super(context);
        setWillNotDraw(false);//重写draw()不起作用时,需要添加这么一句
        mSelectedIndicatorPaint = new Paint();
    }
    void setSelectedIndicatorColor(int color) {
        if (mSelectedIndicatorPaint.getColor() != color) {
            mSelectedIndicatorPaint.setColor(color);
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
    void setSelectedIndicatorHeight(int height) {
        if (mSelectedIndicatorHeight != height) {
            mSelectedIndicatorHeight = height;
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
    boolean childrenNeedLayout() {
        for (int i = 0, z = getChildCount(); i < z; i++) {
            final View child = getChildAt(i);
            if (child.getWidth() <= 0) {
                return true;
            }
        }
        return false;
    }
    void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
        if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
            mIndicatorAnimator.cancel();
        }
        mSelectedPosition = position;
        mSelectionOffset = positionOffset;
        updateIndicatorPosition();
    }
    float getIndicatorPosition() {
        return mSelectedPosition + mSelectionOffset;
    }
    @Override
    public void onRtlPropertiesChanged(int layoutDirection) {
        super.onRtlPropertiesChanged(layoutDirection);

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                if (mLayoutDirection != layoutDirection) {
                requestLayout();
                mLayoutDirection = layoutDirection;
            }
        }
    }
    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
                      return;
        }
        if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) {
            final int count = getChildCount();
            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) {
            return;
            }
            final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN);
            boolean remeasure = false;

            if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) {
                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 {
                mTabGravity = GRAVITY_FILL;
                updateTabViews(false);
                remeasure = true;
            }
            if (remeasure) {
               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()) {
             mIndicatorAnimator.cancel();
            final long duration = mIndicatorAnimator.getDuration();
            animateIndicatorToPosition(mSelectedPosition,
                    Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration));
        } else {
             updateIndicatorPosition();
        }
    }
    private void updateIndicatorPosition() {
        final View selectedTitle = getChildAt(mSelectedPosition);
        int left, right;
        if (selectedTitle != null && selectedTitle.getWidth() > 0) {
            left = selectedTitle.getLeft();
            right = selectedTitle.getRight();
            if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
                 View nextTitle = getChildAt(mSelectedPosition + 1);
                left = (int) (mSelectionOffset * nextTitle.getLeft() +
                        (1.0f - mSelectionOffset) * left);
                right = (int) (mSelectionOffset * nextTitle.getRight() +
                        (1.0f - mSelectionOffset) * right);
            }
        } else {
            left = right = -1;
        }
        setIndicatorPosition(left, right);
    }
    void setIndicatorPosition(int left, int right) {
        if (left != mIndicatorLeft || right != mIndicatorRight) {
            mIndicatorLeft = left;
            mIndicatorRight = right;
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
    void animateIndicatorToPosition(final int position, int duration) {
        if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
            mIndicatorAnimator.cancel();
        }
        final boolean isRtl = ViewCompat.getLayoutDirection(this)
                == ViewCompat.LAYOUT_DIRECTION_RTL;
        final View targetView = getChildAt(position);
        if (targetView == null) {
            updateIndicatorPosition();
            return;
        }
        final int targetLeft = targetView.getLeft();
        final int targetRight = targetView.getRight();
        final int startLeft;
        final int startRight;
        if (Math.abs(position - mSelectedPosition) <= 1) {            
            startLeft = mIndicatorLeft;
            startRight = mIndicatorRight;
        } else {
            final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET);
            if (position < mSelectedPosition) {
                if (isRtl) {
                    startLeft = startRight = targetLeft - offset;
                } else {
                    startLeft = startRight = targetRight + offset;
                }
            } else {
                if (isRtl) {
                    startLeft = startRight = targetRight + offset;
                } else {
                    startLeft = startRight = targetLeft - offset;
                }
            }
        }
        if (startLeft != targetLeft || startRight != targetRight) {
            ValueAnimator animator = mIndicatorAnimator = new ValueAnimator();
            animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
            animator.setDuration(duration);
            animator.setFloatValues(0, 1);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animator) {
                    final float fraction = animator.getAnimatedFraction();
                    setIndicatorPosition(
                            AnimationUtils.lerp(startLeft, targetLeft, fraction),
                            AnimationUtils.lerp(startRight, targetRight, fraction));
                }
            });
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animator) {
                    mSelectedPosition = position;
                    mSelectionOffset = 0f;
                }
            });
            animator.start();
        }
    }
    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
            canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
                    mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/s1120080286/article/details/79316147
今日推荐