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

2020-11-05 10:22:02 浏览数 (1)

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

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

代码语言:javascript复制
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()方法其实是没多大作用。

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

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

下面是代码:

代码语言:javascript复制
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

以上就是本文的全部内容,希望对大家的学习有所帮助。

0 人点赞