[关闭]
@xujun94 2016-08-01T08:58:29.000000Z 字数 19984 阅读 1302

仿网易新闻的顶部导航指示器


我们知道,页面导航器(Navigator)在几乎所有的项目中都会用到,平时大多数时候为了节省时间,都会直接在github上面拿别人的开源项目来用,最近自己在复习自定义View,就尝试封装了一下,源码参考项目PagerSlidingTabStrip

转载请注明原博客地址:

大家先来看一下效果图

基于文字的页面导航器

基于图片的页面导航器

使用方法

主要步骤分为三步

1)在xml文件里面

  1. <com.xujun.viewpagertabindicator.TabPagerIndicator
  2. android:id="@+id/pagerIndicator"
  3. android:layout_width="match_parent"
  4. android:layout_height="50dp"/>
  5. <android.support.v4.view.ViewPager
  6. android:layout_weight="1"
  7. android:id="@+id/viewPager"
  8. android:layout_width="match_parent"
  9. android:layout_height="0dp">
  10. </android.support.v4.view.ViewPager>

2)在代码里面找到相应的控件

  1. mPagerIndicator = (TabPagerIndicator) findViewById(R.id.pagerIndicator);
  2. mViewPager = (ViewPager) findViewById(R.id.viewPager);

3)初始化ViewPager的Adapter和将mViewPager和我们的mPagerIndicator绑定

  1. //必须先给ViewPager设置适配器
  2. mViewPager.setAdapter(mPagerAdapter);
  3. //接着将mViewPage和我们的mPagerIndicator绑定
  4. mPagerIndicator.setViewPager(mViewPager);

注意事项

  1. public CharSequence getPageTitle(int position) {
  2. return titles[position];
  3. }
  1. public class BaseIconAdapter extends FragmentPagerAdapter implements TabPagerIndicator.IconTabProvider {
  2. //省略了若干方法,有兴趣可以去看一下例子
  3. @Override
  4. public int getPageIconResId(int position) {
  5. return resIds[position];
  6. }
  7. }
  1. mPagerIndicator.setIndicatorMode(TabPagerIndicator.IndicatorMode.MODE_WEIGHT_EXPAND_NOSAME,
  2. true);

  1. mPagerIndicator.setIndicatorMode(TabPagerIndicator.IndicatorMode.MODE_WEIGHT_EXPAND_SAME,
  2. true);

关于下划线的 颜色,字体的颜色与大小的设置,请参照源码设置,这里就不列举了


源码分析

大家先来看一下源码吧

  1. public class TabPagerIndicator extends HorizontalScrollView {
  2. public interface IconTabProvider {
  3. int getPageIconResId(int position);
  4. }
  5. // @formatter:off
  6. private static final int[] ATTRS = new int[]{
  7. android.R.attr.textSize,
  8. android.R.attr.textColor
  9. };
  10. // @formatter:on
  11. private LinearLayout.LayoutParams wrapTabLayoutParams;
  12. private LinearLayout.LayoutParams expandedTabLayoutParams;
  13. private final PageListener pageListener = new PageListener();
  14. public OnPageChangeListener delegatePageListener;
  15. private LinearLayout tabsContainer;
  16. private ViewPager pager;
  17. private int tabCount;
  18. private static final String TAG = "xujun";
  19. private int currentPosition = 0;
  20. private float currentPositionOffset = 0f;
  21. private Paint rectPaint;
  22. private Paint dividerPaint;
  23. private int indicatorColor = 0xFF666666;
  24. private int underlineColor = 0x1A000000;
  25. private int dividerColor = 0x1A000000;
  26. //表示是否扩展
  27. private boolean isExpand = false;
  28. //表示下滑线的长度是否与标题字体的长度一样
  29. private boolean isSame = false;
  30. private boolean textAllCaps = true;
  31. private int scrollOffset = 52;
  32. private int indicatorHeight = 8;
  33. private int underlineHeight = 2;
  34. private int dividerPadding = 12;
  35. //表示自己之间的间隔
  36. private int horizontalPadding = 24;
  37. private int verticalPadding = 10;
  38. private int dividerWidth = 1;
  39. private int tabTextSize = 12;
  40. private int tabTextColor = 0xFF666666;
  41. private Typeface tabTypeface = null;
  42. private int tabTypefaceStyle = Typeface.BOLD;
  43. private int lastScrollX = -1;
  44. private int tabBackgroundResId = R.drawable.background_tab;
  45. //Indicator的样式
  46. private IndicatorMode curMode = IndicatorMode.MODE_WRAP_EXPAND_SAME;
  47. private Locale locale;
  48. public TabPagerIndicator(Context context) {
  49. this(context, null);
  50. }
  51. public TabPagerIndicator(Context context, AttributeSet attrs) {
  52. this(context, attrs, 0);
  53. }
  54. public TabPagerIndicator(Context context, AttributeSet attrs, int defStyle) {
  55. super(context, attrs, defStyle);
  56. setFillViewport(true);
  57. setWillNotDraw(false);
  58. tabsContainer = new LinearLayout(context);
  59. tabsContainer.setOrientation(LinearLayout.HORIZONTAL);
  60. tabsContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
  61. LayoutParams.MATCH_PARENT));
  62. addView(tabsContainer);
  63. //根据IndicatorMode初始化各个变量
  64. setIndicatorMode(curMode);
  65. //初始化自定义属性
  66. obtainAttrs(context, attrs);
  67. rectPaint = new Paint();
  68. rectPaint.setAntiAlias(true);
  69. rectPaint.setStyle(Style.FILL);
  70. dividerPaint = new Paint();
  71. dividerPaint.setAntiAlias(true);
  72. dividerPaint.setStrokeWidth(dividerWidth);
  73. wrapTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
  74. LayoutParams.MATCH_PARENT);
  75. expandedTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);
  76. if (locale == null) {
  77. locale = getResources().getConfiguration().locale;
  78. }
  79. }
  80. public void setIndicatorMode(IndicatorMode indicatorMode) {
  81. this.setIndicatorMode(indicatorMode, false);
  82. }
  83. public void setIndicatorMode(IndicatorMode indicatorMode, boolean isNotify) {
  84. switch (indicatorMode) {
  85. case MODE_WRAP_EXPAND_SAME:
  86. isExpand = false;
  87. isSame = true;
  88. break;
  89. case MODE_WRAP_EXPAND_NOSAME:
  90. isExpand = false;
  91. isSame = false;
  92. break;
  93. case MODE_WEIGHT_EXPAND_NOSAME:
  94. isExpand = true;
  95. isSame = false;
  96. break;
  97. case MODE_WEIGHT_EXPAND_SAME:
  98. isExpand = true;
  99. isSame = true;
  100. break;
  101. }
  102. this.curMode = indicatorMode;
  103. if (isNotify) {
  104. notifyDataSetChanged();
  105. }
  106. }
  107. private void obtainAttrs(Context context, AttributeSet attrs) {
  108. DisplayMetrics dm = getResources().getDisplayMetrics();
  109. scrollOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, scrollOffset,
  110. dm);
  111. indicatorHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
  112. indicatorHeight, dm);
  113. underlineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
  114. underlineHeight, dm);
  115. dividerPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
  116. dividerPadding, dm);
  117. horizontalPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
  118. horizontalPadding, dm);
  119. dividerWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerWidth,
  120. dm);
  121. tabTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, tabTextSize, dm);
  122. // get system attrs (android:textSize and android:textColor)
  123. TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
  124. tabTextSize = a.getDimensionPixelSize(0, tabTextSize);
  125. tabTextColor = a.getColor(1, tabTextColor);
  126. a.recycle();
  127. // get custom attrs
  128. a = context.obtainStyledAttributes(attrs, R.styleable.TabPagerIndicator);
  129. indicatorColor = a.getColor(R.styleable.TabPagerIndicator_pstsIndicatorColor,
  130. indicatorColor);
  131. underlineColor = a.getColor(R.styleable.TabPagerIndicator_pstsUnderlineColor,
  132. underlineColor);
  133. dividerColor = a.getColor(R.styleable.TabPagerIndicator_pstsDividerColor, dividerColor);
  134. indicatorHeight = a.getDimensionPixelSize(R.styleable
  135. .TabPagerIndicator_pstsIndicatorHeight, indicatorHeight);
  136. underlineHeight = a.getDimensionPixelSize(R.styleable
  137. .TabPagerIndicator_pstsUnderlineHeight, underlineHeight);
  138. dividerPadding = a.getDimensionPixelSize(R.styleable
  139. .TabPagerIndicator_pstsDividerPadding, dividerPadding);
  140. horizontalPadding = a.getDimensionPixelSize(R.styleable
  141. .TabPagerIndicator_pstsTabPaddingLeftRight, horizontalPadding);
  142. tabBackgroundResId = a.getResourceId(R.styleable.TabPagerIndicator_pstsTabBackground,
  143. tabBackgroundResId);
  144. isExpand = a.getBoolean(R.styleable.TabPagerIndicator_pstsShouldExpand,
  145. isExpand);
  146. scrollOffset = a.getDimensionPixelSize(R.styleable.TabPagerIndicator_pstsScrollOffset,
  147. scrollOffset);
  148. textAllCaps = a.getBoolean(R.styleable.TabPagerIndicator_pstsTextAllCaps, textAllCaps);
  149. a.recycle();
  150. }
  151. public void setViewPager(ViewPager pager) {
  152. this.pager = pager;
  153. if (pager.getAdapter() == null) {
  154. throw new IllegalStateException("ViewPager does not have adapter instance.");
  155. }
  156. pager.addOnPageChangeListener(pageListener);
  157. notifyDataSetChanged();
  158. }
  159. public void addOnPageChangeListener(OnPageChangeListener listener) {
  160. this.delegatePageListener = listener;
  161. }
  162. public void notifyDataSetChanged() {
  163. //先移除掉所有的View ,防止重复添加
  164. tabsContainer.removeAllViews();
  165. tabCount = pager.getAdapter().getCount();
  166. for (int i = 0; i < tabCount; i++) {
  167. //区分是文字还是Icon的导航
  168. if (pager.getAdapter() instanceof IconTabProvider) {
  169. addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconResId(i));
  170. } else {
  171. addTextTab(i, pager.getAdapter().getPageTitle(i).toString());
  172. }
  173. }
  174. updateTabStyles();
  175. //监听视图树,在绘制完毕后调用相关的方法完成初始化工作
  176. getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
  177. @SuppressWarnings("deprecation")
  178. @SuppressLint("NewApi")
  179. @Override
  180. public void onGlobalLayout() {
  181. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
  182. getViewTreeObserver().removeGlobalOnLayoutListener(this);
  183. } else {
  184. getViewTreeObserver().removeOnGlobalLayoutListener(this);
  185. }
  186. currentPosition = pager.getCurrentItem();
  187. scrollToChild(currentPosition, 0);
  188. }
  189. });
  190. }
  191. private void addTextTab(final int position, String title) {
  192. TextView tab = new TextView(getContext());
  193. tab.setText(title);
  194. tab.setGravity(Gravity.CENTER);
  195. tab.setSingleLine();
  196. addTab(position, tab);
  197. }
  198. private void addIconTab(final int position, int resId) {
  199. ImageButton tab = new ImageButton(getContext());
  200. tab.setImageResource(resId);
  201. addTab(position, tab);
  202. }
  203. //添加孩子
  204. private void addTab(final int position, View tab) {
  205. tab.setFocusable(true);
  206. // 设置监听
  207. tab.setOnClickListener(new OnClickListener() {
  208. @Override
  209. public void onClick(View v) {
  210. pager.setCurrentItem(position);
  211. }
  212. });
  213. // 这里我们下划线的 高度是否与文字的长度保持一致,是通过给孩子设置padding或者margin实现的
  214. // 注意与onDraw里面的逻辑结合起来
  215. if (!isSame) {
  216. tab.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
  217. wrapTabLayoutParams.setMargins(0, 0, 0, 0);
  218. expandedTabLayoutParams.setMargins(0, 0, 0, 0);
  219. } else {
  220. wrapTabLayoutParams.setMargins(horizontalPadding, verticalPadding,
  221. horizontalPadding, verticalPadding);
  222. expandedTabLayoutParams.setMargins(horizontalPadding, verticalPadding,
  223. horizontalPadding, verticalPadding);
  224. }
  225. //根据是否可以扩展来设置不同的layoutParams
  226. tabsContainer.addView(tab, position, isExpand ? expandedTabLayoutParams :
  227. wrapTabLayoutParams);
  228. }
  229. private void updateTabStyles() {
  230. for (int i = 0; i < tabCount; i++) {
  231. View v = tabsContainer.getChildAt(i);
  232. v.setBackgroundResource(tabBackgroundResId);
  233. if (v instanceof TextView) {
  234. TextView tab = (TextView) v;
  235. tab.setTextSize(TypedValue.COMPLEX_UNIT_PX, tabTextSize);
  236. tab.setTypeface(tabTypeface, tabTypefaceStyle);
  237. tab.setTextColor(tabTextColor);
  238. // setAllCaps() is only available from API 14, so the upper case is made manually
  239. // if we are on a pre-ICS-build
  240. if (textAllCaps) {
  241. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  242. tab.setAllCaps(true);
  243. } else {
  244. tab.setText(tab.getText().toString().toUpperCase(locale));
  245. }
  246. }
  247. }
  248. }
  249. }
  250. // 调用这个方法是HorizontalScrollView滑动到相应的位置
  251. private void scrollToChild(int position, int offset) {
  252. if (tabCount == 0) {
  253. return;
  254. }
  255. int newScrollX;
  256. View child = tabsContainer.getChildAt(position);
  257. int left = child.getLeft();
  258. if (isSame) {
  259. newScrollX = left + offset - horizontalPadding;
  260. } else {
  261. newScrollX = left + offset;
  262. }
  263. if (position > 0 || offset > 0) {
  264. newScrollX -= scrollOffset;
  265. }
  266. Log.i(TAG, "scrollToChild:newScrollX=" + newScrollX);
  267. if (newScrollX != lastScrollX) {
  268. lastScrollX = newScrollX;
  269. scrollTo(newScrollX, 0);
  270. }
  271. }
  272. @Override
  273. protected void onDraw(Canvas canvas) {
  274. super.onDraw(canvas);
  275. if (isInEditMode() || tabCount == 0) {
  276. return;
  277. }
  278. final int height = getHeight();
  279. // draw indicator line
  280. rectPaint.setColor(indicatorColor);
  281. // default: line below current tab
  282. View currentTab = tabsContainer.getChildAt(currentPosition);
  283. float lineLeft = currentTab.getLeft();
  284. float lineRight = currentTab.getRight();
  285. // if there is an offset, start interpolating left and right coordinates between current
  286. // and next tab
  287. if (currentPositionOffset > 0f && currentPosition < tabCount - 1) {
  288. View nextTab = tabsContainer.getChildAt(currentPosition + 1);
  289. final float nextTabLeft = nextTab.getLeft();
  290. final float nextTabRight = nextTab.getRight();
  291. lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) *
  292. lineLeft);
  293. lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) *
  294. lineRight);
  295. }
  296. canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint);
  297. // draw underline
  298. rectPaint.setColor(underlineColor);
  299. canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint);
  300. // draw divider
  301. dividerPaint.setColor(dividerColor);
  302. for (int i = 0; i < tabCount - 1; i++) {
  303. View tab = tabsContainer.getChildAt(i);
  304. if (!isSame) {
  305. canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(),
  306. height - dividerPadding, dividerPaint);
  307. } else {
  308. canvas.drawLine(tab.getRight() + horizontalPadding, dividerPadding,
  309. tab.getRight() + horizontalPadding, height - dividerPadding, dividerPaint);
  310. }
  311. }
  312. }
  313. private class PageListener implements OnPageChangeListener {
  314. @Override
  315. public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
  316. currentPosition = position;
  317. currentPositionOffset = positionOffset;
  318. View child = tabsContainer.getChildAt(position);
  319. int width = child.getWidth();
  320. if (isSame) {
  321. width += horizontalPadding * 2;
  322. }
  323. Log.i(TAG, "onPageScrolled:width=" + width);
  324. // 调用这个方法是HorizontalScrollView滑动到相应的位置
  325. scrollToChild(position, (int) (positionOffset * width));
  326. //调用这个方法重新绘制
  327. invalidate();
  328. if (delegatePageListener != null) {
  329. delegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
  330. }
  331. }
  332. @Override
  333. public void onPageScrollStateChanged(int state) {
  334. if (delegatePageListener != null) {
  335. delegatePageListener.onPageScrollStateChanged(state);
  336. }
  337. }
  338. @Override
  339. public void onPageSelected(int position) {
  340. if (delegatePageListener != null) {
  341. delegatePageListener.onPageSelected(position);
  342. }
  343. }
  344. }
  345. public void setIndicatorColor(int indicatorColor) {
  346. this.indicatorColor = indicatorColor;
  347. invalidate();
  348. }
  349. public void setIndicatorColorResource(int resId) {
  350. this.indicatorColor = getResources().getColor(resId);
  351. invalidate();
  352. }
  353. public int getIndicatorColor() {
  354. return this.indicatorColor;
  355. }
  356. public void setIndicatorHeight(int indicatorLineHeightPx) {
  357. this.indicatorHeight = indicatorLineHeightPx;
  358. invalidate();
  359. }
  360. public int getIndicatorHeight() {
  361. return indicatorHeight;
  362. }
  363. public void setUnderlineColor(int underlineColor) {
  364. this.underlineColor = underlineColor;
  365. invalidate();
  366. }
  367. public void setUnderlineColorResource(int resId) {
  368. this.underlineColor = getResources().getColor(resId);
  369. invalidate();
  370. }
  371. public int getUnderlineColor() {
  372. return underlineColor;
  373. }
  374. public void setDividerColor(int dividerColor) {
  375. this.dividerColor = dividerColor;
  376. invalidate();
  377. }
  378. public void setDividerColorResource(int resId) {
  379. this.dividerColor = getResources().getColor(resId);
  380. invalidate();
  381. }
  382. public int getDividerColor() {
  383. return dividerColor;
  384. }
  385. public void setUnderlineHeight(int underlineHeightPx) {
  386. this.underlineHeight = underlineHeightPx;
  387. invalidate();
  388. }
  389. public int getUnderlineHeight() {
  390. return underlineHeight;
  391. }
  392. public void setDividerPadding(int dividerPaddingPx) {
  393. this.dividerPadding = dividerPaddingPx;
  394. invalidate();
  395. }
  396. public int getDividerPadding() {
  397. return dividerPadding;
  398. }
  399. public void setScrollOffset(int scrollOffsetPx) {
  400. this.scrollOffset = scrollOffsetPx;
  401. invalidate();
  402. }
  403. public int getScrollOffset() {
  404. return scrollOffset;
  405. }
  406. public void setExpand(boolean expand) {
  407. this.isExpand = expand;
  408. requestLayout();
  409. }
  410. public boolean getExpand() {
  411. return isExpand;
  412. }
  413. public boolean isTextAllCaps() {
  414. return textAllCaps;
  415. }
  416. public void setAllCaps(boolean textAllCaps) {
  417. this.textAllCaps = textAllCaps;
  418. }
  419. public void setTextSize(int textSizePx) {
  420. this.tabTextSize = textSizePx;
  421. updateTabStyles();
  422. }
  423. public int getTextSize() {
  424. return tabTextSize;
  425. }
  426. public void setTextColor(int textColor) {
  427. this.tabTextColor = textColor;
  428. updateTabStyles();
  429. }
  430. public void setTextColorResource(int resId) {
  431. this.tabTextColor = getResources().getColor(resId);
  432. updateTabStyles();
  433. }
  434. public int getTextColor() {
  435. return tabTextColor;
  436. }
  437. public void setTypeface(Typeface typeface, int style) {
  438. this.tabTypeface = typeface;
  439. this.tabTypefaceStyle = style;
  440. updateTabStyles();
  441. }
  442. public void setTabBackground(int resId) {
  443. this.tabBackgroundResId = resId;
  444. }
  445. public int getTabBackground() {
  446. return tabBackgroundResId;
  447. }
  448. public void setTabPaddingLeftRight(int paddingPx) {
  449. this.horizontalPadding = paddingPx;
  450. updateTabStyles();
  451. }
  452. public int getTabPaddingLeftRight() {
  453. return horizontalPadding;
  454. }
  455. @Override
  456. public void onRestoreInstanceState(Parcelable state) {
  457. SavedState savedState = (SavedState) state;
  458. super.onRestoreInstanceState(savedState.getSuperState());
  459. currentPosition = savedState.currentPosition;
  460. requestLayout();
  461. }
  462. @Override
  463. public Parcelable onSaveInstanceState() {
  464. Parcelable superState = super.onSaveInstanceState();
  465. SavedState savedState = new SavedState(superState);
  466. savedState.currentPosition = currentPosition;
  467. return savedState;
  468. }
  469. //用来保存状态
  470. static class SavedState extends BaseSavedState {
  471. int currentPosition;
  472. public SavedState(Parcelable superState) {
  473. super(superState);
  474. }
  475. private SavedState(Parcel in) {
  476. super(in);
  477. currentPosition = in.readInt();
  478. }
  479. @Override
  480. public void writeToParcel(Parcel dest, int flags) {
  481. super.writeToParcel(dest, flags);
  482. dest.writeInt(currentPosition);
  483. }
  484. public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
  485. @Override
  486. public SavedState createFromParcel(Parcel in) {
  487. return new SavedState(in);
  488. }
  489. @Override
  490. public SavedState[] newArray(int size) {
  491. return new SavedState[size];
  492. }
  493. };
  494. }
  495. /**
  496. * 定义4种模式
  497. */
  498. public enum IndicatorMode {
  499. // 给枚举传入自定义的int值
  500. MODE_WRAP_EXPAND_SAME(1),// 可扩展,导航线跟标题相等
  501. MODE_WRAP_EXPAND_NOSAME(2),// 可扩展,导标不相等
  502. MODE_WEIGHT_EXPAND_SAME(3),// 可扩展,导航线跟标题相等
  503. MODE_WEIGHT_EXPAND_NOSAME(4);// 可扩展,导标不相等
  504. private int value;
  505. IndicatorMode(int value) {
  506. this.value = value;
  507. }
  508. public int getValue() {
  509. return value;
  510. }
  511. }
  512. }

思路主要 可以分为以下几个步骤

1)在构造方法里面初始化各种工作,包括一些自定义属性,画笔等等

  1. public TabPagerIndicator(Context context, AttributeSet attrs, int defStyle) {
  2. super(context, attrs, defStyle);
  3. //初始化各种工作
  4. //根据IndicatorMode初始化各个变量
  5. setIndicatorMode(curMode);
  6. //初始化自定义属性
  7. obtainAttrs(context, attrs);
  8. rectPaint = new Paint();
  9. rectPaint.setAntiAlias(true);
  10. rectPaint.setStyle(Style.FILL);
  11. dividerPaint = new Paint();
  12. dividerPaint.setAntiAlias(true);
  13. dividerPaint.setStrokeWidth(dividerWidth);
  14. if (locale == null) {
  15. locale = getResources().getConfiguration().locale;
  16. }
  17. }

2)通过setViewPager()这个方法将控件与ViewPager联系起来

  1. public void setViewPager(ViewPager pager) {
  2. this.pager = pager;
  3. if (pager.getAdapter() == null) {
  4. throw new IllegalStateException("ViewPager does not have adapter instance.");
  5. }
  6. pager.addOnPageChangeListener(pageListener);
  7. notifyDataSetChanged();
  8. }
  9. public void notifyDataSetChanged() {
  10. //先移除掉所有的View ,防止重复添加
  11. tabsContainer.removeAllViews();
  12. tabCount = pager.getAdapter().getCount();
  13. for (int i = 0; i < tabCount; i++) {
  14. //区分是文字还是Icon的导航
  15. if (pager.getAdapter() instanceof IconTabProvider) {
  16. addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconResId(i));
  17. } else {
  18. addTextTab(i, pager.getAdapter().getPageTitle(i).toString());
  19. }
  20. }
  21. updateTabStyles();
  22. //监听视图树,在绘制完毕后调用相关的方法完成初始化工作
  23. getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
  24. @SuppressWarnings("deprecation")
  25. @SuppressLint("NewApi")
  26. @Override
  27. public void onGlobalLayout() {
  28. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
  29. getViewTreeObserver().removeGlobalOnLayoutListener(this);
  30. } else {
  31. getViewTreeObserver().removeOnGlobalLayoutListener(this);
  32. }
  33. currentPosition = pager.getCurrentItem();
  34. scrollToChild(currentPosition, 0);
  35. }
  36. });
  37. }

3)在 onDraw里面根据不同的 Mode绘制不同的下划线样式

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

4)在ViewPager滑动的时候,会调用相应的方法来刷新界面,因为前面我们在setViewPager的时候为其添加pageListener监听器

  1. public void setViewPager(ViewPager pager) {
  2. //省略了若干方法
  3. pager.addOnPageChangeListener(pageListener);
  4. }
  5. private class PageListener implements OnPageChangeListener {
  6. @Override
  7. public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
  8. currentPosition = position;
  9. currentPositionOffset = positionOffset;
  10. View child = tabsContainer.getChildAt(position);
  11. int width = child.getWidth();
  12. if (isSame) {
  13. width += horizontalPadding * 2;
  14. }
  15. Log.i(TAG, "onPageScrolled:width=" + width);
  16. // 调用这个方法是HorizontalScrollView滑动到相应的位置
  17. scrollToChild(position, (int) (positionOffset * width));
  18. //调用这个方法重新绘制
  19. invalidate();
  20. if (delegatePageListener != null) {
  21. delegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
  22. }
  23. }
  24. @Override
  25. public void onPageScrollStateChanged(int state) {
  26. if (delegatePageListener != null) {
  27. delegatePageListener.onPageScrollStateChanged(state);
  28. }
  29. }
  30. @Override
  31. public void onPageSelected(int position) {
  32. if (delegatePageListener != null) {
  33. delegatePageListener.onPageSelected(position);
  34. }
  35. }
  36. }

源码到此分析为止

题外话

1)大家在使用过程实现由什么缺陷的,希望大家能够及时告知,同时大家有空的话也可以到我的github上面start或者fork,谢谢,你们的支持就是我的最大动力

2)在接下来的日子我会尝试封装好多种效果,不过估计得忙过这阵子,因为最近在准备校招,有时候看那些算法题目,感觉世界都没爱了,哈哈,放松一下,大家有什么好的面试经验也来分享一下

源码参考项目:PagerSlidingTabStrip

我的简书主页

转载请注明原博客地址:

Github例子源码下载地址:

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注