add color picker by Martin Stone

Add own modification of HSV-Alpha Color Picker for Android https://github.com/martin-stone/hsv-alpha-color-picker-android
This commit is contained in:
Vologhat
2022-12-02 18:37:12 +03:00
parent d2911e5b6b
commit f45de44c75
12 changed files with 982 additions and 0 deletions
@@ -0,0 +1,53 @@
package ru.vtosters.lite.ui.views.rarepebble;
import android.content.Context;
import android.graphics.Bitmap;
public class AlphaView extends SliderViewBase implements ColorObserver {
private ObservableColor observableColor = new ObservableColor(0);
public AlphaView(Context context) {
super(context);
}
public void observeColor(ObservableColor observableColor) {
this.observableColor = observableColor;
observableColor.addObserver(this);
}
@Override
public void updateColor(ObservableColor observableColor) {
setPos((float)observableColor.getAlpha()/0xff);
updateBitmap();
invalidate();
}
@Override
protected void notifyListener(float currentPos) {
observableColor.updateAlpha((int)(currentPos * 0xff), this);
}
@Override
protected int getPointerColor(float currentPos) {
float solidColorLightness = observableColor.getLightness();
float posLightness = 1 + currentPos * (solidColorLightness - 1);
return posLightness > 0.5f ? 0xff000000 : 0xffffffff;
}
@Override
protected Bitmap makeBitmap(int w, int h) {
final boolean isWide = w > h;
final int n = Math.max(w, h);
int color = observableColor.getColor();
int[] colors = new int[n];
for (int i = 0; i < n; ++i) {
float alpha = isWide ? (float)i / n : 1 - (float)i / n;
colors[i] = color & 0xffffff | (int)(alpha * 0xff) << 24;
}
final int bmpWidth = isWide ? w : 1;
final int bmpHeight = isWide ? 1 : h;
return Bitmap.createBitmap(colors, bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
}
}
@@ -0,0 +1,6 @@
package ru.vtosters.lite.ui.views.rarepebble;
public interface ColorObserver {
void updateColor(ObservableColor observableColor);
}
@@ -0,0 +1,148 @@
package ru.vtosters.lite.ui.views.rarepebble;
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.InputFilter;
import android.text.InputType;
import android.text.method.DigitsKeyListener;
import android.view.Gravity;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import ru.vtosters.lite.utils.LayoutUtils;
public class ColorPickerView extends FrameLayout {
private AlphaView alphaView;
private EditText hexEdit;
private ObservableColor observableColor = new ObservableColor(0);
private SwatchView swatchView;
public ColorPickerView(Context context) {
super(context);
var container = new LinearLayout(context);
container.setOrientation(LinearLayout.VERTICAL);
container.setPadding(dp2px(16), dp2px(16), dp2px(16), dp2px(16));
container.setGravity(Gravity.CENTER);
this.addView(container, LayoutUtils.createFrame(-2, -2));
var colorPickerFrame = new FrameLayout(context);
var colorPickerFrameParams = LayoutUtils.createLinear(-2, -2);
colorPickerFrameParams.weight = 1.0f;
container.addView(colorPickerFrame, colorPickerFrameParams);
swatchView = new SwatchView(context);
colorPickerFrame.addView(swatchView, LayoutUtils.createFrame(-2, -2));
HueSatView hueSatView = new HueSatView(context);
colorPickerFrame.addView(hueSatView, LayoutUtils.createFrame(-2, -2));
ValueView valueView = new ValueView(context);
var valueViewParams = LayoutUtils.createLinear(-1, dp2px(36));
valueViewParams.topMargin = dp2px(16);
container.addView(valueView, valueViewParams);
alphaView = new AlphaView(context);
var alphaViewParams = LayoutUtils.createLinear(-1, dp2px(36));
valueViewParams.topMargin = dp2px(16);
container.addView(alphaView, alphaViewParams);
hexEdit = new EditText(context);
hexEdit.setText("00000000");
hexEdit.setKeyListener(DigitsKeyListener.getInstance("0123456789ABCDEFabcdef"));
hexEdit.setGravity(Gravity.CENTER_HORIZONTAL);
hexEdit.setFilters(new InputFilter[]{ new InputFilter.LengthFilter(4) });
hexEdit.setBackground(null);
hexEdit.setTypeface(Typeface.MONOSPACE);
hexEdit.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
hexEdit.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
container.addView(hexEdit, LayoutUtils.createLinear(-1, -2));
swatchView.observeColor(observableColor);
hueSatView.observeColor(observableColor);
valueView.observeColor(observableColor);
alphaView.observeColor(observableColor);
HexEdit.setUpListeners(hexEdit, observableColor);
showAlpha(true);
showHex(true);
showPreview(true);
// We get all our state saved and restored for free,
// thanks to the EditText and its listeners!
}
/** Returns the color selected by the user */
public int getColor() {
return observableColor.getColor();
}
/**
* Sets the original color swatch and the current color to the specified value.<br>
* Using dark colors may lead to a loss of color information caused by the ARGB color space.
* Use {@link #setColor(int, float, float, float)} instead to avoid this.
*/
public void setColor(int color) {
setOriginalColor(color);
setCurrentColor(color);
}
/**
* Sets the original color swatch and the current color to the specified value.
*
* <ul>
* <li>Alpha [0...255]</li>
* <li>Hue [0...360)</li>
* <li>Saturation [0...1]</li>
* <li>Brightness [0...1]</li>
* </ul>
*/ public void setColor(int alpha, float hue, float sat, float bri) {
setOriginalColor(alpha, hue, sat, bri);
setCurrentColor(alpha, hue, sat, bri);
}
/** Sets the original color swatch without changing the current color. */
public void setOriginalColor(int color) {
swatchView.setOriginalColor(color);
}
/** Sets the original color swatch without changing the current color. */
public void setOriginalColor(int alpha, float hue, float sat, float bri) {
int color = Color.HSVToColor(alpha, new float[]{hue, sat, bri});
swatchView.setOriginalColor(color);
}
/** Updates the current color without changing the original color swatch. */
public void setCurrentColor(int color) {
observableColor.updateColor(color, null);
}
/** Updates the current color without changing the original color swatch. */
public void setCurrentColor(int alpha, float hue, float sat, float bri) {
observableColor.updateColor(alpha, hue, sat, bri, null);
}
public void showAlpha(boolean showAlpha) {
alphaView.setVisibility(showAlpha ? View.VISIBLE : View.GONE);
HexEdit.setShowAlphaDigits(hexEdit, showAlpha);
}
public void addColorObserver(ColorObserver observer) {
observableColor.addObserver(observer);
}
public void showHex(boolean showHex) {
hexEdit.setVisibility(showHex ? View.VISIBLE : View.GONE);
}
public void showPreview(boolean showPreview) {
swatchView.setVisibility(showPreview ? View.VISIBLE : View.GONE);
}
}
@@ -0,0 +1,96 @@
package ru.vtosters.lite.ui.views.rarepebble;
import android.graphics.Color;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Spanned;
import android.text.TextWatcher;
import android.widget.EditText;
class HexEdit {
private static InputFilter[] withoutAlphaDigits = {new ColorPasteLengthFilter()};
private static InputFilter[] withAlphaDigits = {new InputFilter.LengthFilter(8)};
public static void setUpListeners(final EditText hexEdit, final ObservableColor observableColor) {
class MultiObserver implements ColorObserver, TextWatcher {
@Override
public void updateColor(ObservableColor observableColor) {
final String colorString = formatColor(observableColor.getColor());
// Prevent onTextChanged getting called when we update text programmatically
hexEdit.removeTextChangedListener(this);
hexEdit.setText(colorString);
hexEdit.addTextChangedListener(this);
}
private String formatColor(int color) {
return shouldTrimAlphaDigits()
? String.format("%06x", color & 0x00ffffff)
: String.format("%08x", color);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
int color = parseHexColor(s);
if (shouldTrimAlphaDigits()) color = color | 0xff000000;
observableColor.updateColor(color, this);
}
@Override
public void afterTextChanged(Editable s) {
}
private boolean shouldTrimAlphaDigits() {
return hexEdit.getFilters() == withoutAlphaDigits;
}
}
final MultiObserver multiObserver = new MultiObserver();
hexEdit.addTextChangedListener(multiObserver);
observableColor.addObserver(multiObserver);
setShowAlphaDigits(hexEdit, true);
}
private static int parseHexColor(CharSequence s) {
try {
return (int)(Long.parseLong(s.toString(), 16) & 0xffffffffL);
}
catch (NumberFormatException e) {
return Color.GRAY;
}
}
public static void setShowAlphaDigits(final EditText hexEdit, boolean showAlphaDigits) {
hexEdit.setFilters(showAlphaDigits ? withAlphaDigits : withoutAlphaDigits);
hexEdit.setText(hexEdit.getText()); // trigger a reformat of text
}
private static class ColorPasteLengthFilter implements InputFilter {
private static final int MAX_LENGTH = 6;
private static final int PASTED_LEN = 8;
private final InputFilter sixDigitFilter = new InputFilter.LengthFilter(MAX_LENGTH);
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
// If 8 digits have been pasted, replacing all source, trim alpha digits.
// Otherwise standard LengthFilter behavior.
final int srcLength = end - start;
final int dstSelLength = dend - dstart;
if (srcLength == PASTED_LEN && dstSelLength == dest.length()) {
// Discard alpha digits:
return source.subSequence(PASTED_LEN - MAX_LENGTH, PASTED_LEN);
}
else {
return sixDigitFilter.filter(source, start, end, dest, dstart, dend);
}
}
}
}
@@ -0,0 +1,205 @@
package ru.vtosters.lite.ui.views.rarepebble;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
public class HueSatView extends SquareView implements ColorObserver {
private Paint borderPaint;
private Paint pointerPaint;
private Path pointerPath;
private Path borderPath;
private Rect viewRect = new Rect();
private int w;
private int h;
// Bitmap is generated once and shared across instances.
private static Bitmap bitmap;
private final PointF pointer = new PointF();
private ObservableColor observableColor = new ObservableColor(0);
public HueSatView(Context context) {
super(context);
borderPaint = Resources.makeLinePaint();
pointerPaint = Resources.makeLinePaint();
pointerPaint.setColor(0xff000000);
pointerPath = Resources.makePointerPath();
borderPath = new Path();
if (bitmap == null) {
bitmap = makeBitmap(optimalBitmapSize());
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Need clipPath() and setLayerType()...
throw new UnsupportedOperationException("Android API 10 and below is not supported.");
}
else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
// clipPath() is only supported on a software layer.
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
}
private int optimalBitmapSize() {
final int scale = 2;
final int maxBitmapSize = 128;
final DisplayMetrics dm = getResources().getDisplayMetrics();
return Math.min(maxBitmapSize, Math.min(dm.widthPixels, dm.heightPixels) / scale);
}
public void observeColor(ObservableColor observableColor) {
this.observableColor = observableColor;
observableColor.addObserver(this);
}
@Override
public void updateColor(ObservableColor observableColor) {
setPointer(pointer, observableColor.getHue(), observableColor.getSat(), w);
optimisePointerColor();
invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
this.w = w;
this.h = h;
viewRect.set(0, 0, w, h);
float inset = borderPaint.getStrokeWidth() / 2;
makeBorderPath(borderPath, w, h, inset);
// Sets pointer position
updateColor(observableColor);
}
private static void makeBorderPath(Path borderPath, int w, int h, float inset) {
w -= inset;
h -= inset;
borderPath.reset();
borderPath.moveTo(w, inset);
borderPath.lineTo(w, h);
borderPath.lineTo(inset, h);
borderPath.addArc(new RectF(inset, inset, 2 * w, 2 * h), 180, 270);
borderPath.close();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
boolean withinPicker = clamp(pointer, event.getX(), event.getY(), true);
if (withinPicker) update();
return withinPicker;
case MotionEvent.ACTION_MOVE:
clamp(pointer, event.getX(), event.getY(), false);
update();
getParent().requestDisallowInterceptTouchEvent(true);
return true;
}
return super.onTouchEvent(event);
}
private void update() {
observableColor.updateHueSat(
hueForPos(pointer.x, pointer.y, w),
satForPos(pointer.x, pointer.y, w),
this);
optimisePointerColor();
invalidate();
}
private boolean clamp(PointF pointer, float x, float y, boolean rejectOutside) {
x = Math.min(x, w);
y = Math.min(y, h);
final float dx = w - x;
final float dy = h - y;
final float r = (float)Math.sqrt(dx * dx + dy * dy);
boolean outside = r > w;
if (!outside || !rejectOutside) {
if (outside) {
x = w - dx * w / r;
y = w - dy * w / r;
}
pointer.set(x, y);
}
return !outside;
}
private void optimisePointerColor() {
pointerPaint.setColor(
observableColor.getLightnessWithValue(1) > 0.5
? 0xff000000 : 0xffffffff);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.clipPath(borderPath);
canvas.drawBitmap(bitmap, null, viewRect, null);
canvas.translate(pointer.x, pointer.y);
canvas.drawPath(pointerPath, pointerPaint);
canvas.restore();
canvas.drawPath(borderPath, borderPaint);
}
private static Bitmap makeBitmap(int radiusPx) {
int[] colors = new int[radiusPx * radiusPx];
float[] hsv = new float[]{0f, 0f, 1f};
for (int y = 0; y < radiusPx; ++y) {
for (int x = 0; x < radiusPx; ++x) {
final int i = x + y * radiusPx;
final float sat = satForPos(x, y, radiusPx);
final float arcBleed = 2f / radiusPx; // extend curved edge pixels just outside clip area.
if (sat <= 1 + arcBleed) {
hsv[0] = hueForPos(x, y, radiusPx);
hsv[1] = sat;
colors[i] = Color.HSVToColor(0xff, hsv);
}
}
}
return Bitmap.createBitmap(colors, radiusPx, radiusPx, Bitmap.Config.ARGB_8888);
}
private static float hueForPos(float x, float y, float radiusPx) {
final double r = radiusPx - 1; // gives values 0...1 inclusive
final double dx = (r - x) / r;
final double dy = (r - y) / r;
final double angle = Math.atan2(dy, dx);
final double hue = 360 * angle / (Math.PI / 2);
return (float)hue;
}
private static float satForPos(float x, float y, float radiusPx) {
final double r = radiusPx - 1; // gives values 0...1 inclusive
final double dx = (r - x) / r;
final double dy = (r - y) / r;
final double sat = dx * dx + dy * dy; // leave it squared -- exaggerates pale colors
return (float)sat;
}
private static void setPointer(PointF pointer, float hue, float sat, float radiusPx) {
final float r = radiusPx - 1; // for values 0...1 inclusive
final double distance = r * Math.sqrt(sat);
final double angle = hue / 360 * Math.PI / 2;
final double dx = distance * Math.cos(angle);
final double dy = distance * Math.sin(angle);
pointer.set(r - (float)dx, r - (float)dy);
}
}
@@ -0,0 +1,97 @@
package ru.vtosters.lite.ui.views.rarepebble;
import android.graphics.Color;
import java.util.ArrayList;
import java.util.List;
public class ObservableColor {
// Store as HSV & A, otherwise round-trip to int causes color drift.
private final float[] hsv = {0, 0, 0};
private int alpha;
private final List<ColorObserver> observers = new ArrayList<ColorObserver>();
public ObservableColor(int color) {
Color.colorToHSV(color, hsv);
alpha = Color.alpha(color);
}
public void getHsv(float hsvOut[]) {
hsvOut[0] = hsv[0];
hsvOut[1] = hsv[1];
hsvOut[2] = hsv[2];
}
public int getColor() {
return Color.HSVToColor(alpha, hsv);
}
public float getHue() {
return hsv[0];
}
public float getSat() {
return hsv[1];
}
public float getValue() {
return hsv[2];
}
public int getAlpha() {
return alpha;
}
public float getLightness() {
return getLightnessWithValue(hsv[2]);
}
public float getLightnessWithValue(float value) {
float[] hsV = {hsv[0], hsv[1], value};
final int color = Color.HSVToColor(hsV);
return (Color.red(color) * 0.2126f + Color.green(color) * 0.7152f + Color.blue(color) * 0.0722f)/0xff;
}
public void addObserver(ColorObserver observer) {
observers.add(observer);
}
public void updateHueSat(float hue, float sat, ColorObserver sender) {
hsv[0] = hue;
hsv[1] = sat;
notifyOtherObservers(sender);
}
public void updateValue(float value, ColorObserver sender) {
hsv[2] = value;
notifyOtherObservers(sender);
}
public void updateAlpha(int alpha, ColorObserver sender) {
this.alpha = alpha;
notifyOtherObservers(sender);
}
public void updateColor(int color, ColorObserver sender) {
Color.colorToHSV(color, hsv);
alpha = Color.alpha(color);
notifyOtherObservers(sender);
}
public void updateColor(int alpha, float hue, float sat, float bri, ColorObserver sender) {
this.alpha = alpha;
hsv[0] = hue;
hsv[1] = sat;
hsv[2] = bri;
notifyOtherObservers(sender);
}
private void notifyOtherObservers(ColorObserver sender) {
for (ColorObserver observer : observers) {
if (observer != sender) {
observer.updateColor(this);
}
}
}
}
@@ -0,0 +1,46 @@
package ru.vtosters.lite.ui.views.rarepebble;
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Shader;
import ru.vtosters.lite.utils.AndroidUtils;
class Resources {
private static final float LINE_WIDTH_DIP = 1.5f;
private static final float POINTER_RADIUS_DIP = 7;
private static final int VIEW_OUTLINE_COLOR = 0xff808080;
public static Paint makeLinePaint() {
Paint paint = new Paint();
paint.setColor(VIEW_OUTLINE_COLOR);
paint.setStrokeWidth(dp2px(LINE_WIDTH_DIP));
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
return paint;
}
public static Paint makeCheckerPaint(Context context) {
Paint paint = new Paint();
final Bitmap checkerBmp = BitmapFactory.decodeResource(context.getResources(), AndroidUtils.getIdentifier("checker_background", "drawable"));
paint.setShader(new BitmapShader(checkerBmp, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
paint.setStrokeWidth(dp2px(LINE_WIDTH_DIP));
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
return paint;
}
public static Path makePointerPath() {
Path pointerPath = new Path();
final float radiusPx = dp2px(POINTER_RADIUS_DIP);
pointerPath.addCircle(0, 0, radiusPx, Path.Direction.CW);
return pointerPath;
}
}
@@ -0,0 +1,112 @@
package ru.vtosters.lite.ui.views.rarepebble;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.View;
public abstract class SliderViewBase extends View {
private final Paint borderPaint;
private final Paint checkerPaint;
private final Rect viewRect = new Rect();
private int w;
private int h;
private final Path borderPath;
private Bitmap bitmap;
private final Path pointerPath;
private final Paint pointerPaint;
private float currentPos;
public SliderViewBase(Context context) {
super(context);
checkerPaint = Resources.makeCheckerPaint(context);
borderPaint = Resources.makeLinePaint();
pointerPaint = Resources.makeLinePaint();
pointerPath = Resources.makePointerPath();
borderPath = new Path();
}
protected abstract void notifyListener(float currentPos);
protected abstract Bitmap makeBitmap(int w, int h);
protected abstract int getPointerColor(float currentPos);
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
this.w = w;
this.h = h;
viewRect.set(0, 0, w, h);
float inset = borderPaint.getStrokeWidth() / 2;
borderPath.reset();
borderPath.addRect(new RectF(inset, inset, w - inset, h - inset), Path.Direction.CW);
updateBitmap();
}
protected void setPos(float pos) {
currentPos = pos;
optimisePointerColor();
}
protected void updateBitmap() {
if (w > 0 && h > 0) {
bitmap = makeBitmap(w, h);
optimisePointerColor();
}
// else not ready yet
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
currentPos = valueForTouchPos(event.getX(), event.getY());
optimisePointerColor();
notifyListener(currentPos);
invalidate();
getParent().requestDisallowInterceptTouchEvent(true);
return true;
}
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(borderPath, checkerPaint);
canvas.drawBitmap(bitmap, null, viewRect, null);
canvas.drawPath(borderPath, borderPaint);
canvas.save();
if (isWide()) {
canvas.translate(w * currentPos, h / 2);
}
else {
canvas.translate(w / 2, h * (1 - currentPos));
}
canvas.drawPath(pointerPath, pointerPaint);
canvas.restore();
}
private boolean isWide() {
return w > h;
}
private float valueForTouchPos(float x, float y) {
final float val = isWide() ? x / w : 1 - y / h;
return Math.max(0, Math.min(1, val));
}
private void optimisePointerColor() {
pointerPaint.setColor(getPointerColor(currentPos));
}
}
@@ -0,0 +1,38 @@
package ru.vtosters.lite.ui.views.rarepebble;
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
import android.content.Context;
import android.view.View;
class SquareView extends View {
private static final int MIN_SIZE_DIP = 200;
private final int minSizePx;
public SquareView(Context context) {
super(context);
minSizePx = dp2px(MIN_SIZE_DIP);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Constrain to square
final int w = MeasureSpec.getSize(widthMeasureSpec);
final int h = MeasureSpec.getSize(heightMeasureSpec);
final int modeW = MeasureSpec.getMode(widthMeasureSpec);
final int modeH = MeasureSpec.getMode(heightMeasureSpec);
int size = minSizePx;
if (modeW == MeasureSpec.UNSPECIFIED) {
size = h;
}
else if (modeH == MeasureSpec.UNSPECIFIED) {
size = w;
}
else {
size = Math.min(w, h);
}
size = Math.max(size, minSizePx);
setMeasuredDimension(size, size);
}
}
@@ -0,0 +1,125 @@
package ru.vtosters.lite.ui.views.rarepebble;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import ru.vtosters.lite.utils.AndroidUtils;
public class SwatchView extends SquareView implements ColorObserver {
private final Paint borderPaint;
private final Path borderPath;
private final Paint checkerPaint;
private final Path oldFillPath;
private final Path newFillPath;
private final Paint oldFillPaint;
private final Paint newFillPaint;
private final float radialMarginPx;
public SwatchView(Context context) {
super(context);
radialMarginPx = AndroidUtils.dp2px(16);
borderPaint = Resources.makeLinePaint();
checkerPaint = Resources.makeCheckerPaint(context);
oldFillPaint = new Paint();
newFillPaint = new Paint();
borderPath = new Path();
oldFillPath = new Path();
newFillPath = new Path();
}
void setOriginalColor(int color) {
oldFillPaint.setColor(color);
invalidate();
}
void observeColor(ObservableColor observableColor) {
observableColor.addObserver(this);
}
@Override
public void updateColor(ObservableColor observableColor) {
newFillPaint.setColor(observableColor.getColor());
invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// We expect to be stacked behind a square HueSatView and fill the top left corner.
final float inset = borderPaint.getStrokeWidth() / 2;
final float r = Math.min(w, h);
// Trim the corners of the swatch is where it is radialMarginPx thick.
// find how long the outeredges are:
final float margin = radialMarginPx;
final float diagonal = r + 2 * margin;
final float opp = (float)Math.sqrt(diagonal * diagonal - r * r);
final float edgeLen = r - opp;
// Arc angles for drawing CCW
final float outerAngle = (float)Math.toDegrees(Math.atan2(opp, r));
final float startAngle = 270 - outerAngle;
// Sweep angle for each half of the swatch:
final float sweepAngle = outerAngle - 45;
// Sweep angle for the smooth corners:
final float cornerSweepAngle = 90 - outerAngle;
// Outer border:
beginBorder(borderPath, inset, edgeLen, margin, cornerSweepAngle);
mainArc(borderPath, r, margin, startAngle, 2 * sweepAngle);
endBorder(borderPath, inset, edgeLen, margin, cornerSweepAngle);
// Old fill shape:
oldFillPath.reset();
oldFillPath.moveTo(inset, inset);
mainArc(oldFillPath, r, margin, 225, sweepAngle);
endBorder(oldFillPath, inset, edgeLen, margin, cornerSweepAngle);
// New fill shape:
beginBorder(newFillPath, inset, edgeLen, margin, cornerSweepAngle);
mainArc(newFillPath, r, margin, startAngle, sweepAngle);
newFillPath.lineTo(inset, inset);
newFillPath.close();
}
private static void beginBorder(Path path, float inset, float edgeLen, float cornerRadius, float cornerSweepAngle) {
path.reset();
path.moveTo(inset, inset);
cornerArc(path, edgeLen, inset, cornerRadius-inset, 0, cornerSweepAngle);
}
private static void endBorder(Path path, float inset, float edgeLen, float cornerRadius, float cornerSweepAngle) {
cornerArc(path, inset, edgeLen, cornerRadius - inset, 90 - cornerSweepAngle, cornerSweepAngle);
path.lineTo(inset, inset);
path.close();
}
private static void cornerArc(Path path, float cx, float cy, float r, float startAngle, float sweepAngle) {
final RectF ovalRect = new RectF(-r, -r, r, r);
ovalRect.offset(cx, cy);
path.arcTo(ovalRect, startAngle, sweepAngle);
}
private static void mainArc(Path path, float viewSize, float margin, float startAngle, float sweepAngle) {
float r = viewSize + margin;
final RectF ovalRect = new RectF(-r, -r, r, r);
ovalRect.offset(viewSize, viewSize);
path.arcTo(ovalRect, startAngle, sweepAngle);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(borderPath, checkerPaint);
canvas.drawPath(oldFillPath, oldFillPaint);
canvas.drawPath(newFillPath, newFillPaint);
canvas.drawPath(borderPath, borderPaint);
}
}
@@ -0,0 +1,56 @@
package ru.vtosters.lite.ui.views.rarepebble;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
public class ValueView extends SliderViewBase implements ColorObserver {
private ObservableColor observableColor = new ObservableColor(0);
public ValueView(Context context) {
super(context);
}
public void observeColor(ObservableColor observableColor) {
this.observableColor = observableColor;
observableColor.addObserver(this);
}
@Override
public void updateColor(ObservableColor observableColor) {
setPos(this.observableColor.getValue());
updateBitmap();
invalidate();
}
@Override
protected void notifyListener(float currentPos) {
observableColor.updateValue(currentPos, this);
}
@Override
protected int getPointerColor(float currentPos) {
float brightColorLightness = observableColor.getLightness();
float posLightness = currentPos * brightColorLightness;
return posLightness > 0.5f ? 0xff000000 : 0xffffffff;
}
protected Bitmap makeBitmap(int w, int h) {
final boolean isWide = w > h;
final int n = Math.max(w, h);
int[] colors = new int[n];
float[] hsv = new float[]{0, 0, 0};
observableColor.getHsv(hsv);
for (int i = 0; i < n; ++i) {
hsv[2] = isWide ? (float)i / n : 1 - (float)i / n;
colors[i] = Color.HSVToColor(hsv);
}
final int bmpWidth = isWide ? w : 1;
final int bmpHeight = isWide ? 1 : h;
return Bitmap.createBitmap(colors, bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B