前言:
前段时间项目中需要用到色盘取色的功能,百度了许多相关的颜色选择器,发现这方面自定义View例子比较少,有用图片代替色盘的通过bitmap取色,但是只能取色,无法通过颜色值去定位在色盘上的坐标,由于没有找到适用的,所以自己根据需求花了一些时间写了个,本着不重复造轮子的原则,于是便将原来的删除一些多余的代码,在这里分享一下。
效果图:
首先是绘制中间的圆形色盘,因为需要不仅能通过触摸的屏幕坐标获取选中的颜色,还需要通过指定的int型color值来获取到颜色在色盘中所处的坐标,即通过颜色值反向定位坐标。所以在这里使用到了HVS颜色空间模型。
关于HVS颜色模型:
这个模型中颜色的参数分别是:色调(H),饱和度(S),明度(V)。
而在我们的色盘中这里则分别是:角度(H),半径(S),明度(V)不需要用上,有兴趣的可以去网上这个模型的相关资料。
通过HSV模型绘制中间的色盘:
//创建色盘Bitmap
private Bitmap createColorWheelBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
int colorCount = 12;
int colorAngleStep = 360 / colorCount;
int colors[] = new int[colorCount + 1];
float hsv[] = new float[]{0f, 1f, 1f};
for (int i = 0; i < colors.length; i++) {
hsv[0] = 360 - (i * colorAngleStep) % 360;
colors[i] = Color.HSVToColor(hsv);
}
colors[colorCount] = colors[0];
SweepGradient sweepGradient = new SweepGradient(width / 2, height / 2, colors, null);
RadialGradient radialGradient = new RadialGradient(width / 2, height / 2, colorWheelRadius, 0xFFFFFFFF, 0x00FFFFFF, Shader.TileMode.CLAMP);
ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER);
colorWheelPaint.setShader(composeShader);
Canvas canvas = new Canvas(bitmap);
canvas.drawCircle(width / 2, height / 2, colorWheelRadius, colorWheelPaint);
//默认取圆心颜色,给一个默认颜色用于显示点标记
currentColor = getColorAtPoint(markerPoint.x, markerPoint.y);
return bitmap;
}
绘制色盘后白色圆形
private void drawWhiteWheel(Canvas canvas) {
//绘制白色圆圈
whiteWheelPaint.setColor(Color.WHITE);
canvas.drawCircle(centerWheelX, centerWheelY, whiteWheelRadius, whiteWheelPaint);
}
绘制:
//绘制白色圆圈
drawWhiteWheel(canvas);
colorWheelBitmap = createColorWheelBitmap(colorWheelRadius * 2, colorWheelRadius * 2);
//绘制色盘
canvas.drawBitmap(colorWheelBitmap, mColorWheelRect.left, mColorWheelRect.top, null);
接下来绘制外圈色环:
和绘制色盘的方式一样,只不过将绘制圆形的画笔样式改为描边,并设置画笔宽度。
//设置画笔为描边
colorRingPaint.setStyle(Paint.Style.STROKE);
colorRingBitmap = createColorRingBitmap(colorRingRadius * 2 + ringWidth, colorRingRadius * 2 + ringWidth);
//绘制外圈彩色圆环
canvas.drawBitmap(colorRingBitmap, mColorRingRect.left, mColorRingRect.top, null);
//创建彩色圆环bitmap
private Bitmap createColorRingBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
int colorCount = 12;
int colorAngleStep = 360 / colorCount;
int colors[] = new int[colorCount + 1];
float hsv[] = new float[]{0f, 1f, 1f};
for (int i = 0; i < colors.length; i++) {
hsv[0] = 360 - (i * colorAngleStep) % 360;
colors[i] = Color.HSVToColor(hsv);
}
colors[colorCount] = colors[0];
SweepGradient sweepGradient = new SweepGradient(width / 2, height / 2, colors, null);
RadialGradient radialGradient = new RadialGradient(width / 2, height / 2, colorRingRadius, 0xFFFFFFFF, 0x00FFFFFF, Shader.TileMode.CLAMP);
ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER);
colorRingPaint.setShader(composeShader);
Canvas canvas = new Canvas(bitmap);
canvas.drawCircle(width / 2, height / 2, colorRingRadius, colorRingPaint);
return bitmap;
}
然后绘制色盘上的颜色点标记:
//绘制点标记
drawMarker(canvas);
private void drawMarker(Canvas canvas) {
float markerWidth = markerBitmap.getWidth();
float markerHeight = markerBitmap.getHeight();
// 指定图片在屏幕上显示的区域(原图大小)
float left = (markerPoint.x - (markerWidth / 2));//markerPoint 为当前所触摸的点
float top = (markerPoint.y - markerHeight) + markerHeight * 1 / 10;
RectF dst = new RectF(left, top, left + markerWidth, top + markerHeight);
//点标记上的颜色显示
float markerRadius = markerWidth / 3;
markerPaint.setColor(currentColor);//设置颜色为当前颜色
canvas.drawBitmap(markerBitmap, null, dst, null);//绘制点标记图片
canvas.drawCircle(left + markerWidth / 2, top + markerWidth / 2 - 4, markerRadius, markerPaint);//绘制点标记中的变色小圆
}
还需要绘制外圈彩色圆圆环上的滑动按钮,这里使用了Matrix矩阵将按钮图片平移到了彩色圆环上,使用Matrix目的是为了通过旋转的方式去改变滑动按钮的位置,使箭头方向准确
private void drawColorRingBtn(Canvas canvas) {
int colorRingBtnWidth = colorRingBtnBitmap.getWidth();
int colorRingBtnHeight = colorRingBtnBitmap.getHeight();
int left = centerWheelX - colorRingBtnWidth;
int top = centerWheelY - colorRingRadius - colorRingBtnHeight;
// colorRingBtnRect = new RectF(left, top, left + colorRingBtnWidth, top + colorRingBtnHeight);
colorRingBtnPoint.x = left + colorRingBtnWidth / 2;
colorRingBtnPoint.y = top + colorRingBtnHeight / 2;
colorRingMatrix.preTranslate(colorRingBtnPoint.x, colorRingBtnPoint.y);
// canvas.drawBitmap(colorRingBtnBitmap, null, colorRingBtnRect, null);
canvas.drawBitmap(colorRingBtnBitmap, colorRingMatrix, null);
colorRingMatrix.reset();
}
效果图:
最后需要的便是获取色盘以及彩色圆环上的触摸事件,让它们可移动起来并实时获取颜色值。
重写onTouchEvent方法:
private static int colorTmp;///用于判断颜色是否发生改变
private PointF downPointF = new PointF();//按下的位置
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
colorTmp = currentColor;
downPointF.x = event.getX();
downPointF.y = event.getY();
case MotionEvent.ACTION_MOVE:
update(event);
return true;
case MotionEvent.ACTION_UP:
if (colorTmp != currentColor) {
onColorPickerChanger();
}
break;
default:
return true;
}
return super.onTouchEvent(event);
}
private void update(MotionEvent event) {
float x = event.getX();
float y = event.getY();
updateSelector(x, y);
updateRingSelector(x, y);
}
private void onColorPickerChanger() {
if (onColorPickerChangerListener != null) {
int red = (currentColor & 0xff0000) >> 16;
int green = (currentColor & 0x00ff00) >> 8;
int blue = (currentColor & 0x0000ff);
onColorPickerChangerListener.onColorPickerChanger(currentColor, red, green, blue);
}
}
色盘选择颜色:
/**
* 刷新s色盘所选择的颜色
* @param eventX
* @param eventY
*/
private void updateSelector(float eventX, float eventY) {
float x = eventX - centerWheelX;
float y = eventY - centerWheelY;
double r = Math.sqrt(x * x + y * y);
//判断是否在圆内
if (r > colorWheelRadius) {
//不在圆形范围内
return;
}
//同时旋转外圈滑动按钮
colorRingMatrix.preRotate(getRotationBetweenLines(centerWheelX, centerWheelY, eventX, eventY), centerWheelX, centerWheelY);
currentPoint.x = x + centerWheelX;
currentPoint.y = y + centerWheelY;
markerPoint.x = currentPoint.x;//改变点标记位置
markerPoint.y = currentPoint.y;
currentColor = getColorAtPoint(eventX, eventY);//获取到的颜色
invalidate();
}
/**
* 获取两条线的夹角
*
* @param centerX
* @param centerY
* @param xInView
* @param yInView
* @return
*/
public static int getRotationBetweenLines(float centerX, float centerY, float xInView, float yInView) {
double rotation = 0;
double k1 = (double) (centerY - centerY) / (centerX * 2 - centerX);
double k2 = (double) (yInView - centerY) / (xInView - centerX);
double tmpDegree = Math.atan((Math.abs(k1 - k2)) / (1 + k1 * k2)) / Math.PI * 180;
if (xInView > centerX && yInView < centerY) { //第一象限
rotation = 90 - tmpDegree;
} else if (xInView > centerX && yInView > centerY) //第二象限
{
rotation = 90 + tmpDegree;
} else if (xInView < centerX && yInView > centerY) { //第三象限
rotation = 270 - tmpDegree;
} else if (xInView < centerX && yInView < centerY) { //第四象限
rotation = 270 + tmpDegree;
} else if (xInView == centerX && yInView < centerY) {
rotation = 0;
} else if (xInView == centerX && yInView > centerY) {
rotation = 180;
}
return (int) rotation;
}
/**
* 根据坐标获取颜色
* @param eventX
* @param eventY
* @return
*/
private int getColorAtPoint(float eventX, float eventY) {
float x = eventX - centerWheelX;
float y = eventY - centerWheelY;
double r = Math.sqrt(x * x + y * y);
float[] hsv = {0, 0, 1};
hsv[0] = (float) (Math.atan2(y, -x) / Math.PI * 180f) + 180;
hsv[1] = Math.max(0f, Math.min(1f, (float) (r / colorWheelRadius)));
return Color.HSVToColor(hsv);
}
色环颜色选择:
/**
* 刷新色环选择
*
* @param eventX
* @param eventY
*/
private void updateRingSelector(float eventX, float eventY) {
float x = downPointF.x - centerWheelX;
float y = downPointF.y - centerWheelY;
double r = Math.sqrt(x * x + y * y);//按下位置的半径
//判断是否在圆内,或者色环上
if ((r < colorRingRadius + ringWidth && r > colorRingRadius - ringWidth)) {
colorRingMatrix.preRotate(getRotationBetweenLines(centerWheelX, centerWheelY, eventX, eventY), centerWheelX, centerWheelY);
currentColor = getColorAtPoint(eventX, eventY);//int值颜色
float[] hsv = getHSVColorAtPoint(eventX, eventY);//hsv值颜色
float h = hsv[0];//hsv色盘色点角度
float s = hsv[1];//hsv色盘色点相对于半径的比值
float colorDotRadius = colorWheelRadius * s;//色点半径
//根据角度和半径获取坐标
float radian = (float) Math.toRadians(-h);
float colorDotX = (float) (centerWheelX + Math.cos(radian) * colorDotRadius);
float colorDotY = (float) (centerWheelY + Math.sin(radian) * colorDotRadius);
markerPoint.x = colorDotX;同时改变色盘上点标记的位置
markerPoint.y = colorDotY;
invalidate();
}
}
/**
* 根据坐标获取HSV颜色值
* @param eventX
* @param eventY
* @return
*/
private float[] getHSVColorAtPoint(float eventX, float eventY) {
float x = eventX - centerWheelX;
float y = eventY - centerWheelY;
double r = Math.sqrt(x * x + y * y);
float[] hsv = {0, 0, 1};
hsv[0] = (float) (Math.atan2(y, -x) / Math.PI * 180f) + 180;
hsv[1] = Math.max(0f, Math.min(1f, (float) (r / colorWheelRadius)));
return hsv;
}
最后贴一下设置颜色改变滑动按钮和点标记的方法,和上面的代码是一样的。
public void setColor(int color) {
float[] hsv = {0, 0, 1};
Color.colorToHSV(color, hsv);
//根据hsv角度及半径获取坐标
//根据角度和半径获取坐标
float radian = (float) Math.toRadians(-hsv[0]);
float colorDotRadius = hsv[1] * colorWheelRadius;
float colorDotX = (float) (centerWheelX + Math.cos(radian) * colorDotRadius);
float colorDotY = (float) (centerWheelY + Math.sin(radian) * colorDotRadius);
//设置marker位置
markerPoint.x = colorDotX;
markerPoint.y = colorDotY;
currentColor = getColorAtPoint(markerPoint.x, markerPoint.y);//设置当前颜色
//设置色环按钮位置
colorRingMatrix.preRotate(getRotationBetweenLines(centerWheelX, centerWheelY, markerPoint.x, markerPoint.y), centerWheelX, centerWheelY);
invalidate();
// paint.setColor(Color.rgb(red, green, blue));
}
总结:
主要的实现原理其实就是通过HSV颜色模型绘制色盘,然后通过HSV颜色空间的坐标对应到色盘上,以圆心为原点H(0-360)对应角度,S(0-255)对应半径,V(明度)用不上,通过坐标系的方式取HSV模型上的颜色,这样取到的颜色值会更加的准确。反过来又根据HSV颜色值计算在模型上的坐标,对应计算出该颜色值在色盘上的坐标,即可实现通过颜色值反向定位坐标位置。