[关闭]
@xujun94 2016-08-21T08:41:47.000000Z 字数 7808 阅读 1520

Android打造不一样的新手引导页面(一)


转载请注明原博客地址:

本篇博客主要讲解怎样自定义一个circleIndicator控件?

下一遍博客主要讲解怎样更改ViewPager切换的效果, 预计明天晚上之前更新。

效果图如下

1)首先我们先来看一下要怎样使用我们的circleIndicator控件

其实很简单,值需要两个步骤

1) 在xml布局文件里面

  1. <RelativeLayout
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. >
  6. <android.support.v4.view.ViewPager
  7. android:layout_below="@id/rl_header"
  8. android:id="@+id/viewPager"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent"
  11. android:layout_marginTop="20dp">
  12. </android.support.v4.view.ViewPager>
  13. <com.xujun.administrator.customviewspecif.view.CirclePageIndicator
  14. android:id="@+id/circle_indicator"
  15. android:layout_width="match_parent"
  16. android:layout_height="wrap_content"
  17. android:layout_alignParentBottom="true"
  18. android:layout_marginBottom="20dp">
  19. </com.xujun.administrator.customviewspecif.view.CirclePageIndicator>
  20. </RelativeLayout>

2)在代码里面

  1. mViewPager = (ViewPager) findViewById(R.id.viewPager);
  2. mCirclePageIndicator = (CirclePageIndicator) findViewById(R.id.circle_indicator);
  3. //注意下面初始化的顺序不可以调换
  4. mFragemntAdapter = new BaseFragemntAdapter(
  5. getSupportFragmentManager(), mFragments);
  6. mViewPager.setAdapter(mFragemntAdapter);
  7. //将mCirclePageIndicator与我们的mViewPager绑定在一起
  8. mCirclePageIndicator.setViewPager(mViewPager);

扩展

1)在xml布局里面更改我们的样式

  1. xmlns:app="http://schemas.android.com/apk/res-auto"
  2. //例如更改我们移动小圆点的颜色
  3. app:fillColor="#fff"
  4. //其他属性的更改请参考以下我们自定义的属性
  5. <declare-styleable name="CirclePageIndicator">
  6. <!-- Whether or not the indicators should be centered. -->
  7. <attr name="centered" />
  8. <!-- Color of the filled circle that represents the current page. -->
  9. <attr name="fillColor" format="color" />
  10. <!-- Color of the filled circles that represents pages. -->
  11. <attr name="pageColor" format="color" />
  12. <!-- Orientation of the indicator. -->
  13. <attr name="android:orientation"/>
  14. <!-- Radius of the circles. This is also the spacing between circles. -->
  15. <attr name="radius" format="dimension" />
  16. <!-- Whether or not the selected indicator snaps to the circles. -->
  17. <attr name="snap" format="boolean" />
  18. <!-- Color of the open circles. -->
  19. <attr name="strokeColor" format="color" />
  20. <!-- Width of the stroke used to draw the circles. -->
  21. <attr name="strokeWidth" />
  22. <!-- View background -->
  23. <attr name="android:background"/>
  24. </declare-styleable>

2)在Java代码里面动态更改

  1. // 设置滑动的时候移动的小圆点是否跳跃
  2. mCirclePageIndicator.setSnap(false);
  3. //设置小圆点的半径
  4. mCirclePageIndicator.setRadius(10 * density);
  5. // 设置页面小圆点的颜色
  6. mCirclePageIndicator.setPageColor(0x880000FF);
  7. // 设置移动的小圆点的颜色
  8. mCirclePageIndicator.setFillColor(0xFF888888);
  9. // 设置外边框的颜色
  10. mCirclePageIndicator.setStrokeColor(0xFF000000);
  11. //设置外表框的宽度
  12. mCirclePageIndicator.setStrokeWidth(2 * density);

2)下面我们一起来看我们是怎样CircleIndicator是怎样实现的

大概可以分为以下几个步骤

  1. public CirclePageIndicator(Context context, AttributeSet attrs, int defStyle) {
  2. super(context, attrs, defStyle);
  3. if (isInEditMode()) return;
  4. //初始化自定义属性
  5. final Resources res = getResources();
  6. final int defaultPageColor = res.getColor(R.color.default_circle_indicator_page_color);
  7. final int defaultFillColor = res.getColor(R.color.default_circle_indicator_fill_color);
  8. final int defaultOrientation = res.getInteger(R.integer
  9. .default_circle_indicator_orientation);
  10. 在这里省略了若干方法
  11. a.recycle();
  12. final ViewConfiguration configuration = ViewConfiguration.get(context);
  13. mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
  14. }
  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. if (mOrientation == HORIZONTAL) {
  4. setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec));
  5. } else {
  6. setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec));
  7. }
  8. }
  9. /**
  10. * Determines the width of this view
  11. *
  12. * @param measureSpec A measureSpec packed into an int
  13. * @return The width of the view, honoring constraints from measureSpec
  14. */
  15. private int measureLong(int measureSpec) {
  16. int result;
  17. int specMode = MeasureSpec.getMode(measureSpec);
  18. int specSize = MeasureSpec.getSize(measureSpec);
  19. if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
  20. //We were told how big to be
  21. result = specSize;
  22. } else {
  23. //Calculate the width according the views count
  24. final int count = mViewPager.getAdapter().getCount();
  25. result = (int) (getPaddingLeft() + getPaddingRight()
  26. + (count * 2 * mRadius) + (count - 1) * mRadius + 1);
  27. //Respect AT_MOST value if that was what is called for by measureSpec
  28. if (specMode == MeasureSpec.AT_MOST) {
  29. result = Math.min(result, specSize);
  30. }
  31. }
  32. return result;
  33. }
  34. /**
  35. * Determines the height of this view
  36. *
  37. * @param measureSpec A measureSpec packed into an int
  38. * @return The height of the view, honoring constraints from measureSpec
  39. */
  40. private int measureShort(int measureSpec) {
  41. int result;
  42. int specMode = MeasureSpec.getMode(measureSpec);
  43. int specSize = MeasureSpec.getSize(measureSpec);
  44. if (specMode == MeasureSpec.EXACTLY) {
  45. //We were told how big to be
  46. result = specSize;
  47. } else {
  48. //Measure the height
  49. result = (int) (2 * mRadius + getPaddingTop() + getPaddingBottom() + 1);
  50. //Respect AT_MOST value if that was what is called for by measureSpec
  51. if (specMode == MeasureSpec.AT_MOST) {
  52. result = Math.min(result, specSize);
  53. }
  54. }
  55. return result;
  56. }
  1. @Override
  2. public void setViewPager(ViewPager view) {
  3. if (mViewPager == view) {
  4. return;
  5. }
  6. if (mViewPager != null) {
  7. mViewPager.addOnPageChangeListener(null);
  8. }
  9. if (view.getAdapter() == null) {
  10. throw new IllegalStateException("ViewPager does not have adapter instance.");
  11. }
  12. mViewPager = view;
  13. mViewPager.addOnPageChangeListener(this);
  14. invalidate();
  15. }

里面主要的逻辑简单来说就是判断我们的ViewPager是否已经设置adapter,没有的话抛出异常,接着监听ViewPager的PageChangListener事件。
调用invalidate()方法重新绘制CirclePagerIndicator

  1. @Override
  2. public void onPageScrollStateChanged(int state) {
  3. mScrollState = state;
  4. if (mListener != null) {
  5. mListener.onPageScrollStateChanged(state);
  6. }
  7. }
  8. @Override
  9. public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
  10. mCurrentPage = position;
  11. mPageOffset = positionOffset;
  12. invalidate();
  13. if (mListener != null) {
  14. mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
  15. }
  16. }
  17. @Override
  18. public void onPageSelected(int position) {
  19. if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) {
  20. mCurrentPage = position;
  21. mSnapPage = position;
  22. invalidate();
  23. }
  24. if (mListener != null) {
  25. mListener.onPageSelected(position);
  26. }
  27. }
  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. if (mViewPager == null) {
  5. return;
  6. }
  7. final int count = mViewPager.getAdapter().getCount();
  8. if (count == 0) {
  9. return;
  10. }
  11. if (mCurrentPage >= count) {
  12. setCurrentItem(count - 1);
  13. return;
  14. }
  15. int longSize;
  16. int longPaddingBefore;
  17. int longPaddingAfter;
  18. int shortPaddingBefore;
  19. // 根据方向的不同初始化各个变量
  20. if (mOrientation == HORIZONTAL) {
  21. longSize = getWidth();
  22. longPaddingBefore = getPaddingLeft();
  23. longPaddingAfter = getPaddingRight();
  24. shortPaddingBefore = getPaddingTop();
  25. } else {
  26. longSize = getHeight();
  27. longPaddingBefore = getPaddingTop();
  28. longPaddingAfter = getPaddingBottom();
  29. shortPaddingBefore = getPaddingLeft();
  30. }
  31. final float threeRadius = mRadius * 3;
  32. final float shortOffset = shortPaddingBefore + mRadius;
  33. float longOffset = longPaddingBefore + mRadius;
  34. /**
  35. * 居中显示的时候
  36. */
  37. if (mCentered) {
  38. longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f)
  39. - ((count * threeRadius) / 2.0f);
  40. }
  41. float dX;
  42. float dY;
  43. float pageFillRadius = mRadius;
  44. if (mPaintStroke.getStrokeWidth() > 0) {
  45. pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f;
  46. }
  47. //Draw stroked circles
  48. for (int iLoop = 0; iLoop < count; iLoop++) {
  49. float drawLong = longOffset + (iLoop * threeRadius);
  50. if (mOrientation == HORIZONTAL) {
  51. dX = drawLong;
  52. dY = shortOffset;
  53. } else {
  54. dX = shortOffset;
  55. dY = drawLong;
  56. }
  57. // Only paint fill if not completely transparent
  58. if (mPaintPageFill.getAlpha() > 0) {
  59. canvas.drawCircle(dX, dY, pageFillRadius, mPaintPageFill);
  60. }
  61. // Only paint stroke if a stroke width was non-zero
  62. if (pageFillRadius != mRadius) {
  63. canvas.drawCircle(dX, dY, mRadius, mPaintStroke);
  64. }
  65. }
  66. //下面绘制移动的实心圆
  67. float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius;
  68. //根据移动的实心圆是否跳跃计算偏移量
  69. if (!mSnap) {
  70. cx += mPageOffset * threeRadius;
  71. }
  72. if (mOrientation == HORIZONTAL) {
  73. dX = longOffset + cx;
  74. dY = shortOffset;
  75. } else {
  76. dX = shortOffset;
  77. dY = longOffset + cx;
  78. }
  79. canvas.drawCircle(dX, dY, mRadius, mPaintFill);
  80. }

其实核心就是根据方向的不同绘制我们的小圆点,那些偏移量是一些数学运算而已,不过别小看这些,计算这些偏移量还是挺繁琐的。

到此我们的源码分析为止


题外话

如果各位觉得还行的话,欢迎在github上面 star或者 fork,谢谢 ,github项目地址ViewPagerTabIndicator

关于页面导航器的,可以查看我的这一篇博客仿网易新闻的顶部导航指示器

源码github参考库ViewPagerIndicator:

转载请注明原博客地址:

源码下载地址:

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