时间: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