package com.swifty.fillcolor.view; /* * Copyright 2013 Piotr Adamus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ComposeShader; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint.Join; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.RadialGradient; import android.graphics.RectF; import android.graphics.Shader.TileMode; import android.graphics.SweepGradient; import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import com.swifty.fillcolor.util.L; public class ColorPicker extends View { /** * Customizable display parameters (in percents) */ private final int paramOuterPadding = 2; // outer padding of the whole color picker view private final int paramInnerPadding = 5; // distance between value slider wheel and inner color wheel private final int paramValueSliderWidth = 10; // width of the value slider private final int paramArrowPointerSize = 4; // size of the arrow pointer; set to 0 to hide the pointer private Paint colorWheelPaint; private Paint valueSliderPaint; //private Paint colorViewPaint; private Paint colorPointerPaint; private RectF colorPointerCoords; private Paint valuePointerPaint; private Paint valuePointerArrowPaint; private RectF outerWheelRect; private RectF innerWheelRect; private Path colorViewPath; private Path valueSliderPath; private Path arrowPointerPath; private Bitmap colorWheelBitmap; private int valueSliderWidth; private int innerPadding; private int outerPadding; private int arrowPointerSize; private int outerWheelRadius; private int innerWheelRadius; private int colorWheelRadius; private Matrix gradientRotationMatrix; /** * Currently selected color */ private float[] colorHSV = new float[]{0f, 0f, 1f}; public void setOnChangedListener(OnColorChangedListener onChangedListener) { this.onChangedListener = onChangedListener; } private OnColorChangedListener onChangedListener; public ColorPicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public ColorPicker(Context context, AttributeSet attrs) { super(context, attrs); init(); } public ColorPicker(Context context) { super(context); init(); } private void init() { colorPointerPaint = new Paint(); colorPointerPaint.setStyle(Style.STROKE); colorPointerPaint.setStrokeWidth(2f); colorPointerPaint.setARGB(255, 0, 0, 0); valuePointerPaint = new Paint(); valuePointerPaint.setStyle(Style.STROKE); valuePointerPaint.setStrokeWidth(2f); valuePointerArrowPaint = new Paint(); colorWheelPaint = new Paint(); colorWheelPaint.setAntiAlias(true); colorWheelPaint.setDither(true); valueSliderPaint = new Paint(); valueSliderPaint.setAntiAlias(true); valueSliderPaint.setDither(true); // colorViewPaint = new Paint(); // colorViewPaint.setAntiAlias(true); colorViewPath = new Path(); valueSliderPath = new Path(); arrowPointerPath = new Path(); outerWheelRect = new RectF(); innerWheelRect = new RectF(); colorPointerCoords = new RectF(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int size = Math.min(widthSize, heightSize); setMeasuredDimension(size, size); } @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { int centerX = getWidth() / 2; int centerY = getHeight() / 2; // drawing color wheel canvas.drawBitmap(colorWheelBitmap, centerX - colorWheelRadius, centerY - colorWheelRadius, null); // drawing color view // colorViewPaint.setColor(Color.HSVToColor(colorHSV)); // canvas.drawPath(colorViewPath, colorViewPaint); // drawing value slider float[] hsv = new float[]{colorHSV[0], colorHSV[1], 1f}; SweepGradient sweepGradient = new SweepGradient(centerX, centerY, new int[]{Color.BLACK, Color.HSVToColor(hsv), Color.WHITE}, null); sweepGradient.setLocalMatrix(gradientRotationMatrix); valueSliderPaint.setShader(sweepGradient); canvas.drawPath(valueSliderPath, valueSliderPaint); // drawing color wheel pointer float hueAngle = (float) Math.toRadians(colorHSV[0]); int colorPointX = (int) (-Math.cos(hueAngle) * colorHSV[1] * colorWheelRadius) + centerX; int colorPointY = (int) (-Math.sin(hueAngle) * colorHSV[1] * colorWheelRadius) + centerY; float pointerRadius = 0.075f * colorWheelRadius; int pointerX = (int) (colorPointX - pointerRadius / 2); int pointerY = (int) (colorPointY - pointerRadius / 2); colorPointerCoords.set(pointerX, pointerY, pointerX + pointerRadius, pointerY + pointerRadius); canvas.drawOval(colorPointerCoords, colorPointerPaint); // drawing value pointer valuePointerPaint.setColor(Color.HSVToColor(new float[]{0f, 0f, 1f - colorHSV[2]})); double valueAngle = (colorHSV[2] - 0.5f) * Math.PI; float valueAngleX = (float) Math.cos(valueAngle); float valueAngleY = (float) Math.sin(valueAngle); canvas.drawLine(valueAngleX * innerWheelRadius + centerX, valueAngleY * innerWheelRadius + centerY, valueAngleX * outerWheelRadius + centerX, valueAngleY * outerWheelRadius + centerY, valuePointerPaint); // drawing pointer arrow if (arrowPointerSize > 0) { drawPointerArrow(canvas); } } private void drawPointerArrow(Canvas canvas) { int centerX = getWidth() / 2; int centerY = getHeight() / 2; double tipAngle = (colorHSV[2] - 0.5f) * Math.PI; double leftAngle = tipAngle + Math.PI / 96; double rightAngle = tipAngle - Math.PI / 96; double tipAngleX = Math.cos(tipAngle) * outerWheelRadius; double tipAngleY = Math.sin(tipAngle) * outerWheelRadius; double leftAngleX = Math.cos(leftAngle) * (outerWheelRadius + arrowPointerSize); double leftAngleY = Math.sin(leftAngle) * (outerWheelRadius + arrowPointerSize); double rightAngleX = Math.cos(rightAngle) * (outerWheelRadius + arrowPointerSize); double rightAngleY = Math.sin(rightAngle) * (outerWheelRadius + arrowPointerSize); arrowPointerPath.reset(); arrowPointerPath.moveTo((float) tipAngleX + centerX, (float) tipAngleY + centerY); arrowPointerPath.lineTo((float) leftAngleX + centerX, (float) leftAngleY + centerY); arrowPointerPath.lineTo((float) rightAngleX + centerX, (float) rightAngleY + centerY); arrowPointerPath.lineTo((float) tipAngleX + centerX, (float) tipAngleY + centerY); valuePointerArrowPaint.setColor(Color.HSVToColor(colorHSV)); valuePointerArrowPaint.setStyle(Style.FILL); canvas.drawPath(arrowPointerPath, valuePointerArrowPaint); valuePointerArrowPaint.setStyle(Style.STROKE); valuePointerArrowPaint.setStrokeJoin(Join.ROUND); valuePointerArrowPaint.setColor(Color.BLACK); canvas.drawPath(arrowPointerPath, valuePointerArrowPaint); } @Override protected void onSizeChanged(int width, int height, int oldw, int oldh) { int centerX = width / 2; int centerY = height / 2; innerPadding = (int) (paramInnerPadding * width / 100); outerPadding = (int) (paramOuterPadding * width / 100); arrowPointerSize = (int) (paramArrowPointerSize * width / 100); valueSliderWidth = (int) (paramValueSliderWidth * width / 100); outerWheelRadius = width / 2 - outerPadding - arrowPointerSize; innerWheelRadius = outerWheelRadius - valueSliderWidth; colorWheelRadius = innerWheelRadius - innerPadding; outerWheelRect.set(centerX - outerWheelRadius, centerY - outerWheelRadius, centerX + outerWheelRadius, centerY + outerWheelRadius); innerWheelRect.set(centerX - innerWheelRadius, centerY - innerWheelRadius, centerX + innerWheelRadius, centerY + innerWheelRadius); colorWheelBitmap = createColorWheelBitmap(colorWheelRadius * 2, colorWheelRadius * 2); gradientRotationMatrix = new Matrix(); gradientRotationMatrix.preRotate(270, width / 2, height / 2); colorViewPath.arcTo(outerWheelRect, 270, -180); colorViewPath.arcTo(innerWheelRect, 90, 180); valueSliderPath.arcTo(outerWheelRect, 288, 162); valueSliderPath.arcTo(innerWheelRect, 90, -162); } private Bitmap createColorWheelBitmap(int width, int height) { Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); int colorCount = 12; int colorAngleStep = 360 / 12; int colors[] = new int[colorCount + 1]; float hsv[] = new float[]{0f, 1f, 1f}; for (int i = 0; i < colors.length; i++) { hsv[0] = (i * colorAngleStep + 180) % 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, 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); return bitmap; } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: int x = (int) event.getX(); int y = (int) event.getY(); int cx = x - getWidth() / 2; int cy = y - getHeight() / 2; double d = Math.sqrt(cx * cx + cy * cy); if (d <= colorWheelRadius) { colorHSV[0] = (float) (Math.toDegrees(Math.atan2(cy, cx)) + 180f); colorHSV[1] = Math.max(0f, Math.min(1f, (float) (d / colorWheelRadius))); if (onChangedListener != null) { onChangedListener.colorChangedListener(getColor()); } invalidate(); } else if (x >= getWidth() / 2 && d >= innerWheelRadius) { float light = (float) Math.max(0, Math.min(1, Math.atan2(cy, cx) / Math.PI + 0.5f)); colorHSV[2] = light > 0.1 ? light : (float) 0.1; if (onChangedListener != null) { onChangedListener.colorChangedListener(getColor()); } invalidate(); } L.e("color2", colorHSV[0] + ";" + colorHSV[1] + ";" + colorHSV[2]); return true; } return super.onTouchEvent(event); } public void setColor(int color) { Color.colorToHSV(color, colorHSV); invalidate(); } public int getColor() { return Color.HSVToColor(colorHSV); } @Override protected Parcelable onSaveInstanceState() { Bundle state = new Bundle(); state.putFloatArray("color", colorHSV); state.putParcelable("super", super.onSaveInstanceState()); return state; } @Override protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { Bundle bundle = (Bundle) state; colorHSV = bundle.getFloatArray("color"); super.onRestoreInstanceState(bundle.getParcelable("super")); } else { super.onRestoreInstanceState(state); } } public interface OnColorChangedListener { void colorChangedListener(int color); } }