软件编程
位置:首页>> 软件编程>> Android编程>> android自定义View实现圆环颜色选择器

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

作者:zxc123e  发布时间:2023-11-07 19:16:02 

标签:android,颜色选择器

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

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

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


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()方法其实是没多大作用。

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

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

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

下面是代码:


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

来源:https://blog.csdn.net/zxc123e/article/details/70810722

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com