android自定義View實(shí)現(xiàn)圓環(huán)顏色選擇器
最近工作需要,自定了一個(gè)顏色選擇器,效果圖如下:

顏色種類(lèi)是固定的,圓環(huán)上有個(gè)指示器,指示選中的顏色,這個(gè)定義起來(lái)應(yīng)該是很簡(jiǎn)單了,直接上代碼。
public class MyColorPicker extends View {
private int mThumbHeight;
private int mThumbWidth;
private String[] colors ;
private int sections;
//每個(gè)小塊的度數(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;
}
注意的幾個(gè)地方:
1. 可滑動(dòng)位置的判斷以及如何求滑動(dòng)的角度,這里還去腦補(bǔ)了下atan2這個(gè)三角函數(shù)
2. 設(shè)置指示器的開(kāi)始的位置,外部調(diào)用setStartColor()方法時(shí),這個(gè)View可能還沒(méi)真正完成繪制。如果沒(méi)有完成繪制,第幾行的invalidate()方法其實(shí)是沒(méi)多大作用。
上面是選擇單個(gè)顏色,下面來(lái)個(gè)加強(qiáng)版,選擇的是顏色區(qū)間,先上效果圖:

區(qū)間可以自己選擇,并且可以反轉(zhuǎn)(低指示器在高指示器順時(shí)針?lè)较蚧蚰鏁r(shí)針?lè)较颍?/p>
下面是代碼:
public class IntervalColorPicker extends View {
private int mThumbHeight;
private int mThumbWidth;
private int mThumbLowHeight, mThumbLowWidth;
private String[] colors = ColorUtils.getAllColors();
private int sections;
//每個(gè)小塊的度數(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. 手勢(shì)抬起時(shí)用了一個(gè)postDelayed方法,還是避免繪制的先后問(wèn)題。
2. isDown變量的作用是判斷,手勢(shì)按下時(shí)是否在圓環(huán)上。當(dāng)手勢(shì)從圓環(huán)外滑倒圓環(huán)上時(shí),避免指示器一下彈到手指位置。
github地址:colorpicker
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android實(shí)現(xiàn)長(zhǎng)按圓環(huán)動(dòng)畫(huà)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)占比動(dòng)畫(huà)
- 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開(kāi)發(fā)筆記之:在ImageView上繪制圓環(huán)的實(shí)現(xiàn)方法
- Android自定義view實(shí)現(xiàn)半圓環(huán)效果
相關(guān)文章
Android自定義view實(shí)現(xiàn)滑動(dòng)解鎖效果
這篇文章主要為大家詳細(xì)介紹了Android自定義view實(shí)現(xiàn)滑動(dòng)解鎖效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05
Android自定義View實(shí)現(xiàn)拼圖小游戲
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)拼圖小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11
Android使用自定義PageTransformer實(shí)現(xiàn)個(gè)性的ViewPager動(dòng)畫(huà)切換效果
這篇文章主要介紹了Android使用自定義PageTransformer實(shí)現(xiàn)個(gè)性的ViewPager切換動(dòng)畫(huà),具有很好的參考價(jià)值,一起跟隨小編過(guò)來(lái)看看吧2018-05-05
淺談Android app開(kāi)發(fā)中Fragment的Transaction操作
這篇文章主要介紹了Android app開(kāi)發(fā)中Fragment的Transaction操作,包括Transaction和Fragment的生命周期的聯(lián)系等內(nèi)容,需要的朋友可以參考下2016-02-02
Android軟鍵盤(pán)的顯示隱藏功能實(shí)現(xiàn)過(guò)程
這篇文章主要介紹了Android軟鍵盤(pán)的顯示隱藏功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03
Android實(shí)現(xiàn)郵箱驗(yàn)證功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)郵箱驗(yàn)證功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05
Android ShareSDK快速實(shí)現(xiàn)分享功能
這篇文章主要介紹了Android ShareSDK快速實(shí)現(xiàn)分享功能的相關(guān)資料,需要的朋友可以參考下2016-02-02

