Android仿网易新闻导航栏PagerSlidingTabStrip源码分析


转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持!


前言

最近工作比较忙,所以现在才更新博文,对不住大家了~!言归正传,我们来说说这个PagerSlidingTabStrip,它是配合ViewPager使用的导航栏,网易新闻就是用的这个导航,我们仔细观察这个导航栏不仅他是跟着ViewPager滑动而滑动,而且指示器还会随着标题的长度而动态的变化长度。

·

下载地址:

Github:https://github.com/astuetz/PagerSlidingTabStrip

CSDN:http://download.csdn.net/detail/cym492224103/8413393

在分析源码的前一个步骤,我们先学会如何使用:

1.把项目源码下载下来导入工程,可以看到


library为引用工程,再看看如何使用PagerSlidingTabStrip。

2.在布局文件配置:

[html]  view plain copy print ?
  1. <com.astuetz.PagerSlidingTabStrip  
  2.        android:id="@+id/tabs"  
  3.        android:layout_width="match_parent"  
  4.        android:layout_height="48dip"  
  5.        android:background="@drawable/background_tabs" />  

3.获取PagerSlidingTabStrip控件,绑定ViewPager

[java]  view plain copy print ?
  1. @Override  
  2.     protected void onCreate(Bundle savedInstanceState) {  
  3.         super.onCreate(savedInstanceState);  
  4.         setContentView(R.layout.activity_main);  
  5.   
  6.         tabs = (PagerSlidingTabStrip) findViewById(R.id.tabs);  
  7.         pager = (ViewPager) findViewById(R.id.pager);  
  8.         adapter = new MyPagerAdapter(getSupportFragmentManager());  
  9.   
  10.         pager.setAdapter(adapter);  
  11.   
  12.         final int pageMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources()  
  13.                 .getDisplayMetrics());  
  14.         pager.setPageMargin(pageMargin);  
  15.   
  16.         tabs.setViewPager(pager);  
  17.   
  18.         changeColor(currentColor);  
  19.     }  
这样就已经完成了,很简单吧~!

它还提供了很多方法来让开发者更好的使用它来完成自己想要的风格:

  • pstsIndicatorColor滑动指示器颜色
  • pstsUnderlineColor视图的底部的全宽线的颜色
  • pstsDividerColor选项卡之间的分隔线的颜色
  • pstsIndicatorHeight滑动指标高度
  • pstsUnderlineHeight视图的底部高度的全宽线
  • pstsDividerPadding顶部和分频器的底部填充
  • pstsTabPaddingLeftRight左,每个选项卡的右填充
  • pstsScrollOffset所选选项卡的滚动偏移量
  • pstsTabBackground每个标签的背景可拉伸,应该是一个StateListDrawable
  • pstsShouldExpand如果设置为true,每个选项卡被赋予了相同的权重,默认为false
  • pstsTextAllCaps如果为true,所有的选项卡标题将是大写,默认为true

所有属性都有各自的getter和setter方法​​在运行时改变它们

源码分析:

学习完使用之后我们就来解开它的神秘面纱,带着问题我们来看看它到底是如何实现的。


1.引用工程目录结构


就一个PagerSlidingTabStrip类,比起我之前分析过的ResideMenu要容易一些。


2.看继承体系

[java]  view plain copy print ?
  1. public class PagerSlidingTabStrip extends HorizontalScrollView  
继承了HorizontalScrillView也就是说,拥有了HorizontalScrillView的横向滑动的功能。


3.看构造方法

[java]  view plain copy print ?
  1. public PagerSlidingTabStrip(Context context, AttributeSet attrs, int defStyle) {  
  2.     super(context, attrs, defStyle);  
  3.   
  4.     setFillViewport(true);  
  5.     setWillNotDraw(false);  
  6.   
  7.     tabsContainer = new LinearLayout(context);  
  8.     tabsContainer.setOrientation(LinearLayout.HORIZONTAL);  
  9.     tabsContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));  
  10.     addView(tabsContainer);  
  11.   
  12.     DisplayMetrics dm = getResources().getDisplayMetrics();  
  13.   
  14.     scrollOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, scrollOffset, dm);  
  15.     indicatorHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, indicatorHeight, dm);  
  16.     underlineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, underlineHeight, dm);  
  17.     dividerPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerPadding, dm);  
  18.     tabPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tabPadding, dm);  
  19.     dividerWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerWidth, dm);  
  20.     tabTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, tabTextSize, dm);  
  21.   
  22.     // get system attrs (android:textSize and android:textColor)  
  23.   
  24.     TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);  
  25.   
  26.     tabTextSize = a.getDimensionPixelSize(0, tabTextSize);  
  27.     tabTextColor = a.getColor(1, tabTextColor);  
  28.   
  29.     a.recycle();  
  30.   
  31.     // get custom attrs  
  32.   
  33.     a = context.obtainStyledAttributes(attrs, R.styleable.PagerSlidingTabStrip);  
  34.   
  35.     indicatorColor = a.getColor(R.styleable.PagerSlidingTabStrip_pstsIndicatorColor, indicatorColor);  
  36.     underlineColor = a.getColor(R.styleable.PagerSlidingTabStrip_pstsUnderlineColor, underlineColor);  
  37.     dividerColor = a.getColor(R.styleable.PagerSlidingTabStrip_pstsDividerColor, dividerColor);  
  38.     indicatorHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsIndicatorHeight, indicatorHeight);  
  39.     underlineHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsUnderlineHeight, underlineHeight);  
  40.     dividerPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsDividerPadding, dividerPadding);  
  41.     tabPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsTabPaddingLeftRight, tabPadding);  
  42.     tabBackgroundResId = a.getResourceId(R.styleable.PagerSlidingTabStrip_pstsTabBackground, tabBackgroundResId);  
  43.     shouldExpand = a.getBoolean(R.styleable.PagerSlidingTabStrip_pstsShouldExpand, shouldExpand);  
  44.     scrollOffset = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsScrollOffset, scrollOffset);  
  45.     textAllCaps = a.getBoolean(R.styleable.PagerSlidingTabStrip_pstsTextAllCaps, textAllCaps);  
  46.   
  47.     a.recycle();  
  48.   
  49.     rectPaint = new Paint();  
  50.     rectPaint.setAntiAlias(true);  
  51.     rectPaint.setStyle(Style.FILL);  
  52.   
  53.     dividerPaint = new Paint();  
  54.     dividerPaint.setAntiAlias(true);  
  55.     dividerPaint.setStrokeWidth(dividerWidth);  
  56.   
  57.     defaultTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);  
  58.     expandedTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);  
  59.   
  60.     if (locale == null) {  
  61.         locale = getResources().getConfiguration().locale;  
  62.     }  
  63. }  
以上构造里面做了5件事情:

1.生成了一个横向的LinearLayout

2.初始化数值(如:tab边距、tab字体大小等等)

3.获取布局文件的配置属性,因此我们就知道了,它不仅暴露了方法可以设置这些参数,而且还可以通过配置布局文件设置这些参数。(如:tab边距、tab字体大小等等)

4.创建了两个画笔,一个是标题的分割线用的,一个是画指示器用的。

5.创建两个Tabl的LinearLayout.LayoutParams,一个为默认的(宽度随内容长度),一个为扩展的(宽度比例都一致)。


4.看一下绑定ViewPager里面它做了什么?

[java]  view plain copy print ?
  1. public void setViewPager(ViewPager pager) {  
  2.         this.pager = pager;  
  3.   
  4.         if (pager.getAdapter() == null) {  
  5.             throw new IllegalStateException("ViewPager does not have adapter instance.");  
  6.         }  
  7.   
  8.         pager.setOnPageChangeListener(pageListener);  
  9.   
  10.         notifyDataSetChanged();  
  11.     }  

接下来在看看notifyDataSetChanged这个方法里面做了什么?

[java]  view plain copy print ?
  1. public void notifyDataSetChanged() {  
  2.   
  3.     tabsContainer.removeAllViews();  
  4.   
  5.     tabCount = pager.getAdapter().getCount();  
  6.   
  7.     for (int i = 0; i < tabCount; i++) {  
  8.   
  9.         if (pager.getAdapter() instanceof IconTabProvider) {  
  10.             addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconResId(i));  
  11.         } else {  
  12.             addTextTab(i, pager.getAdapter().getPageTitle(i).toString());  
  13.         }  
  14.   
  15.     }  
  16.   
  17.     updateTabStyles();  
  18.   
  19.     getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {  
  20.   
  21.         @SuppressWarnings("deprecation")  
  22.         @SuppressLint("NewApi")  
  23.         @Override  
  24.         public void onGlobalLayout() {  
  25.   
  26.             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {  
  27.                 getViewTreeObserver().removeGlobalOnLayoutListener(this);  
  28.             } else {  
  29.                 getViewTreeObserver().removeOnGlobalLayoutListener(this);  
  30.             }  
  31.   
  32.             currentPosition = pager.getCurrentItem();  
  33.             scrollToChild(currentPosition, 0);  
  34.         }  
  35.     });  
  36.   
  37. }  
创建导航栏标题,可以用文字或图标的形式为标题内容。初始化完成后默认滑动到第一个标题。

pager设置了一个监听,看看监听里面做了什么?

[java]  view plain copy print ?
  1. private class PageListener implements OnPageChangeListener {  
  2.   
  3.         @Override  
  4.         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {  
  5.   
  6.             currentPosition = position;  
  7.             currentPositionOffset = positionOffset;  
  8.   
  9.             scrollToChild(position, (int) (positionOffset * tabsContainer.getChildAt(position).getWidth()));  
  10.   
  11.             invalidate();  
  12.   
  13.             if (delegatePageListener != null) {  
  14.                 delegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels);  
  15.             }  
  16.         }  
  17.   
  18.         @Override  
  19.         public void onPageScrollStateChanged(int state) {  
  20.             if (state == ViewPager.SCROLL_STATE_IDLE) {  
  21.                 scrollToChild(pager.getCurrentItem(), 0);  
  22.             }  
  23.   
  24.             if (delegatePageListener != null) {  
  25.                 delegatePageListener.onPageScrollStateChanged(state);  
  26.             }  
  27.         }  
  28.   
  29.         @Override  
  30.         public void onPageSelected(int position) {  
  31.             if (delegatePageListener != null) {  
  32.                 delegatePageListener.onPageSelected(position);  
  33.             }  
  34.         }  
  35.   
  36.     }  

观察onPageScrolled(当页面在滑动的时候会调用此方法)方法里面做了什么,

1.改变当前滑动位置。(在此就实现了滑动下面的ViewPager,同时导航也在跟随这ViewPager滑动而滑动)

2.调用invalidate方法。(invalidate则会调用onDraw,所以看看它在做什么)

[java]  view plain copy print ?
  1. @Override  
  2.     protected void onDraw(Canvas canvas) {  
  3.         super.onDraw(canvas);  
  4.   
  5.         if (isInEditMode() || tabCount == 0) {  
  6.             return;  
  7.         }  
  8.   
  9.         final int height = getHeight();  
  10.   
  11.         // draw indicator line  
  12.   
  13.         rectPaint.setColor(indicatorColor);  
  14.   
  15.         // default: line below current tab  
  16.         View currentTab = tabsContainer.getChildAt(currentPosition);  
  17.         float lineLeft = currentTab.getLeft();  
  18.         float lineRight = currentTab.getRight();  
  19.   
  20.         // if there is an offset, start interpolating left and right coordinates between current and next tab  
  21.         if (currentPositionOffset > 0f && currentPosition < tabCount - 1) {  
  22.   
  23.             View nextTab = tabsContainer.getChildAt(currentPosition + 1);  
  24.             final float nextTabLeft = nextTab.getLeft();  
  25.             final float nextTabRight = nextTab.getRight();  
  26.   
  27.             lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * lineLeft);  
  28.             lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * lineRight);  
  29.         }  
  30.   
  31.         canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint);  
  32.   
  33.         // draw underline  
  34.   
  35.         rectPaint.setColor(underlineColor);  
  36.         canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint);  
  37.   
  38.         // draw divider  
  39.   
  40.         dividerPaint.setColor(dividerColor);  
  41.         for (int i = 0; i < tabCount - 1; i++) {  
  42.             View tab = tabsContainer.getChildAt(i);  
  43.             canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(), height - dividerPadding, dividerPaint);  
  44.         }  
  45.     }  
做了两件事情:

1.绘制标题的分割线

2.绘制指示器。

这样就实现了跟随ViewPager滑动而滑动,而且指示器还会随着标题的长度而动态的变化长度的效果了。


6.我们来看看它暴露的方法是怎么写的:

[java]  view plain copy print ?
  1. public void setIndicatorColor(int indicatorColor) {  
  2.         this.indicatorColor = indicatorColor;  
  3.         invalidate();  
  4.     }  
  5.   
  6.     public void setIndicatorColorResource(int resId) {  
  7.         this.indicatorColor = getResources().getColor(resId);  
  8.         invalidate();  
  9.     }  
  10.   
  11.     public int getIndicatorColor() {  
  12.         return this.indicatorColor;  
  13.     }  
  14.   
  15.     public void setIndicatorHeight(int indicatorLineHeightPx) {  
  16.         this.indicatorHeight = indicatorLineHeightPx;  
  17.         invalidate();  
  18.     }  
  19.   
  20.     public int getIndicatorHeight() {  
  21.         return indicatorHeight;  
  22.     }  
  23.   
  24.     public void setUnderlineColor(int underlineColor) {  
  25.         this.underlineColor = underlineColor;  
  26.         invalidate();  
  27.     }  
  28.   
  29.     public void setUnderlineColorResource(int resId) {  
  30.         this.underlineColor = getResources().getColor(resId);  
  31.         invalidate();  
  32.     }  
  33.   
  34.     public int getUnderlineColor() {  
  35.         return underlineColor;  
  36.     }  
  37.   
  38.     public void setDividerColor(int dividerColor) {  
  39.         this.dividerColor = dividerColor;  
  40.         invalidate();  
  41.     }  
  42.   
  43.     public void setDividerColorResource(int resId) {  
  44.         this.dividerColor = getResources().getColor(resId);  
  45.         invalidate();  
  46.     }  
  47.   
  48.     public int getDividerColor() {  
  49.         return dividerColor;  
  50.     }  
  51.   
  52.     public void setUnderlineHeight(int underlineHeightPx) {  
  53.         this.underlineHeight = underlineHeightPx;  
  54.         invalidate();  
  55.     }  
  56.   
  57.     public int getUnderlineHeight() {  
  58.         return underlineHeight;  
  59.     }  
  60.   
  61.     public void setDividerPadding(int dividerPaddingPx) {  
  62.         this.dividerPadding = dividerPaddingPx;  
  63.         invalidate();  
  64.     }  
  65.   
  66.     public int getDividerPadding() {  
  67.         return dividerPadding;  
  68.     }  
  69.   
  70.     public void setScrollOffset(int scrollOffsetPx) {  
  71.         this.scrollOffset = scrollOffsetPx;  
  72.         invalidate();  
  73.     }  
  74.   
  75.     public int getScrollOffset() {  
  76.         return scrollOffset;  
  77.     }  
  78.   
  79.     public void setShouldExpand(boolean shouldExpand) {  
  80.         this.shouldExpand = shouldExpand;  
  81.         requestLayout();  
  82.     }  
  83.   
  84.     public boolean getShouldExpand() {  
  85.         return shouldExpand;  
  86.     }  
  87.   
  88.     public boolean isTextAllCaps() {  
  89.         return textAllCaps;  
  90.     }  
  91.   
  92.     public void setAllCaps(boolean textAllCaps) {  
  93.         this.textAllCaps = textAllCaps;  
  94.     }  
  95.   
  96.     public void setTextSize(int textSizePx) {  
  97.         this.tabTextSize = textSizePx;  
  98.         updateTabStyles();  
  99.     }  
  100.   
  101.     public int getTextSize() {  
  102.         return tabTextSize;  
  103.     }  
  104.   
  105.     public void setTextColor(int textColor) {  
  106.         this.tabTextColor = textColor;  
  107.         updateTabStyles();  
  108.     }  
  109.   
  110.     public void setTextColorResource(int resId) {  
  111.         this.tabTextColor = getResources().getColor(resId);  
  112.         updateTabStyles();  
  113.     }  
  114.   
  115.     public int getTextColor() {  
  116.         return tabTextColor;  
  117.     }  
  118.   
  119.     public void setTypeface(Typeface typeface, int style) {  
  120.         this.tabTypeface = typeface;  
  121.         this.tabTypefaceStyle = style;  
  122.         updateTabStyles();  
  123.     }  
  124.   
  125.     public void setTabBackground(int resId) {  
  126.         this.tabBackgroundResId = resId;  
  127.     }  
  128.   
  129.     public int getTabBackground() {  
  130.         return tabBackgroundResId;  
  131.     }  
  132.   
  133.     public void setTabPaddingLeftRight(int paddingPx) {  
  134.         this.tabPadding = paddingPx;  
  135.         updateTabStyles();  
  136.     }  
  137.   
  138.     public int getTabPaddingLeftRight() {  
  139.         return tabPadding;  
  140.     }  


7.除此之外它还做了一些细节上的处理,如:Activity销毁的时候,它还保存了当前的选择标题的坐标,这样就可以防止横竖屏导致Activity销毁而引起选择器位置重置的问题,来看看代码吧~!

[java]  view plain copy print ?
  1. @Override  
  2.     public void onRestoreInstanceState(Parcelable state) {  
  3.         SavedState savedState = (SavedState) state;  
  4.         super.onRestoreInstanceState(savedState.getSuperState());  
  5.         currentPosition = savedState.currentPosition;  
  6.         requestLayout();  
  7.     }  
  8.   
  9.     @Override  
  10.     public Parcelable onSaveInstanceState() {  
  11.         Parcelable superState = super.onSaveInstanceState();  
  12.         SavedState savedState = new SavedState(superState);  
  13.         savedState.currentPosition = currentPosition;  
  14.         return savedState;  
  15.     }  
  16.   
  17.     static class SavedState extends BaseSavedState {  
  18.         int currentPosition;  
  19.   
  20.         public SavedState(Parcelable superState) {  
  21.             super(superState);  
  22.         }  
  23.   
  24.         private SavedState(Parcel in) {  
  25.             super(in);  
  26.             currentPosition = in.readInt();  
  27.         }  
  28.   
  29.         @Override  
  30.         public void writeToParcel(Parcel dest, int flags) {  
  31.             super.writeToParcel(dest, flags);  
  32.             dest.writeInt(currentPosition);  
  33.         }  
  34.   
  35.         public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {  
  36.             @Override  
  37.             public SavedState createFromParcel(Parcel in) {  
  38.                 return new SavedState(in);  
  39.             }  
  40.   
  41.             @Override  
  42.             public SavedState[] newArray(int size) {  
  43.                 return new SavedState[size];  
  44.             }  
  45.         };  
  46.     }  



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值