当前位置:主页 > 移动开发 > Android代码 >

android自定义View实现圆环颜色选择器

时间:2021-01-10 11:07:40 | 栏目:Android代码 | 点击:

最近工作需要,自定了一个颜色选择器,效果图如下:

这里写图片描述

颜色种类是固定的,圆环上有个指示器,指示选中的颜色,这个定义起来应该是很简单了,直接上代码。

public class MyColorPicker extends View {

 private int mThumbHeight;
 private int mThumbWidth;
 private String[] colors ;

 private int sections;
 //每个小块的度数
 private int sectionAngle;

 private Paint mPaint;

 private int ringWidth;

 private RectF mRectF;

 private Drawable mThumbDrawable = null;
 private float mThumbLeft;
 private float mThumbTop;
 private double mViewCenterX, mViewCenterY;
 private double mViewRadisu;
 //起始角度
 private int mStartDegree = -90;

 //当前view的尺寸
 private int mViewSize;

 private int textColor;
 private String text="";

 private Paint textPaint;

 private Rect mBounds;

 private float textSize;

 private int colorType;

 private int default_size = 100;

 public MyColorPicker(Context context) {
  this(context, null);
 }

 public MyColorPicker(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }

 public MyColorPicker(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  TypedArray localTypedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleColorPicker);
  mThumbDrawable = localTypedArray.getDrawable(R.styleable.CircleColorPicker_thumb);
  ringWidth = (int) localTypedArray.getDimension(R.styleable.CircleColorPicker_ring_span, 30);
  colorType = localTypedArray.getInt(R.styleable.CircleColorPicker_color_type, 0);
  textColor = localTypedArray.getColor(R.styleable.CircleColorPicker_text_color, Color.BLACK);
  text = localTypedArray.getString(R.styleable.CircleColorPicker_text);
  textSize = localTypedArray.getDimension(R.styleable.CircleColorPicker_text_size, 20);
  localTypedArray.recycle();
  default_size = SystemUtils.dip2px(context, 260);
  init();
 }

 private void init() {

  colors = colorType == 1 ? ColorUtils.getMacaroon():ColorUtils.getAllColors();
  sections = colors.length;
  mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  mPaint.setStyle(Paint.Style.STROKE);
  mPaint.setStrokeWidth(ringWidth);

  textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  textPaint.setColor(textColor);
  textPaint.setTextSize(textSize);
  mThumbWidth = this.mThumbDrawable.getIntrinsicWidth();
  mThumbHeight = this.mThumbDrawable.getIntrinsicHeight();

  sectionAngle = 360/sections;

  mBounds = new Rect();

 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  setMeasuredDimension(getMeasuredLength(widthMeasureSpec, true), getMeasuredLength(heightMeasureSpec, false));

  int circleX = getMeasuredWidth();
  int circleY = getMeasuredHeight();
  if (circleY < circleX)
  {
   circleX = circleY;
  }
  mViewSize = circleX;
  mViewCenterX = circleX/2;
  mViewCenterY = circleY/2;
  mViewRadisu = circleX/2 - mThumbWidth / 2;

  setThumbPosition(Math.toRadians(mStartDegree));
 }

 private int getMeasuredLength(int length, boolean isWidth) {
  int specMode = MeasureSpec.getMode(length);
  int specSize = MeasureSpec.getSize(length);
  int size;
  int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
  if (specMode == MeasureSpec.EXACTLY) {
   size = specSize;
  } else {
   size = default_size + padding;
   if (specMode == MeasureSpec.AT_MOST) {
    size = Math.min(size, specSize);
   }
  }
  return size;
 }

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);

  mRectF = new RectF(0+mThumbWidth/2, 0+mThumbWidth/2, mViewSize-mThumbWidth/2, mViewSize-mThumbWidth/2);

  for (int i = 0; i < colors.length; i++)
  {
   mPaint.setColor(Color.parseColor(colors[i]));
   canvas.drawArc(mRectF, i*sectionAngle-90, sectionAngle+1,false, mPaint);
  }

  mThumbDrawable.setBounds((int) mThumbLeft, (int) mThumbTop,
    (int) (mThumbLeft + mThumbWidth), (int) (mThumbTop + mThumbHeight));
  mThumbDrawable.draw(canvas);

  textPaint.getTextBounds(text, 0, text.length(), mBounds);
  float textWidth = mBounds.width();
  float textHeight = mBounds.height();

  float textLeft = (float) (mViewCenterX - textWidth/2);
  float textTop = (float)(mViewCenterY + textHeight/2);
  canvas.drawText(text, 0, text.length(), textLeft, textTop, textPaint);

 }

 private void setThumbPosition(double radian) {
  double x = mViewCenterX + mViewRadisu * Math.cos(radian);
  double y = mViewCenterY + mViewRadisu * Math.sin(radian);
  mThumbLeft = (float) (x - mThumbWidth / 2);
  mThumbTop = (float) (y - mThumbHeight / 2);
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  float eventX = event.getX();
  float eventY = event.getY();
  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN:
    seekTo(eventX, eventY, false);
    break ;

   case MotionEvent.ACTION_MOVE:
    seekTo(eventX, eventY, false);
    break ;

   case MotionEvent.ACTION_UP:
//    seekTo(eventX, eventY, true);
    float part = sectionAngle / 4.0f;
    for (int i = 0; i < sections; i++) {
     if ( mSweepDegree > (i-1)*sectionAngle+part*3 && mSweepDegree < i *sectionAngle + part)
     {
      if (mSweepDegree < i*sectionAngle)
      {
       setThumbPosition(Math.toRadians((i-1)*sectionAngle+part*2));
      }else {
       setThumbPosition(Math.toRadians(i*sectionAngle+part*2));
      }
     }
    }
    if (mSweepDegree > ((sections-1)*sectionAngle)+part*3)
    {
     setThumbPosition(Math.toRadians((sections-1)*sectionAngle+part*2));
    }
    invalidate();
    break ;
  }
  return true;
 }

 private int preColor;

 private float mSweepDegree;
 private void seekTo(float eventX, float eventY, boolean isUp) {
  if (true == isPointOnThumb(eventX, eventY) && false == isUp) {
//   mThumbDrawable.setState(mThumbPressed);

   double radian = Math.atan2(eventY - mViewCenterY, eventX - mViewCenterX);
   /*
    * 由于atan2返回的值为[-pi,pi]
    * 因此需要将弧度值转换一下,使得区间为[0,2*pi]
    */
   if (radian < 0){
    radian = radian + 2*Math.PI;
   }
   setThumbPosition(radian);

   mSweepDegree = (float) Math.round(Math.toDegrees(radian));

   int currentColor = getColor(mSweepDegree);
   if (currentColor != preColor)
   {
    preColor = currentColor;
    if (onColorChangeListener != null)
    {
     onColorChangeListener.colorChange(preColor);
    }
   }

   invalidate();
  }else{
//   mThumbDrawable.setState(mThumbNormal);
   invalidate();
  }
 }

 private int getColor(float mSweepDegree) {

  int tempIndex = (int) (mSweepDegree/sectionAngle);

  int num = 90 / sectionAngle;

  if (tempIndex ==sections)
  {
   tempIndex = 0;
  }

  int index = tempIndex;
  if (tempIndex >= 0) {
   index = tempIndex+num;
  }
  if (tempIndex >= (sections-num))
  {
   index = tempIndex-(sections-num);
  }

  return Color.parseColor(colors[index]);
 }


 private boolean isPointOnThumb(float eventX, float eventY) {
  boolean result = false;
  double distance = Math.sqrt(Math.pow(eventX - mViewCenterX, 2)
    + Math.pow(eventY - mViewCenterY, 2));
  if (distance < mViewSize && distance > (mViewSize / 2 - mThumbWidth)){
   result = true;
  }
  return result;
 }


 public int getCurrentColor()
 {
  return preColor;
 }

 public void setStartColor(String color)
 {
  for (int i = 0; i < colors.length; i++)
  {
   if (colors[i].equals(color))
   {
    preColor = Color.parseColor(colors[i]);
    int sweepAngle = (i- 90 /sectionAngle)*sectionAngle+sectionAngle/2;
//    postDelayed(()->{
//     setThumbPosition(Math.toRadians(sweepAngle));
//     invalidate();
//    },200);
    mStartDegree = sweepAngle;
    //最好加上
    invalidate();
    break;
   }
  }
 }

 public void setColor(String color) {
  for (int i = 0; i < colors.length; i++)
  {
   if (colors[i].equals(color))
   {
    preColor = Color.parseColor(colors[i]);
    int sweepAngle = (i- 90 /sectionAngle)*sectionAngle+sectionAngle/2;
    setThumbPosition(Math.toRadians(sweepAngle));
    invalidate();
    break;
   }
  }
 }


 public interface OnColorChangeListener
 {
  void colorChange(int color);
 }

 public void setOnColorChangeListener(OnColorChangeListener onColorChangeListener) {
  this.onColorChangeListener = onColorChangeListener;
 }

 private OnColorChangeListener onColorChangeListener;
}

注意的几个地方:

1. 可滑动位置的判断以及如何求滑动的角度,这里还去脑补了下atan2这个三角函数
2. 设置指示器的开始的位置,外部调用setStartColor()方法时,这个View可能还没真正完成绘制。如果没有完成绘制,第几行的invalidate()方法其实是没多大作用。

上面是选择单个颜色,下面来个加强版,选择的是颜色区间,先上效果图:

这里写图片描述

区间可以自己选择,并且可以反转(低指示器在高指示器顺时针方向或逆时针方向)。

下面是代码:

public class IntervalColorPicker extends View {
 private int mThumbHeight;
 private int mThumbWidth;

 private int mThumbLowHeight, mThumbLowWidth;
 private String[] colors = ColorUtils.getAllColors();

 private int sections;
 //每个小块的度数
 private int sectionAngle;

 private Paint mPaint;

 private Paint arcPaint;

 private int ringWidth;

 private RectF mRectF;

 private Drawable mThumbHighDrawable = null;
 private Drawable mThumbLowDrawable;
 private float mThumbLeft;
 private float mThumbTop;

 private float mThumbLowLeft, mThumbLowTop;

 private double mViewCenterX, mViewCenterY;
 private double mViewRadisu;
 //起始角度
 private float mStartDegree = 270;

 //当前view的尺寸
 private int mViewSize;

 //区间
 private int interval = 7;

 private boolean reverse;

 private float tempStartAngle = mStartDegree;

 public IntervalColorPicker(Context context) {
  this(context, null);
 }

 public IntervalColorPicker(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }

 public IntervalColorPicker(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  TypedArray localTypedArray = context.obtainStyledAttributes(attrs, R.styleable.IntervalColorPicker);
  mThumbHighDrawable = localTypedArray.getDrawable(R.styleable.IntervalColorPicker_thumbHigh);
  mThumbLowDrawable = localTypedArray.getDrawable(R.styleable.IntervalColorPicker_thumbLow);
  ringWidth = (int) localTypedArray.getDimension(R.styleable.IntervalColorPicker_ring_breadth, 30);
  localTypedArray.recycle();
  init();
 }

 private void init() {
  sections = colors.length;
  mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  mPaint.setStyle(Paint.Style.STROKE);
  mPaint.setStrokeWidth(ringWidth);

  arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  arcPaint.setStyle(Paint.Style.STROKE);
  arcPaint.setStrokeWidth(ringWidth + 1);
  arcPaint.setColor(Color.GRAY);

  mThumbWidth = this.mThumbHighDrawable.getIntrinsicWidth();
  mThumbHeight = this.mThumbHighDrawable.getIntrinsicHeight();
  mThumbLowHeight = mThumbLowDrawable.getIntrinsicHeight();
  mThumbLowWidth = mThumbHighDrawable.getIntrinsicWidth();

  sectionAngle = 360 / sections;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  int circleX = getMeasuredWidth();
  int circleY = getMeasuredHeight();
  if (circleY < circleX) {
   circleX = circleY;
  }
  mViewSize = circleX;
  mViewCenterX = circleX / 2;
  mViewCenterY = circleY / 2;
  mViewRadisu = circleX / 2 - mThumbWidth / 2;
 }

 private float sweepAngle;

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);

  mRectF = new RectF(0 + mThumbWidth / 2, 0 + mThumbWidth / 2, mViewSize - mThumbWidth / 2, mViewSize - mThumbWidth / 2);

  for (int i = 0; i < colors.length; i++) {
   mPaint.setColor(Color.parseColor(colors[i]));
   canvas.drawArc(mRectF, i * sectionAngle - 90, sectionAngle + 1, false, mPaint);
  }

  int tempAng = (int) (tempStartAngle + sweepAngle);
  int intervalAngle = interval * sectionAngle;

  if (reverse) {
   setThumbPosition(Math.toRadians(tempAng));
   setThumbLowPosition(Math.toRadians(tempAng - intervalAngle));
   canvas.drawArc(mRectF, tempAng, 360 - intervalAngle, false, arcPaint);
  } else {
   setThumbPosition(Math.toRadians(tempAng));
   setThumbLowPosition(Math.toRadians(tempAng + intervalAngle));
   canvas.drawArc(mRectF, (int) (tempAng + intervalAngle), 360 - intervalAngle, false, arcPaint);
  }

  mThumbHighDrawable.setBounds((int) mThumbLeft, (int) mThumbTop,
    (int) (mThumbLeft + mThumbWidth), (int) (mThumbTop + mThumbHeight));
  mThumbLowDrawable.setBounds((int) mThumbLowLeft, (int) mThumbLowTop, (int) (mThumbLowLeft + mThumbLowWidth), (int) (mThumbLowTop + mThumbLowHeight));


  mThumbHighDrawable.draw(canvas);
  mThumbLowDrawable.draw(canvas);
 }

 private void setThumbPosition(double radian) {
  double x = mViewCenterX + mViewRadisu * Math.cos(radian);
  double y = mViewCenterY + mViewRadisu * Math.sin(radian);
  mThumbLeft = (float) (x - mThumbWidth / 2);
  mThumbTop = (float) (y - mThumbHeight / 2);
 }

 private void setThumbLowPosition(double radian) {
  double x = mViewCenterX + mViewRadisu * Math.cos(radian);
  double y = mViewCenterY + mViewRadisu * Math.sin(radian);
  mThumbLowLeft = (float) (x - mThumbLowWidth / 2);
  mThumbLowTop = (float) (y - mThumbLowHeight / 2);
 }

 private boolean isDown = true;

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  float eventX = event.getX();
  float eventY = event.getY();
  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN:
    getEventDegree(eventX, eventY);
//    seekTo(eventX, eventY, false);
    break;

   case MotionEvent.ACTION_MOVE:

    seekTo(eventX, eventY);
    break;

   case MotionEvent.ACTION_UP:
    postDelayed(() -> {
     tempStartAngle = tempStartAngle + sweepAngle;
     sweepAngle = 0;
     getSelectedColor();
     if (onColorChangeListener != null) {
      onColorChangeListener.colorChange(selectedColors);
     }

    }, 100);

    break;

  }
  return true;
 }

 private float downDegree;

 private void getEventDegree(float eventX, float eventY) {
  if (isPointOnThumb(eventX, eventY)) {
   double radian = Math.atan2(eventY - mViewCenterY, eventX - mViewCenterX);
   /*
    * 由于atan2返回的值为[-pi,pi]
    * 因此需要将弧度值转换一下,使得区间为[0,2*pi]
    */
   if (radian < 0) {
    radian = radian + 2 * Math.PI;
   }
   isDown = true;
   downDegree = Math.round(Math.toDegrees(radian));
  } else {
   isDown = false;
  }
 }

 private void seekTo(float eventX, float eventY) {
  if (true == isPointOnThumb(eventX, eventY)) {
//   mThumbHighDrawable.setState(mThumbPressed);

   if (!isDown) {
    getEventDegree(eventX, eventY);
    isDown = true;
   }

   double radian = Math.atan2(eventY - mViewCenterY, eventX - mViewCenterX);
   /*
    * 由于atan2返回的值为[-pi,pi]
    * 因此需要将弧度值转换一下,使得区间为[0,2*pi]
    */
   if (radian < 0) {
    radian = radian + 2 * Math.PI;
   }
   setThumbPosition(radian);

   float mSweepDegree = (float) Math.round(Math.toDegrees(radian));

   sweepAngle = mSweepDegree - downDegree;


   invalidate();
  }
 }

 //选中的颜色
 private ArrayList<Integer> selectedColors = new ArrayList<>(interval);

 public void getSelectedColor() {
  int tempIndex = (int) (tempStartAngle / sectionAngle);
  int num = 90 / sectionAngle;
  if (tempIndex == sections) {
   tempIndex = 0;
  }
  int index = tempIndex;
  if (tempIndex >= 0) {
   index = tempIndex + num;
  }
  if (tempIndex >= (sections - num)) {
   index = tempIndex - (sections - num);
  }


  if (index>colors.length)
   index = index%colors.length;
  while (index<0)
  {
   index = colors.length+index;
  }
  selectedColors.clear();
  int startIndex = 0;
  if (reverse)
  {
   startIndex = index - interval -1;
   while (startIndex < 0)
   {
    startIndex = startIndex+colors.length;
   }
   if (startIndex > index)
   {
    for (int i = startIndex+1; i < colors.length; i++) {
     selectedColors.add(Color.parseColor(colors[i]));
    }
    for (int i = 0; i <= index; i++) {
     selectedColors.add(Color.parseColor(colors[i]));
    }
   }else {
    for (int i = startIndex+1; i <= index; i++) {
     selectedColors.add(Color.parseColor(colors[i]));
    }
   }
  }else {
   startIndex = index+interval+1;
   while (startIndex>colors.length)
   {
    startIndex = startIndex-colors.length;
   }
   if (startIndex < index)
   {
    for (int i = startIndex-1; i >= 0; i--) {
     selectedColors.add(Color.parseColor(colors[i]));
    }
    for (int i = colors.length-1; i >= index; i--) {
     selectedColors.add(Color.parseColor(colors[i]));
    }
   }else {
    for (int i = startIndex-1; i >=index; i--) {
     selectedColors.add(Color.parseColor(colors[i]));
    }
   }

  }

 }


 private boolean isPointOnThumb(float eventX, float eventY) {
  boolean result = false;
  double distance = Math.sqrt(Math.pow(eventX - mViewCenterX, 2)
    + Math.pow(eventY - mViewCenterY, 2));
  if (distance < mViewSize && distance > (mViewSize / 2 - mThumbWidth)) {
   result = true;
  }
  return result;
 }


 public boolean isReverse() {
  return reverse;
 }

 public void setReverse(boolean reverse) {
  this.reverse = reverse;
  invalidate();
 }

 public interface OnColorChangeListener {
  void colorChange(ArrayList<Integer> colors);
 }

 public void setOnColorChangeListener(OnColorChangeListener onColorChangeListener) {
  this.onColorChangeListener = onColorChangeListener;
 }

 private OnColorChangeListener onColorChangeListener;
}

注意的地方:

1. 手势抬起时用了一个postDelayed方法,还是避免绘制的先后问题。
2. isDown变量的作用是判断,手势按下时是否在圆环上。当手势从圆环外滑倒圆环上时,避免指示器一下弹到手指位置。

github地址:colorpicker

您可能感兴趣的文章:

相关文章