package com.c9mj.platform.widget.inicator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PointF; import android.util.SparseArray; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import net.lucode.hackware.magicindicator.NavigatorHelper; import net.lucode.hackware.magicindicator.abs.IPagerNavigator; import net.lucode.hackware.magicindicator.buildins.ArgbEvaluatorHolder; import net.lucode.hackware.magicindicator.buildins.UIUtil; import java.util.ArrayList; import java.util.List; /** * author: liminjie * date: 2017/2/27 * desc: ScaleCircleNavigator可缩放圆点指示器 */ public class ScaleCircleNavigator extends View implements IPagerNavigator, NavigatorHelper.OnNavigatorScrollListener { private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final List<PointF> mCirclePoints = new ArrayList<>(); private final SparseArray<Float> mCircleRadiusArray = new SparseArray<>(); private final NavigatorHelper mNavigatorHelper = new NavigatorHelper(); private int mMinRadius; private int mMaxRadius; private int mNormalCircleColor = Color.parseColor("#bdbdbd"); private int mSelectedCircleColor = Color.parseColor("#ffffff"); private int mCircleSpacing; private int mCircleCount; // 事件回调 private boolean mTouchable; private ScaleCircleNavigator.OnCircleClickListener mCircleClickListener; private float mDownX; private float mDownY; private int mTouchSlop; private boolean mFollowTouch = true; // 是否跟随手指滑动 private Interpolator mStartInterpolator = new LinearInterpolator(); public ScaleCircleNavigator(Context context) { super(context); init(context); } private void init(Context context) { mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mMinRadius = UIUtil.dip2px(context, 2); mMaxRadius = UIUtil.dip2px(context, 3); mCircleSpacing = UIUtil.dip2px(context, 8); mNavigatorHelper.setNavigatorScrollListener(this); mNavigatorHelper.setSkimOver(true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } private int measureWidth(int widthMeasureSpec) { int mode = MeasureSpec.getMode(widthMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int result = 0; switch (mode) { case MeasureSpec.EXACTLY: result = width; break; case MeasureSpec.AT_MOST: case MeasureSpec.UNSPECIFIED: result = (mCircleCount - 1) * mMinRadius * 2 + mMaxRadius * 2 + (mCircleCount - 1) * mCircleSpacing + getPaddingLeft() + getPaddingRight(); break; default: break; } return result; } private int measureHeight(int heightMeasureSpec) { int mode = MeasureSpec.getMode(heightMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int result = 0; switch (mode) { case MeasureSpec.EXACTLY: result = height; break; case MeasureSpec.AT_MOST: case MeasureSpec.UNSPECIFIED: result = mMaxRadius * 2 + getPaddingTop() + getPaddingBottom(); break; default: break; } return result; } @Override protected void onDraw(Canvas canvas) { for (int i = 0, j = mCirclePoints.size(); i < j; i++) { PointF point = mCirclePoints.get(i); float radius = mCircleRadiusArray.get(i, (float) mMinRadius); mPaint.setColor(ArgbEvaluatorHolder.eval((radius - mMinRadius) / (mMaxRadius - mMinRadius), mNormalCircleColor, mSelectedCircleColor)); canvas.drawCircle(point.x, getHeight() / 2.0f, radius, mPaint); } } private void prepareCirclePoints() { mCirclePoints.clear(); if (mCircleCount > 0) { int y = Math.round(getHeight() / 2.0f); int centerSpacing = mMinRadius * 2 + mCircleSpacing; int startX = mMaxRadius + getPaddingLeft(); for (int i = 0; i < mCircleCount; i++) { PointF pointF = new PointF(startX, y); mCirclePoints.add(pointF); startX += centerSpacing; } } } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (mTouchable) { mDownX = x; mDownY = y; return true; } break; case MotionEvent.ACTION_UP: if (mCircleClickListener != null) { if (Math.abs(x - mDownX) <= mTouchSlop && Math.abs(y - mDownY) <= mTouchSlop) { float max = Float.MAX_VALUE; int index = 0; for (int i = 0; i < mCirclePoints.size(); i++) { PointF pointF = mCirclePoints.get(i); float offset = Math.abs(pointF.x - x); if (offset < max) { max = offset; index = i; } } mCircleClickListener.onClick(index); } } break; default: break; } return super.onTouchEvent(event); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { mNavigatorHelper.onPageScrolled(position, positionOffset, positionOffsetPixels); } @Override public void onPageSelected(int position) { mNavigatorHelper.onPageSelected(position); } @Override public void onPageScrollStateChanged(int state) { mNavigatorHelper.onPageScrollStateChanged(state); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { prepareCirclePoints(); } @Override public void notifyDataSetChanged() { prepareCirclePoints(); invalidate(); } @Override public void onAttachToMagicIndicator() { } @Override public void onDetachFromMagicIndicator() { } public void setMinRadius(int minRadius) { mMinRadius = minRadius; prepareCirclePoints(); invalidate(); } public void setMaxRadius(int maxRadius) { mMaxRadius = maxRadius; prepareCirclePoints(); invalidate(); } public void setNormalCircleColor(int normalCircleColor) { mNormalCircleColor = normalCircleColor; invalidate(); } public void setSelectedCircleColor(int selectedCircleColor) { mSelectedCircleColor = selectedCircleColor; invalidate(); } public void setCircleSpacing(int circleSpacing) { mCircleSpacing = circleSpacing; prepareCirclePoints(); invalidate(); } public void setStartInterpolator(Interpolator startInterpolator) { mStartInterpolator = startInterpolator; if (mStartInterpolator == null) { mStartInterpolator = new LinearInterpolator(); } } public void setCircleCount(int count) { mCircleCount = count; // 此处不调用invalidate,让外部调用notifyDataSetChanged mNavigatorHelper.setTotalCount(mCircleCount); } public void setTouchable(boolean touchable) { mTouchable = touchable; } public void setFollowTouch(boolean followTouch) { mFollowTouch = followTouch; } public void setSkimOver(boolean skimOver) { mNavigatorHelper.setSkimOver(skimOver); } public void setCircleClickListener(OnCircleClickListener circleClickListener) { if (!mTouchable) { mTouchable = true; } mCircleClickListener = circleClickListener; } @Override public void onEnter(int index, int totalCount, float enterPercent, boolean leftToRight) { if (mFollowTouch) { float radius = mMinRadius + (mMaxRadius - mMinRadius) * mStartInterpolator.getInterpolation(enterPercent); mCircleRadiusArray.put(index, radius); invalidate(); } } @Override public void onLeave(int index, int totalCount, float leavePercent, boolean leftToRight) { if (mFollowTouch) { float radius = mMaxRadius + (mMinRadius - mMaxRadius) * mStartInterpolator.getInterpolation(leavePercent); mCircleRadiusArray.put(index, radius); invalidate(); } } @Override public void onSelected(int index, int totalCount) { if (!mFollowTouch) { mCircleRadiusArray.put(index, (float) mMaxRadius); invalidate(); } } @Override public void onDeselected(int index, int totalCount) { if (!mFollowTouch) { mCircleRadiusArray.put(index, (float) mMinRadius); invalidate(); } } public interface OnCircleClickListener { void onClick(int index); } }