后面陆续分享各种自定义View,大家可以点点关注啥的
本次分享一个圆环色盘,之前做的时候有去参考网上代码,具体是哪位大佬的忘记了,现在这个是在那个基础上优化的,有大佬认领的可以提一下,我加上来。
外部圆环
需要考虑的是,圆环位置转换成颜色,以及颜色转换成对应位置,之前的是写有一个颜色数组,通过位置判断,从而获取数组里面相对的颜色,这部分陆续的优化成为了直接通过hsv处理,h的范围值为0-360,对应整个圆环360°,角度转换成颜色,颜色转换成角度,都一目了然
private double getColorAngle(float hue) {
double angle;
float unit = 1 - hue / 360f;
if (unit >= 0.5f) {
angle = (unit * (2 * Math.PI)) - Math.PI * 2;
} else {
angle = unit * (2 * Math.PI);
}
return angle;
}
private float getColorHue(double angle) {
float hue = 0;
if (angle >= 0) {
hue = (float) (1 - angle / (2 * Math.PI)) * 360;
} else {
hue = (float) (1 - (2 * Math.PI + angle) / (2 * Math.PI)) * 360;
}
return hue;
}
用1去减是为了调转颜色顺序问题
内部圆
这部分主要是两个着色器组合起来,一个代表hsv里面的s(饱和度) ,一个代表hsv里面的v(明度)
/**
* 创建SV着色器(明度线性着色器 + 饱和度线性着色器)
*
* @return 着色器
*/
private ComposeShader generateSVShader() {
//明度线性着色器
if (mValShader == null) {
mValShader = new LinearGradient(mSatValRect.left, mSatValRect.top, mSatValRect.left, mSatValRect.bottom,
0xffffffff, 0xff000000, Shader.TileMode.CLAMP);
}
//饱和线性着色器
Shader satShader = new LinearGradient(mSatValRect.left, mSatValRect.top, mSatValRect.right, mSatValRect.top,
0xffffffff, Color.HSVToColor(new float[]{mHue, 1f, 1f}), Shader.TileMode.CLAMP);
//组合着色器 = 明度线性着色器 + 饱和度线性着色器
return new ComposeShader(mValShader, satShader, PorterDuff.Mode.MULTIPLY);
}
然后就是绘制选中的小圆环,主要还是看下面的代码吧
Content.color1是当前颜色
整体代码
public class ColorWheel extends View {
private Paint mColorWheelPaint;
private Paint mPointerHaloPaint;
private Paint mHaloPaint;
private Paint mPointerPaint;
private Paint mCirclePaint;
private int mPointerColor = -16777216;
private int mCircleColor = -16777216;
private int mColorWheelThickness;
private int mColorWheelRadius;
private int mPreferredColorWheelRadius;
private int mColorCenterRadius;
private int mPreferredColorCenterRadius;
private int mColorCenterHaloRadius;
private int mPreferredColorCenterHaloRadius;
private int mColorPointerRadius;
private int mColorPointerHaloRadius;
private RectF mColorWheelRectangle = new RectF();
private RectF mCenterRectangle = new RectF();
private RectF mSatValRect = new RectF();
private boolean outerMovingPointer = false;
private int mCenterNewColor;
private float mTranslationOffset;
private float mSlopX;
private float mSlopY;
private double mAngle;
private Paint mCenterOldPaint;
private Paint mCenterNewPaint;
private Shader mValShader;
private float mHue = 360f;
private float mSat = 1f;
private float mVal = 1f;
private OnColorChangedListener onColorChangedListener;
private int mColorRadius;
private int mColorHaloRadius;
private boolean insideMovingPointer = false;
private boolean mShowCenterOldColor = false;
public ColorWheel(Context context) {
super(context);
init(null, 0);
}
public ColorWheel(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public ColorWheel(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
public interface OnColorChangedListener {
void onColorChanged(float[] colorHSV);
void onColorFinish(float[] colorHSV);
}
public void setOnColorChangedListener(OnColorChangedListener listener) {
this.onColorChangedListener = listener;
}
public OnColorChangedListener getOnColorChangedListener() {
return this.onColorChangedListener;
}
private int oldChangedListenerColor;
private void init(AttributeSet attrs, int defStyle) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
final TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.ColorWheel, defStyle, 0);
final Resources b = getContext().getResources();
mColorWheelThickness = a.getDimensionPixelSize(
R.styleable.ColorWheel_color_wheel_thickness,
b.getDimensionPixelSize(R.dimen.color_wheel_thickness));
mColorWheelRadius = a.getDimensionPixelSize(
R.styleable.ColorWheel_color_wheel_radius,
b.getDimensionPixelSize(R.dimen.color_wheel_radius));
mPreferredColorWheelRadius = mColorWheelRadius;
mColorCenterRadius = a.getDimensionPixelSize(
R.styleable.ColorWheel_color_center_radius,
b.getDimensionPixelSize(R.dimen.color_center_radius));
mPreferredColorCenterRadius = mColorCenterRadius;
mColorCenterHaloRadius = a.getDimensionPixelSize(
R.styleable.ColorWheel_color_center_halo_radius,
b.getDimensionPixelSize(R.dimen.color_center_halo_radius));
mPreferredColorCenterHaloRadius = mColorCenterHaloRadius;
mColorPointerRadius = a.getDimensionPixelSize(
R.styleable.ColorWheel_color_pointer_radius,
b.getDimensionPixelSize(R.dimen.dp_18));
mColorPointerHaloRadius = a.getDimensionPixelSize(
R.styleable.ColorWheel_color_pointer_halo_radius,
b.getDimensionPixelSize(R.dimen.dp_19));
mColorRadius = b.getDimensionPixelSize(R.dimen.dp_11);
mColorHaloRadius = b.getDimensionPixelSize(R.dimen.dp_12);
a.recycle();
Shader s = new SweepGradient(0, 0, ColorUtils.COLORS, null);
mColorWheelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mColorWheelPaint.setShader(s);
mColorWheelPaint.setStyle(Paint.Style.STROKE);
mColorWheelPaint.setStrokeWidth(mColorWheelThickness);
mPointerHaloPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPointerHaloPaint.setColor(Color.WHITE);
mPointerHaloPaint.setShadowLayer(5f, 0f, 0f, getContext().getResources().getColor(R.color.progress_shadow_b));
mHaloPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mHaloPaint.setColor(Color.WHITE);
mHaloPaint.setShadowLayer(5f, 0f, 0f, getContext().getResources().getColor(R.color.progress_shadow_b));
mPointerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPointerPaint.setColor(mPointerColor);
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setColor(mCircleColor);
mCenterNewPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCenterNewPaint.setStyle(Paint.Style.FILL);
mCenterOldPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCenterOldPaint.setColor(Color.HSVToColor(Content.color1));
mCenterOldPaint.setStyle(Paint.Style.FILL);
float[] hsv = Content.color1;
mHue = hsv[0];
mAngle = getColorAngle(mHue);
mSat = hsv[1];
mVal = hsv[2];
mCenterNewColor = Color.HSVToColor(Content.color1);
mCircleColor = Color.HSVToColor(new float[]{mHue, mSat, mVal});
mPointerColor = Color.HSVToColor(new float[]{getColorHue(mAngle), 1, 1});
mCenterNewPaint.setColor(mCenterNewColor);
mCirclePaint.setColor(mCircleColor);
mPointerPaint.setColor(mPointerColor);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.translate(mTranslationOffset, mTranslationOffset);
canvas.drawOval(mColorWheelRectangle, mColorWheelPaint);
float[] pointerPosition = calculatePointerPosition(mAngle);
Log.d("位置", "onDraw: " + pointerPosition[0] + "/" + pointerPosition[1]);
canvas.drawCircle(pointerPosition[0], pointerPosition[1], mColorPointerHaloRadius, mPointerHaloPaint);
canvas.drawCircle(pointerPosition[0], pointerPosition[1], mColorPointerRadius, mPointerPaint);
drawSatValPanel(canvas);
}
/**
* 绘制S、V选择区域(矩形)
*
* @param canvas 画布
*/
private void drawSatValPanel(Canvas canvas) {
//组合着色器 = 明度线性着色器 + 饱和度线性着色器
ComposeShader mShader = generateSVShader();
mCenterNewPaint.setShader(mShader);
canvas.drawArc(mSatValRect, 0, 360, true, mCenterNewPaint);
//初始化选择器的位置
Point p = satValToPoint(mSat, mVal);
int radius = mColorHaloRadius * 2;
if (mShowCenterOldColor) {
canvas.drawCircle(p.x, p.y, radius + 3, mHaloPaint);
canvas.drawArc((p.x - radius), (p.y - radius), (p.x + radius), (p.y + radius), 90, 180, true, mCenterOldPaint);
canvas.drawArc((p.x - radius), (p.y - radius), (p.x + radius), (p.y + radius), 270, 180, true, mCirclePaint);
} else {
canvas.drawCircle(p.x, p.y, mColorHaloRadius, mHaloPaint);
canvas.drawCircle(p.x, p.y, mColorRadius, mCirclePaint);
}
}
/**
* 创建SV着色器(明度线性着色器 + 饱和度线性着色器)
*
* @return 着色器
*/
private ComposeShader generateSVShader() {
//明度线性着色器
if (mValShader == null) {
mValShader = new LinearGradient(mSatValRect.left, mSatValRect.top, mSatValRect.left, mSatValRect.bottom,
0xffffffff, 0xff000000, Shader.TileMode.CLAMP);
}
//饱和线性着色器
Shader satShader = new LinearGradient(mSatValRect.left, mSatValRect.top, mSatValRect.right, mSatValRect.top,
0xffffffff, Color.HSVToColor(new float[]{mHue, 1f, 1f}), Shader.TileMode.CLAMP);
//组合着色器 = 明度线性着色器 + 饱和度线性着色器
return new ComposeShader(mValShader, satShader, PorterDuff.Mode.MULTIPLY);
}
private Point satValToPoint(float sat, float val) {
final float height = mSatValRect.height();
final float width = mSatValRect.width();
Point p = new Point();
float newX = sat * width + mSatValRect.left;
float newY = (1f - val) * height + mSatValRect.top;
//点击位置x坐标与圆心的x坐标的距离
float distanceX = Math.abs(newX);
//点击位置y坐标与圆心的y坐标的距离
float distanceY = Math.abs(newY);
//点击位置与圆心的直线距离
int distanceZ = (int) Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
//如果点击位置与圆心的距离大于圆的半径,证明点击位置没有在圆内
if (distanceZ > mColorCenterRadius) {
p.x = (int) ((mColorCenterRadius * newX) / distanceZ);
p.y = (int) ((mColorCenterRadius * newY) / distanceZ);
} else {
p.x = (int) newX;
p.y = (int) newY;
}
return p;
}
private float[] pointToSatVal(float x, float y) {
final RectF rect = mSatValRect;
float[] result = new float[2];
float width = rect.width();
float height = rect.height();
if (x < rect.left) {
x = 0f;
} else if (x > rect.right) {
x = width;
} else {
x = x - rect.left;
}
if (y < rect.top) {
y = 0f;
} else if (y > rect.bottom) {
y = height;
} else {
y = y - rect.top;
}
result[0] = 1.f / width * x;
result[1] = 1.f - (1.f / height * y);
return result;
}
@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int intrinsicSize = 2 * (mPreferredColorWheelRadius + mColorPointerHaloRadius);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(intrinsicSize, widthSize);
} else {
width = intrinsicSize;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(intrinsicSize, heightSize);
} else {
height = intrinsicSize;
}
int min = Math.min(width, height);
setMeasuredDimension(min, min);
mTranslationOffset = min * 0.5f;
// fill the rectangle instances.
mColorWheelRadius = (int) (mTranslationOffset - mColorWheelThickness / 2);
mColorWheelRectangle.set(-mColorWheelRadius, -mColorWheelRadius,
mColorWheelRadius, mColorWheelRadius);
mColorCenterRadius = (int) ((float) mPreferredColorCenterRadius * ((float) mColorWheelRadius / (float) mPreferredColorWheelRadius));
mColorCenterHaloRadius = (int) ((float) mPreferredColorCenterHaloRadius * ((float) mColorWheelRadius / (float) mPreferredColorWheelRadius));
mCenterRectangle.set(-mColorCenterRadius / 2f, -mColorCenterRadius / 2f,
mColorCenterRadius / 2f, mColorCenterRadius / 2f);
mSatValRect.set(-mColorCenterRadius, -mColorCenterRadius,
mColorCenterRadius, mColorCenterRadius);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX() - mTranslationOffset;
float y = event.getY() - mTranslationOffset;
double sqrt = Math.sqrt(x * x + y * y);
float[] result = pointToSatVal(event.getX() - mColorWheelRadius - mColorCenterRadius / 2f, event.getY() - mColorWheelRadius - mColorCenterRadius / 2f);
int color;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
float[] pointerPosition = calculatePointerPosition(mAngle);
if (x >= (pointerPosition[0] - mColorPointerHaloRadius)
&& x <= (pointerPosition[0] + mColorPointerHaloRadius)
&& y >= (pointerPosition[1] - mColorPointerHaloRadius)
&& y <= (pointerPosition[1] + mColorPointerHaloRadius)) {
mSlopX = x - pointerPosition[0];
mSlopY = y - pointerPosition[1];
outerMovingPointer = true;
insideMovingPointer = false;
mColorPointerRadius = getContext().getResources().getDimensionPixelSize(R.dimen.dp_21);
mColorPointerHaloRadius = getContext().getResources().getDimensionPixelSize(R.dimen.dp_23);
} else if (x >= -mColorCenterRadius && x <= mColorCenterRadius
&& y >= -mColorCenterRadius && y <= mColorCenterRadius) {
mSat = result[0];
mVal = result[1];
outerMovingPointer = true;
insideMovingPointer = true;
mShowCenterOldColor = false;
} else {
if (sqrt <= mColorWheelRadius + mColorPointerHaloRadius && sqrt >= mColorWheelRadius - mColorPointerHaloRadius) {
outerMovingPointer = true;
insideMovingPointer = false;
invalidate();
} else {
getParent().requestDisallowInterceptTouchEvent(false);
return false;
}
}
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (outerMovingPointer) {
if (insideMovingPointer) {
mSat = result[0];
mVal = result[1];
color = Color.HSVToColor(new float[]{mHue, mSat, mVal});
if (onColorChangedListener != null && color != oldChangedListenerColor) {
mCirclePaint.setColor(color);
onColorChangedListener.onColorChanged(new float[]{mHue, mSat, mVal});
oldChangedListenerColor = color;
}
mShowCenterOldColor = true;
} else {
mAngle = Math.atan2(y - mSlopY, x - mSlopX);
mHue = getColorHue(mAngle);
Log.d("测试", "onTouchEvent: " + mAngle + "/" + mHue);
int colors = Color.HSVToColor(new float[]{mHue, 1, 1});
mPointerPaint.setColor(colors);
mCenterOldPaint.setColor(colors);
mCenterNewColor = colors;
mCenterNewPaint.setColor(colors);
colors = Color.HSVToColor(new float[]{mHue, mSat, mVal});
mCircleColor = colors;
mCirclePaint.setColor(colors);
if (onColorChangedListener != null && mCenterNewColor != oldChangedListenerColor) {
onColorChangedListener.onColorChanged(new float[]{mHue, mSat, mVal});
oldChangedListenerColor = colors;
}
}
} else {
getParent().requestDisallowInterceptTouchEvent(false);
return false;
}
break;
case MotionEvent.ACTION_UP:
outerMovingPointer = false;
mShowCenterOldColor = false;
mColorPointerRadius = getContext().getResources().getDimensionPixelSize(R.dimen.dp_18);
mColorPointerHaloRadius = getContext().getResources().getDimensionPixelSize(R.dimen.dp_19);
if (onColorChangedListener != null) {
onColorChangedListener.onColorFinish(new float[]{mHue, mSat, mVal});
}
break;
default:
break;
}
invalidate();
return true;
}
private float[] calculatePointerPosition(double angle) {
float x = (float) (mColorWheelRadius * Math.cos(angle));
float y = (float) (mColorWheelRadius * Math.sin(angle));
return new float[]{x, y};
}
public void setCenterColor(float[] hsv) {
int color = Color.HSVToColor(hsv);
mCenterNewColor = color;
mCenterNewPaint.setColor(color);
float[] oldColor = new float[3];
oldColor[0] = hsv[0];
oldColor[1] = 1;
oldColor[2] = 1;
mCenterOldPaint.setColor(Color.HSVToColor(oldColor));
mHue = hsv[0];
mSat = hsv[1];
mVal = hsv[2];
mAngle = getColorAngle(mHue);
mCircleColor = color;
mCirclePaint.setColor(color);
int colors = Color.HSVToColor(new float[]{mHue, 1f, 1f});
mPointerColor = colors;
mPointerPaint.setColor(colors);
invalidate();
}
private double getColorAngle(float hue) {
double angle;
float unit = 1 - hue / 360f;
if (unit >= 0.5f) {
angle = (unit * (2 * Math.PI)) - Math.PI * 2;
} else {
angle = unit * (2 * Math.PI);
}
return angle;
}
private float getColorHue(double angle) {
float hue = 0;
if (angle >= 0) {
hue = (float) (1 - angle / (2 * Math.PI)) * 360;
} else {
hue = (float) (1 - (2 * Math.PI + angle) / (2 * Math.PI)) * 360;
}
return hue;
}
}
attrs.xml
<declare-styleable name="ColorWheel">
<attr name="color_wheel_radius" format="dimension" />
<attr name="color_wheel_thickness" format="dimension" />
<attr name="color_center_radius" format="dimension" />
<attr name="color_center_halo_radius" format="dimension" />
<attr name="color_pointer_radius" format="dimension" />
<attr name="color_pointer_halo_radius" format="dimension" />
</declare-styleable>
layout.xml
<com.suina.custom.view.ColorWheel
android:id="@+id/picker"
android:layout_width="@dimen/dp_250"
android:layout_height="@dimen/dp_250"
android:layout_margin="@dimen/dp_10"
app:color_center_halo_radius="@dimen/dp_100"
app:color_center_radius="@dimen/dp_120"
app:color_wheel_radius="@dimen/dp_180"
app:color_wheel_thickness="@dimen/dp_45" />