Android自定义轮播图指示器

batmanlf 3年前
   <p>轮播图在项目中一般会使用VeiwPager来实现,同时还会关联轮播指示器。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1340681258f51eb8babebc9d055fadc3.gif"></p>    <p style="text-align:center">轮播指示器效果gif</p>    <p>改造ViewPager的OnPageChangedListener,添加自定义的ViewPager滚动监听器(直接将上一篇文章的代码贴过来了)</p>    <pre>  <code class="language-java">/**   * ViewPager辅助类   */  public class ViewPagerHelper implements ViewPager.OnPageChangeListener {        private double mLastPositionOffsetSum;  // 上一次滑动总的偏移量      private OnPageScrollListener mOnPageScrollListener;        @Override      public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {          // 当前总的偏移量          float currentPositionOffsetSum = position + positionOffset;          // 上次滑动的总偏移量大于此次滑动的总偏移量,页面从右向左进入(手指从右向左滑动)          boolean rightToLeft = mLastPositionOffsetSum <= currentPositionOffsetSum;          if (currentPositionOffsetSum == mLastPositionOffsetSum) return;          int enterPosition;          int leavePosition;          float percent;          if (rightToLeft) {  // 从右向左滑              enterPosition = (positionOffset == 0.0f) ? position : position + 1;              leavePosition = enterPosition - 1;              percent = (positionOffset == 0.0f) ? 1.0f : positionOffset;          } else {            // 从左向右滑              enterPosition = position;              leavePosition = position + 1;              percent = 1 - positionOffset;          }          if (mOnPageScrollListener != null) {              mOnPageScrollListener.onPageScroll(enterPosition, leavePosition, percent);          }          mLastPositionOffsetSum = currentPositionOffsetSum;      }        @Override      public void onPageSelected(int position) {          if (mOnPageScrollListener != null) {              mOnPageScrollListener.onPageSelected(position);          }      }        /**       * @param state 当前滑动状态       *              ViewPager.SCROLL_STATE_IDLE     页面处于闲置、稳定状态,即没被拖动也没惯性滑动       *              ViewPager.SCROLL_STATE_DRAGGING 页面正在被用户拖动,即手指正在拖动状态       *              Viewpager.SCROLL_STATE_SETTLING 页面处于即将到达最终状态的过程,即手指松开后惯性滑动状态       */      @Override      public void onPageScrollStateChanged(int state) {          if (mOnPageScrollListener != null) {              mOnPageScrollListener.onPageScrollStateChanged(state);          }      }        public void bindScrollListener(ViewPager viewPager, OnPageScrollListener onPageScrollListener) {          mOnPageScrollListener = onPageScrollListener;          viewPager.addOnPageChangeListener(this);      }  }</code></pre>    <pre>  <code class="language-java">/**   * ViewPage的页面滚动监听器   */  public interface OnPageScrollListener {      /**       * 页面滚动时调用       *       * @param enterPosition 进入页面的位置       * @param leavePosition 离开的页面的位置       * @param percent       滑动百分比       */      void onPageScroll(int enterPosition, int leavePosition, float percent);        /**       * 页面选中时调用       *       * @param position 选中页面的位置       */      void onPageSelected(int position);        /**       * 页面滚动状态变化时调用       *       * @param state 页面的滚动状态       */      void onPageScrollStateChanged(int state);  }</code></pre>    <p>根据两种指示器的效果分析,都是通过回调onPageScroll方法中不断变化的enterPositon、leavePosition和percent来实现。</p>    <p><strong>NumberIndicator(数字指示器)</strong></p>    <p>当ViewPager页面从右向左滑动时,指示器中对应页面的数字从下往上滚动,页面停止,数字停止在中间位置。</p>    <p>当ViewPager页面从左向右滑动时,指示器中对应页面的数字从下往上滚动,页面停止,数字停止在中间位置。</p>    <p>最终指示器效果可以采用绘制View的方式来实现(当然也可以采用组合控件的方式来实现)。ViewPager页面滑动时,不断重绘View,达到指示数字上下滑动的效果。</p>    <pre>  <code class="language-java">/**   * 数字指示器   */  public class NumberIndicater extends View implements OnPageScrollListener {        private Context mContext;        private int mCircleColor;      private int mCircleSize;      private int mNumberColor;      private int mNumberSize;        private int mCount;      private int mCurrent;        private Paint mCirclePaint;      private Paint mTextPaint;        private float offset;       // 页面偏移百分比      private boolean isUp;       // 指示器数字是否向上滑动        public NumberIndicater(Context context) {          this(context, null);      }        public NumberIndicater(Context context, AttributeSet attrs) {          this(context, attrs, 0);      }        public NumberIndicater(Context context, AttributeSet attrs, int defStyleAttr) {          super(context, attrs, defStyleAttr);          initAttrs(context, attrs);          initPaint();      }        /**       * 初始化属性       *       * @param context       * @param attrs       */      private void initAttrs(Context context, AttributeSet attrs) {          mContext = context;          mCircleSize = dp2px(48f);          mCircleColor = 0xfffdd63b;          mNumberSize = sp2px(14f);          mNumberColor = 0xff353535;          // 自定义属性          TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NumberIndicater);          mCircleColor = ta.getColor(R.styleable.NumberIndicater_circle_color, mCircleColor);          mCircleSize = (int) ta.getDimension(R.styleable.NumberIndicater_circle_size, mCircleSize);          mNumberColor = ta.getColor(R.styleable.NumberIndicater_number_color, mNumberColor);          mNumberSize = (int) ta.getDimension(R.styleable.NumberIndicater_number_size, mNumberSize);          ta.recycle();      }        private void initPaint() {          mCirclePaint = new Paint();          mCirclePaint.setAntiAlias(true);          mCirclePaint.setColor(mCircleColor);          mCirclePaint.setStyle(Paint.Style.FILL_AND_STROKE);            mTextPaint = new Paint();          mTextPaint.setAntiAlias(true);          mTextPaint.setTextAlign(Paint.Align.LEFT);          mTextPaint.setColor(mNumberColor);          mTextPaint.setTextSize(mNumberSize);            offset = 1;          mCount = 3;          mCurrent = 1;          isUp = true;      }        @Override      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {          // 设置测量后的尺寸          setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));      }        private int measure(int measureSpec) {          int size = 0;          int specMode = MeasureSpec.getMode(measureSpec);          int specSize = MeasureSpec.getSize(measureSpec);          switch (specMode) {              case MeasureSpec.EXACTLY:                  size = specSize;                  break;              case MeasureSpec.AT_MOST:                  size = mCircleSize;                  break;          }          return size;      }        @Override      protected void onDraw(Canvas canvas) {          super.onDraw(canvas);          // 绘制圆形底图          canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, mCircleSize / 2f, mCirclePaint);          // 绘制分割线          drawSplit(canvas);          // 绘制右边总数数字          drawTotleNumber(canvas);          // 绘制左边指示数字          drawIndicatNumber(canvas);      }        private void drawSplit(Canvas canvas) {          String text = "/";          float width = mTextPaint.measureText(text);          float x = (getWidth() - width) / 2f;          Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();          float y = getHeight() / 2f + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2f;          // x为绘制文本左边缘距离X轴的距离,y为绘制文本基线距离Y轴的位置          canvas.drawText(text, x, y, mTextPaint);      }        private void drawTotleNumber(Canvas canvas) {          String text = String.valueOf(mCount);          float x = getWidth() / 2f + mTextPaint.measureText("/") / 2f + 3;          Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();          float y = getHeight() / 2f + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2f;          canvas.drawText(text, x, y, mTextPaint);      }        private void drawIndicatNumber(Canvas canvas) {          mTextPaint.setTextSize(mNumberSize * 1.3f);          String text = String.valueOf(mCurrent);          Rect rect = new Rect();          // 获取文本的宽度          float width = mTextPaint.measureText(text);          // 获取文本的高度          mTextPaint.getTextBounds(text, 0, text.length(), rect);          float height = rect.height();          // 文本左边缘距离X轴的距离          float x = getWidth() / 2f - mTextPaint.measureText("/") / 2f - 3 - width;          Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();          // 文本基线位置距离Y轴的距离          float y = getHeight() / 2f + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2f;          if (isUp) { // 指示数字向上滑动              y = offset * y + (1 - offset) * (getHeight() / 2f - mCircleSize / 2f + mCircleSize + height);          } else {    // 指示数字向下滑动              y = offset * y + (1 - offset) * (getHeight() / 2f - mCircleSize / 2f);          }          canvas.drawText(text, x, y, mTextPaint);          mTextPaint.setTextSize(mNumberSize);      }        /**       * 将指示器绑定到ViewPager       *       * @param viewPager view pager       */      public void bindViewPager(ViewPager viewPager) {          if (viewPager != null && viewPager.getAdapter() != null) {              mCount = viewPager.getAdapter().getCount();              new ViewPagerHelper().bindScrollListener(viewPager, this);              invalidate();    // 绑定ViewPager后指示器重绘,因为指示器的数字与初始的可能不同          }      }        @Override      public void onPageScroll(int enterPosition, int leavePosition, float percent) {          mCurrent = enterPosition + 1;          offset = percent;          isUp = enterPosition > leavePosition;          postInvalidate();       // 滑动过程中不断重绘      }        @Override      public void onPageSelected(int position) {        }        @Override      public void onPageScrollStateChanged(int state) {        }        private int dp2px(float dpValue) {          return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,                  dpValue,                  mContext.getResources().getDisplayMetrics());      }        private int sp2px(float dpValue) {          return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,                  dpValue,                  mContext.getResources().getDisplayMetrics());      }  }</code></pre>    <p><strong>PointIndicator(圆点指示器)</strong></p>    <p>根据ViewPager滑动的位置和百分比,动态绘制指示小圆点。通过Viewpger滑动过程中的enterPosition和leavePosition以及滑动百分比percent来计算出滑动小圆点的左边位置即可。</p>    <pre>  <code class="language-java">/**   * 圆点指示器   */  public class PointIndicator extends View implements OnPageScrollListener {        private Context mContext;        private int mNormalColor;      private int mSelectColor;      private int mPointSize;      private int mPointSpace;        private Paint mNormalPaint;      private Paint mSelectPaint;        private int mCount;      private int enterPosition;      private int leavePosition;      private float percent;        public PointIndicator(Context context) {          this(context, null);      }        public PointIndicator(Context context, AttributeSet attrs) {          this(context, attrs, 0);      }        public PointIndicator(Context context, AttributeSet attrs, int defStyleAttr) {          super(context, attrs, defStyleAttr);          initAttrs(context, attrs);          initPaint();      }        private void initPaint() {          mNormalPaint = new Paint();          mNormalPaint.setColor(mNormalColor);          mNormalPaint.setAntiAlias(true);            mSelectPaint = new Paint();          mSelectPaint.setColor(mSelectColor);          mSelectPaint.setAntiAlias(true);            mCount = 4;      }        private void initAttrs(Context context, AttributeSet attrs) {          mContext = context;            mNormalColor = 0x66cccccc;          mSelectColor = 0xfffdd63b;          mPointSize = dp2px(3f);          mPointSpace = dp2px(3f);            // 自定义属性          TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PointIndicator);          mNormalColor = ta.getColor(R.styleable.PointIndicator_normal_color, mNormalColor);          mSelectColor = ta.getColor(R.styleable.PointIndicator_select_color, mSelectColor);          mPointSize = (int) ta.getDimension(R.styleable.PointIndicator_point_size, mPointSize);          mPointSpace = (int) ta.getDimension(R.styleable.PointIndicator_point_space, mPointSpace);          ta.recycle();      }        @Override      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {          setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));      }        private int measureWidth(int measureSpec) {          int size = 0;          int specMode = MeasureSpec.getMode(measureSpec);          int specSize = MeasureSpec.getSize(measureSpec);          switch (specMode) {              case MeasureSpec.EXACTLY:                  size = specSize;                  break;              case MeasureSpec.AT_MOST:                  size = mCount * mPointSize + (mCount - 1) * mPointSpace;                  break;          }          return size;      }        private int measureHeight(int measureSpec) {          int size = 0;          int specMode = MeasureSpec.getMode(measureSpec);          int specSize = MeasureSpec.getSize(measureSpec);          switch (specMode) {              case MeasureSpec.EXACTLY:                  size = specSize;                  break;              case MeasureSpec.AT_MOST:                  size = mPointSize;                  break;          }          return size;      }        @Override      protected void onDraw(Canvas canvas) {          // 绘制normalPoint          drawNormalPoint(canvas);          // 绘制selectPoint          drawSelectPoint(canvas);        }        private void drawSelectPoint(Canvas canvas) {          float cx;          if (enterPosition > leavePosition) {              cx = (leavePosition + 0.5f) * mPointSize                      + leavePosition * mPointSpace                      + (mPointSize + mPointSpace) * percent;          } else {              cx = (leavePosition + 0.5f) * mPointSize                      + leavePosition * mPointSpace                      - (mPointSize + mPointSpace) * percent;          }          float cy = getHeight() / 2;          float radius = mPointSize / 2f;          canvas.drawCircle(cx, cy, radius, mSelectPaint);      }        private void drawNormalPoint(Canvas canvas) {          for (int i = 0; i < mCount; i++) {              float cx = mPointSize / 2f + (mPointSize + mPointSpace) * i;              float cy = getHeight() / 2;              float radius = mPointSize / 2f;              canvas.drawCircle(cx, cy, radius, mNormalPaint);          }      }        public void bindViewPager(ViewPager viewPager) {          if (viewPager != null) {              if (viewPager.getAdapter() != null) {                  mCount = viewPager.getAdapter().getCount();                  new ViewPagerHelper().bindScrollListener(viewPager, this);                  requestLayout(); // 绑定ViewPager后指示器重新布局,因为指示器的数量和宽度可能有变化              }          }      }        @Override      public void onPageScroll(int enterPosition, int leavePosition, float percent) {          this.enterPosition = enterPosition;          this.leavePosition = leavePosition;          this.percent = percent;          postInvalidate();      }        @Override      public void onPageSelected(int position) {        }        @Override      public void onPageScrollStateChanged(int state) {        }        private int dp2px(float dpValue) {          return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,                  dpValue,                  mContext.getResources().getDisplayMetrics());      }    }</code></pre>    <p>涉及到的自定义属性:</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <resources>      <declare-styleable name="NumberIndicater">          <attr name="circle_color" format="color"/>          <attr name="circle_size" format="dimension"/>          <attr name="number_size" format="dimension"/>          <attr name="number_color" format="color"/>      </declare-styleable>        <declare-styleable name="PointIndicator">          <attr name="point_size" format="dimension"/>          <attr name="point_space" format="dimension"/>          <attr name="normal_color" format="color"/>          <attr name="select_color" format="color"/>      </declare-styleable>  </resources></code></pre>    <p>以上两种指示器都是通过自绘View的方式来实现。通过自定义的OnPageScrollListener还可以实现更多效果炫酷的指示器。</p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/60be684d897d</p>    <p> </p>