android自定義View實(shí)現(xiàn)圓環(huán)顏色選擇器
最近工作需要,自定了一個顏色選擇器,效果圖如下:
顏色種類是固定的,圓環(huán)上有個指示器,指示選中的顏色,這個定義起來應(yīng)該是很簡單了,直接上代碼。
public class MyColorPicker extends View { private int mThumbHeight; private int mThumbWidth; private String[] colors ; private int sections; //每個小塊的度數(shù) 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; //當(dāng)前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] * 因此需要將弧度值轉(zhuǎn)換一下,使得區(qū)間為[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. 可滑動位置的判斷以及如何求滑動的角度,這里還去腦補(bǔ)了下atan2這個三角函數(shù)
2. 設(shè)置指示器的開始的位置,外部調(diào)用setStartColor()方法時,這個View可能還沒真正完成繪制。如果沒有完成繪制,第幾行的invalidate()方法其實(shí)是沒多大作用。
上面是選擇單個顏色,下面來個加強(qiáng)版,選擇的是顏色區(qū)間,先上效果圖:
區(qū)間可以自己選擇,并且可以反轉(zhuǎn)(低指示器在高指示器順時針方向或逆時針方向)。
下面是代碼:
public class IntervalColorPicker extends View { private int mThumbHeight; private int mThumbWidth; private int mThumbLowHeight, mThumbLowWidth; private String[] colors = ColorUtils.getAllColors(); private int sections; //每個小塊的度數(shù) 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; //當(dāng)前view的尺寸 private int mViewSize; //區(qū)間 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] * 因此需要將弧度值轉(zhuǎn)換一下,使得區(qū)間為[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] * 因此需要將弧度值轉(zhuǎn)換一下,使得區(qū)間為[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變量的作用是判斷,手勢按下時是否在圓環(huán)上。當(dāng)手勢從圓環(huán)外滑倒圓環(huán)上時,避免指示器一下彈到手指位置。
github地址:colorpicker
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android實(shí)現(xiàn)長按圓環(huán)動畫View效果的思路代碼
- Android自定義View實(shí)現(xiàn)圓環(huán)進(jìn)度條
- Android自定義View實(shí)現(xiàn)圓環(huán)帶數(shù)字百分比進(jìn)度條
- Android自定義view實(shí)現(xiàn)圓環(huán)效果實(shí)例代碼
- Android自定義view繪制圓環(huán)占比動畫
- Android自定義View實(shí)現(xiàn)圓環(huán)交替效果
- Android中自定義View實(shí)現(xiàn)圓環(huán)等待及相關(guān)的音量調(diào)節(jié)效果
- Android自定義View之酷炫數(shù)字圓環(huán)
- Android開發(fā)筆記之:在ImageView上繪制圓環(huán)的實(shí)現(xiàn)方法
- Android自定義view實(shí)現(xiàn)半圓環(huán)效果
相關(guān)文章
Android自定義view實(shí)現(xiàn)滑動解鎖效果
這篇文章主要為大家詳細(xì)介紹了Android自定義view實(shí)現(xiàn)滑動解鎖效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05Android自定義View實(shí)現(xiàn)拼圖小游戲
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)拼圖小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11Android使用自定義PageTransformer實(shí)現(xiàn)個性的ViewPager動畫切換效果
這篇文章主要介紹了Android使用自定義PageTransformer實(shí)現(xiàn)個性的ViewPager切換動畫,具有很好的參考價(jià)值,一起跟隨小編過來看看吧2018-05-05淺談Android app開發(fā)中Fragment的Transaction操作
這篇文章主要介紹了Android app開發(fā)中Fragment的Transaction操作,包括Transaction和Fragment的生命周期的聯(lián)系等內(nèi)容,需要的朋友可以參考下2016-02-02Android軟鍵盤的顯示隱藏功能實(shí)現(xiàn)過程
這篇文章主要介紹了Android軟鍵盤的顯示隱藏功能,非常不錯,具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03Android實(shí)現(xiàn)郵箱驗(yàn)證功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)郵箱驗(yàn)證功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05Android ShareSDK快速實(shí)現(xiàn)分享功能
這篇文章主要介紹了Android ShareSDK快速實(shí)現(xiàn)分享功能的相關(guān)資料,需要的朋友可以參考下2016-02-02