Ym -- Android imitation Netease news navigation bar PagerSlidingTabStrip source code analysis, pagerslidingtabstrip
Reprinted please indicate this article from Cym blog (http://blog.csdn.net/cym492224103). Thank you for your support!
Preface
I have been busy recently, so I have only updated my blog ~! Let's get down to the point, let's talk about PagerSlidingTabStrip, which is used in combination with ViewPager's navigation bar. NetEase news uses this navigation bar. We can carefully observe that this navigation bar not only slides with ViewPager, the length of the title varies dynamically with the title length.
·
:
Github: https://github.com/astuetz/PagerSlidingTabStrip
Http://download.csdn.net/detail/cym492224103/8413393 (CSDN)
Before analyzing the source code, we should first learn how to use it:
1. Download the project source code and import it to the project.
Library is a reference project, and then you can see how to use PagerSlidingTabStrip.
2. Configure the layout file:
<com.astuetz.PagerSlidingTabStrip android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="48dip" android:background="@drawable/background_tabs" />
3. Obtain the PagerSlidingTabStrip control and bind it to ViewPager.
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tabs = (PagerSlidingTabStrip) findViewById(R.id.tabs);pager = (ViewPager) findViewById(R.id.pager);adapter = new MyPagerAdapter(getSupportFragmentManager());pager.setAdapter(adapter);final int pageMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());pager.setPageMargin(pageMargin);tabs.setViewPager(pager);changeColor(currentColor);}This is done. It's easy ~!
It also provides many methods for developers to better use it to achieve their desired style:
pstsIndicatorColorSlide indicator color
pstsUnderlineColorColor of the full width line at the bottom of the View
pstsDividerColorThe color of the separation line between tabs
pstsIndicatorHeightSliding indicator height
pstsUnderlineHeightFull Width line at the bottom of the View
pstsDividerPaddingFill in the top and the bottom of the divider
pstsTabPaddingLeftRightLeft, right filling of each tab
pstsScrollOffsetScroll offset of the Selected tab
pstsTabBackgroundThe background of each label can be stretched. It should be a StateListDrawable
pstsShouldExpandIf this parameter is set to true, each tab is assigned the same weight. The default value is false.
pstsTextAllCapsIf this parameter is set to true, all tab titles are in uppercase. The default value is true.
All attributes have their own getter and setter methods to change them at runtime.
Source code analysis:
After learning how to use it, Let's unlock its mystery and take a look at how it actually works.
1. reference the project directory structure
Just a PagerSlidingTabStrip class, which is easier than the ResideMenu I have previously analyzed.
2. view the inheritance system
public class PagerSlidingTabStrip extends HorizontalScrollView
It inherits HorizontalScrillView, that is, it has the horizontal slide function of HorizontalScrillView.
3. view the constructor
public PagerSlidingTabStrip(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);setFillViewport(true);setWillNotDraw(false);tabsContainer = new LinearLayout(context);tabsContainer.setOrientation(LinearLayout.HORIZONTAL);tabsContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));addView(tabsContainer);DisplayMetrics dm = getResources().getDisplayMetrics();scrollOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, scrollOffset, dm);indicatorHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, indicatorHeight, dm);underlineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, underlineHeight, dm);dividerPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerPadding, dm);tabPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tabPadding, dm);dividerWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerWidth, dm);tabTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, tabTextSize, dm);// get system attrs (android:textSize and android:textColor)TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);tabTextSize = a.getDimensionPixelSize(0, tabTextSize);tabTextColor = a.getColor(1, tabTextColor);a.recycle();// get custom attrsa = context.obtainStyledAttributes(attrs, R.styleable.PagerSlidingTabStrip);indicatorColor = a.getColor(R.styleable.PagerSlidingTabStrip_pstsIndicatorColor, indicatorColor);underlineColor = a.getColor(R.styleable.PagerSlidingTabStrip_pstsUnderlineColor, underlineColor);dividerColor = a.getColor(R.styleable.PagerSlidingTabStrip_pstsDividerColor, dividerColor);indicatorHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsIndicatorHeight, indicatorHeight);underlineHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsUnderlineHeight, underlineHeight);dividerPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsDividerPadding, dividerPadding);tabPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsTabPaddingLeftRight, tabPadding);tabBackgroundResId = a.getResourceId(R.styleable.PagerSlidingTabStrip_pstsTabBackground, tabBackgroundResId);shouldExpand = a.getBoolean(R.styleable.PagerSlidingTabStrip_pstsShouldExpand, shouldExpand);scrollOffset = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsScrollOffset, scrollOffset);textAllCaps = a.getBoolean(R.styleable.PagerSlidingTabStrip_pstsTextAllCaps, textAllCaps);a.recycle();rectPaint = new Paint();rectPaint.setAntiAlias(true);rectPaint.setStyle(Style.FILL);dividerPaint = new Paint();dividerPaint.setAntiAlias(true);dividerPaint.setStrokeWidth(dividerWidth);defaultTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);expandedTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);if (locale == null) {locale = getResources().getConfiguration().locale;}}Five things are done in the above structure:
1. Generate a horizontal LinearLayout
2. initialize numeric values (such as tab margin and tab font size)
3. Get the configuration attributes of the layout file, so we know that it not only exposes methods to set these parameters, but also sets these parameters through the configuration layout file. (For example, tab margin and tab font size)
4. Two paint brushes are created, one for the title split line and the other for the indicator.
5. Create Two Tabl LinearLayout. LayoutParams. One is default (the width varies with the content length), and the other is extended (the width ratio is consistent ).
4. Let's take a look at what ViewPager does?
public void setViewPager(ViewPager pager) {this.pager = pager;if (pager.getAdapter() == null) {throw new IllegalStateException("ViewPager does not have adapter instance.");}pager.setOnPageChangeListener(pageListener);notifyDataSetChanged();}
Next, let's take a look at what is done in the notifyDataSetChanged method?
public void notifyDataSetChanged() {tabsContainer.removeAllViews();tabCount = pager.getAdapter().getCount();for (int i = 0; i < tabCount; i++) {if (pager.getAdapter() instanceof IconTabProvider) {addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconResId(i));} else {addTextTab(i, pager.getAdapter().getPageTitle(i).toString());}}updateTabStyles();getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {@SuppressWarnings("deprecation")@SuppressLint("NewApi")@Overridepublic void onGlobalLayout() {if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {getViewTreeObserver().removeGlobalOnLayoutListener(this);} else {getViewTreeObserver().removeOnGlobalLayoutListener(this);}currentPosition = pager.getCurrentItem();scrollToChild(currentPosition, 0);}});}Create a navigation bar title, which can be the title content in the form of text or icons. After initialization, the system slides to the first title by default.
Pager sets a listener to see what the listener has done?
private class PageListener implements OnPageChangeListener {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {currentPosition = position;currentPositionOffset = positionOffset;scrollToChild(position, (int) (positionOffset * tabsContainer.getChildAt(position).getWidth()));invalidate();if (delegatePageListener != null) {delegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels);}}@Overridepublic void onPageScrollStateChanged(int state) {if (state == ViewPager.SCROLL_STATE_IDLE) {scrollToChild(pager.getCurrentItem(), 0);}if (delegatePageListener != null) {delegatePageListener.onPageScrollStateChanged(state);}}@Overridepublic void onPageSelected(int position) {if (delegatePageListener != null) {delegatePageListener.onPageSelected(position);}}}
Observe what the onPageScrolled method does (this method is called when the page slides,
1. Change the current sliding position. (The ViewPager under the sliding is implemented here, and the navigation is also sliding with the ViewPager)
2. Call the invalidate method. (Invalidate calls onDraw, so let's see what it is doing)
@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (isInEditMode() || tabCount == 0) {return;}final int height = getHeight();// draw indicator linerectPaint.setColor(indicatorColor);// default: line below current tabView currentTab = tabsContainer.getChildAt(currentPosition);float lineLeft = currentTab.getLeft();float lineRight = currentTab.getRight();// if there is an offset, start interpolating left and right coordinates between current and next tabif (currentPositionOffset > 0f && currentPosition < tabCount - 1) {View nextTab = tabsContainer.getChildAt(currentPosition + 1);final float nextTabLeft = nextTab.getLeft();final float nextTabRight = nextTab.getRight();lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * lineLeft);lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * lineRight);}canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint);// draw underlinerectPaint.setColor(underlineColor);canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint);// draw dividerdividerPaint.setColor(dividerColor);for (int i = 0; i < tabCount - 1; i++) {View tab = tabsContainer.getChildAt(i);canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(), height - dividerPadding, dividerPaint);}}Two things are done:
1. Draw the split line of the title
2. Draw the indicator.
In this way, the system slides with ViewPager, and the indicator dynamically changes the length with the length of the title.
6. Let's see how the exposed method is written:
public void setIndicatorColor(int indicatorColor) {this.indicatorColor = indicatorColor;invalidate();}public void setIndicatorColorResource(int resId) {this.indicatorColor = getResources().getColor(resId);invalidate();}public int getIndicatorColor() {return this.indicatorColor;}public void setIndicatorHeight(int indicatorLineHeightPx) {this.indicatorHeight = indicatorLineHeightPx;invalidate();}public int getIndicatorHeight() {return indicatorHeight;}public void setUnderlineColor(int underlineColor) {this.underlineColor = underlineColor;invalidate();}public void setUnderlineColorResource(int resId) {this.underlineColor = getResources().getColor(resId);invalidate();}public int getUnderlineColor() {return underlineColor;}public void setDividerColor(int dividerColor) {this.dividerColor = dividerColor;invalidate();}public void setDividerColorResource(int resId) {this.dividerColor = getResources().getColor(resId);invalidate();}public int getDividerColor() {return dividerColor;}public void setUnderlineHeight(int underlineHeightPx) {this.underlineHeight = underlineHeightPx;invalidate();}public int getUnderlineHeight() {return underlineHeight;}public void setDividerPadding(int dividerPaddingPx) {this.dividerPadding = dividerPaddingPx;invalidate();}public int getDividerPadding() {return dividerPadding;}public void setScrollOffset(int scrollOffsetPx) {this.scrollOffset = scrollOffsetPx;invalidate();}public int getScrollOffset() {return scrollOffset;}public void setShouldExpand(boolean shouldExpand) {this.shouldExpand = shouldExpand;requestLayout();}public boolean getShouldExpand() {return shouldExpand;}public boolean isTextAllCaps() {return textAllCaps;}public void setAllCaps(boolean textAllCaps) {this.textAllCaps = textAllCaps;}public void setTextSize(int textSizePx) {this.tabTextSize = textSizePx;updateTabStyles();}public int getTextSize() {return tabTextSize;}public void setTextColor(int textColor) {this.tabTextColor = textColor;updateTabStyles();}public void setTextColorResource(int resId) {this.tabTextColor = getResources().getColor(resId);updateTabStyles();}public int getTextColor() {return tabTextColor;}public void setTypeface(Typeface typeface, int style) {this.tabTypeface = typeface;this.tabTypefaceStyle = style;updateTabStyles();}public void setTabBackground(int resId) {this.tabBackgroundResId = resId;}public int getTabBackground() {return tabBackgroundResId;}public void setTabPaddingLeftRight(int paddingPx) {this.tabPadding = paddingPx;updateTabStyles();}public int getTabPaddingLeftRight() {return tabPadding;}
7. in addition, it also handles some details. For example, when Activity is destroyed, it also saves the coordinates of the selected title, this will prevent the Activity destruction caused by the horizontal and vertical screens and cause the reset of the selector position. Let's take a look at the code ~!
@Overridepublic void onRestoreInstanceState(Parcelable state) {SavedState savedState = (SavedState) state;super.onRestoreInstanceState(savedState.getSuperState());currentPosition = savedState.currentPosition;requestLayout();}@Overridepublic Parcelable onSaveInstanceState() {Parcelable superState = super.onSaveInstanceState();SavedState savedState = new SavedState(superState);savedState.currentPosition = currentPosition;return savedState;}static class SavedState extends BaseSavedState {int currentPosition;public SavedState(Parcelable superState) {super(superState);}private SavedState(Parcel in) {super(in);currentPosition = in.readInt();}@Overridepublic void writeToParcel(Parcel dest, int flags) {super.writeToParcel(dest, flags);dest.writeInt(currentPosition);}public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {@Overridepublic SavedState createFromParcel(Parcel in) {return new SavedState(in);}@Overridepublic SavedState[] newArray(int size) {return new SavedState[size];}};}After the analysis, I want to see more source code analysis:
Ym -- Android imitation QQ5.0 slide menu ResideMenu source code analysis