1002 lines
28 KiB
Java
1002 lines
28 KiB
Java
/*
|
|
* Copyright (C) 2017 JRummy Apps Inc.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
package com.jrummyapps.android.colorpicker;
|
|
|
|
import android.content.Context;
|
|
import android.content.res.TypedArray;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Bitmap.Config;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.ComposeShader;
|
|
import android.graphics.LinearGradient;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Paint.Align;
|
|
import android.graphics.Paint.Style;
|
|
import android.graphics.Point;
|
|
import android.graphics.PorterDuff;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RectF;
|
|
import android.graphics.Shader;
|
|
import android.graphics.Shader.TileMode;
|
|
import android.os.Bundle;
|
|
import android.os.Parcelable;
|
|
import android.util.AttributeSet;
|
|
import android.util.TypedValue;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
|
|
/**
|
|
* Displays a color picker to the user and allow them to select a color. A slider for the alpha channel is also available.
|
|
* Enable it by setting setAlphaSliderVisible(boolean) to true.
|
|
*/
|
|
public class ColorPickerView extends View {
|
|
|
|
private final static int DEFAULT_BORDER_COLOR = 0xFF6E6E6E;
|
|
private final static int DEFAULT_SLIDER_COLOR = 0xFFBDBDBD;
|
|
|
|
private final static int HUE_PANEL_WDITH_DP = 30;
|
|
private final static int ALPHA_PANEL_HEIGH_DP = 20;
|
|
private final static int PANEL_SPACING_DP = 10;
|
|
private final static int CIRCLE_TRACKER_RADIUS_DP = 5;
|
|
private final static int SLIDER_TRACKER_SIZE_DP = 4;
|
|
private final static int SLIDER_TRACKER_OFFSET_DP = 2;
|
|
|
|
/**
|
|
* The width in pixels of the border
|
|
* surrounding all color panels.
|
|
*/
|
|
private final static int BORDER_WIDTH_PX = 1;
|
|
|
|
/**
|
|
* The width in px of the hue panel.
|
|
*/
|
|
private int huePanelWidthPx;
|
|
/**
|
|
* The height in px of the alpha panel
|
|
*/
|
|
private int alphaPanelHeightPx;
|
|
/**
|
|
* The distance in px between the different
|
|
* color panels.
|
|
*/
|
|
private int panelSpacingPx;
|
|
/**
|
|
* The radius in px of the color palette tracker circle.
|
|
*/
|
|
private int circleTrackerRadiusPx;
|
|
/**
|
|
* The px which the tracker of the hue or alpha panel
|
|
* will extend outside of its bounds.
|
|
*/
|
|
private int sliderTrackerOffsetPx;
|
|
/**
|
|
* Height of slider tracker on hue panel,
|
|
* width of slider on alpha panel.
|
|
*/
|
|
private int sliderTrackerSizePx;
|
|
|
|
private Paint satValPaint;
|
|
private Paint satValTrackerPaint;
|
|
|
|
private Paint alphaPaint;
|
|
private Paint alphaTextPaint;
|
|
private Paint hueAlphaTrackerPaint;
|
|
|
|
private Paint borderPaint;
|
|
|
|
private Shader valShader;
|
|
private Shader satShader;
|
|
private Shader alphaShader;
|
|
|
|
/*
|
|
* We cache a bitmap of the sat/val panel which is expensive to draw each time.
|
|
* We can reuse it when the user is sliding the circle picker as long as the hue isn't changed.
|
|
*/
|
|
private BitmapCache satValBackgroundCache;
|
|
/* We cache the hue background to since its also very expensive now. */
|
|
private BitmapCache hueBackgroundCache;
|
|
|
|
/* Current values */
|
|
private int alpha = 0xff;
|
|
private float hue = 360f;
|
|
private float sat = 0f;
|
|
private float val = 0f;
|
|
|
|
private boolean showAlphaPanel = false;
|
|
private String alphaSliderText = null;
|
|
private int sliderTrackerColor = DEFAULT_SLIDER_COLOR;
|
|
private int borderColor = DEFAULT_BORDER_COLOR;
|
|
|
|
/**
|
|
* Minimum required padding. The offset from the
|
|
* edge we must have or else the finger tracker will
|
|
* get clipped when it's drawn outside of the view.
|
|
*/
|
|
private int mRequiredPadding;
|
|
|
|
/**
|
|
* The Rect in which we are allowed to draw.
|
|
* Trackers can extend outside slightly,
|
|
* due to the required padding we have set.
|
|
*/
|
|
private Rect drawingRect;
|
|
|
|
private Rect satValRect;
|
|
private Rect hueRect;
|
|
private Rect alphaRect;
|
|
|
|
private Point startTouchPoint = null;
|
|
|
|
private AlphaPatternDrawable alphaPatternDrawable;
|
|
private OnColorChangedListener onColorChangedListener;
|
|
|
|
public ColorPickerView(Context context) {
|
|
this(context, null);
|
|
}
|
|
|
|
public ColorPickerView(Context context, AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
|
|
super(context, attrs, defStyle);
|
|
init(context, attrs);
|
|
}
|
|
|
|
@Override public Parcelable onSaveInstanceState() {
|
|
Bundle state = new Bundle();
|
|
state.putParcelable("instanceState", super.onSaveInstanceState());
|
|
state.putInt("alpha", alpha);
|
|
state.putFloat("hue", hue);
|
|
state.putFloat("sat", sat);
|
|
state.putFloat("val", val);
|
|
state.putBoolean("show_alpha", showAlphaPanel);
|
|
state.putString("alpha_text", alphaSliderText);
|
|
|
|
return state;
|
|
}
|
|
|
|
@Override public void onRestoreInstanceState(Parcelable state) {
|
|
|
|
if (state instanceof Bundle) {
|
|
Bundle bundle = (Bundle) state;
|
|
|
|
alpha = bundle.getInt("alpha");
|
|
hue = bundle.getFloat("hue");
|
|
sat = bundle.getFloat("sat");
|
|
val = bundle.getFloat("val");
|
|
showAlphaPanel = bundle.getBoolean("show_alpha");
|
|
alphaSliderText = bundle.getString("alpha_text");
|
|
|
|
state = bundle.getParcelable("instanceState");
|
|
}
|
|
super.onRestoreInstanceState(state);
|
|
}
|
|
|
|
private void init(Context context, AttributeSet attrs) {
|
|
//Load those if set in xml resource file.
|
|
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPickerView);
|
|
showAlphaPanel = a.getBoolean(R.styleable.ColorPickerView_cpv_alphaChannelVisible, false);
|
|
alphaSliderText = a.getString(R.styleable.ColorPickerView_cpv_alphaChannelText);
|
|
sliderTrackerColor = a.getColor(R.styleable.ColorPickerView_cpv_sliderColor, 0xFFBDBDBD);
|
|
borderColor = a.getColor(R.styleable.ColorPickerView_cpv_borderColor, 0xFF6E6E6E);
|
|
a.recycle();
|
|
|
|
applyThemeColors(context);
|
|
|
|
huePanelWidthPx = DrawingUtils.dpToPx(getContext(), HUE_PANEL_WDITH_DP);
|
|
alphaPanelHeightPx = DrawingUtils.dpToPx(getContext(), ALPHA_PANEL_HEIGH_DP);
|
|
panelSpacingPx = DrawingUtils.dpToPx(getContext(), PANEL_SPACING_DP);
|
|
circleTrackerRadiusPx = DrawingUtils.dpToPx(getContext(), CIRCLE_TRACKER_RADIUS_DP);
|
|
sliderTrackerSizePx = DrawingUtils.dpToPx(getContext(), SLIDER_TRACKER_SIZE_DP);
|
|
sliderTrackerOffsetPx = DrawingUtils.dpToPx(getContext(), SLIDER_TRACKER_OFFSET_DP);
|
|
|
|
mRequiredPadding = getResources().getDimensionPixelSize(R.dimen.cpv_required_padding);
|
|
|
|
initPaintTools();
|
|
|
|
//Needed for receiving trackball motion events.
|
|
setFocusable(true);
|
|
setFocusableInTouchMode(true);
|
|
}
|
|
|
|
private void applyThemeColors(Context c) {
|
|
// If no specific border/slider color has been
|
|
// set we take the default secondary text color
|
|
// as border/slider color. Thus it will adopt
|
|
// to theme changes automatically.
|
|
|
|
final TypedValue value = new TypedValue();
|
|
TypedArray a = c.obtainStyledAttributes(value.data, new int[]{android.R.attr.textColorSecondary});
|
|
|
|
if (borderColor == DEFAULT_BORDER_COLOR) {
|
|
borderColor = a.getColor(0, DEFAULT_BORDER_COLOR);
|
|
}
|
|
|
|
if (sliderTrackerColor == DEFAULT_SLIDER_COLOR) {
|
|
sliderTrackerColor = a.getColor(0, DEFAULT_SLIDER_COLOR);
|
|
}
|
|
|
|
a.recycle();
|
|
}
|
|
|
|
private void initPaintTools() {
|
|
|
|
satValPaint = new Paint();
|
|
satValTrackerPaint = new Paint();
|
|
hueAlphaTrackerPaint = new Paint();
|
|
alphaPaint = new Paint();
|
|
alphaTextPaint = new Paint();
|
|
borderPaint = new Paint();
|
|
|
|
satValTrackerPaint.setStyle(Style.STROKE);
|
|
satValTrackerPaint.setStrokeWidth(DrawingUtils.dpToPx(getContext(), 2));
|
|
satValTrackerPaint.setAntiAlias(true);
|
|
|
|
hueAlphaTrackerPaint.setColor(sliderTrackerColor);
|
|
hueAlphaTrackerPaint.setStyle(Style.STROKE);
|
|
hueAlphaTrackerPaint.setStrokeWidth(DrawingUtils.dpToPx(getContext(), 2));
|
|
hueAlphaTrackerPaint.setAntiAlias(true);
|
|
|
|
alphaTextPaint.setColor(0xff1c1c1c);
|
|
alphaTextPaint.setTextSize(DrawingUtils.dpToPx(getContext(), 14));
|
|
alphaTextPaint.setAntiAlias(true);
|
|
alphaTextPaint.setTextAlign(Align.CENTER);
|
|
alphaTextPaint.setFakeBoldText(true);
|
|
|
|
}
|
|
|
|
@Override protected void onDraw(Canvas canvas) {
|
|
if (drawingRect.width() <= 0 || drawingRect.height() <= 0) {
|
|
return;
|
|
}
|
|
|
|
drawSatValPanel(canvas);
|
|
drawHuePanel(canvas);
|
|
drawAlphaPanel(canvas);
|
|
}
|
|
|
|
private void drawSatValPanel(Canvas canvas) {
|
|
final Rect rect = satValRect;
|
|
|
|
if (BORDER_WIDTH_PX > 0) {
|
|
borderPaint.setColor(borderColor);
|
|
canvas.drawRect(drawingRect.left, drawingRect.top,
|
|
rect.right + BORDER_WIDTH_PX,
|
|
rect.bottom + BORDER_WIDTH_PX, borderPaint);
|
|
}
|
|
|
|
if (valShader == null) {
|
|
//Black gradient has either not been created or the view has been resized.
|
|
valShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, 0xffffffff, 0xff000000, TileMode.CLAMP);
|
|
}
|
|
|
|
//If the hue has changed we need to recreate the cache.
|
|
if (satValBackgroundCache == null || satValBackgroundCache.value != hue) {
|
|
|
|
if (satValBackgroundCache == null) {
|
|
satValBackgroundCache = new BitmapCache();
|
|
}
|
|
|
|
//We create our bitmap in the cache if it doesn't exist.
|
|
if (satValBackgroundCache.bitmap == null) {
|
|
satValBackgroundCache.bitmap = Bitmap
|
|
.createBitmap(rect.width(), rect.height(), Config.ARGB_8888);
|
|
}
|
|
|
|
//We create the canvas once so we can draw on our bitmap and the hold on to it.
|
|
if (satValBackgroundCache.canvas == null) {
|
|
satValBackgroundCache.canvas = new Canvas(satValBackgroundCache.bitmap);
|
|
}
|
|
|
|
int rgb = Color.HSVToColor(new float[]{hue, 1f, 1f});
|
|
|
|
satShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, 0xffffffff, rgb, TileMode.CLAMP);
|
|
|
|
ComposeShader mShader = new ComposeShader(
|
|
valShader, satShader, PorterDuff.Mode.MULTIPLY);
|
|
satValPaint.setShader(mShader);
|
|
|
|
// Finally we draw on our canvas, the result will be
|
|
// stored in our bitmap which is already in the cache.
|
|
// Since this is drawn on a canvas not rendered on
|
|
// screen it will automatically not be using the
|
|
// hardware acceleration. And this was the code that
|
|
// wasn't supported by hardware acceleration which mean
|
|
// there is no need to turn it of anymore. The rest of
|
|
// the view will still be hw accelerated.
|
|
satValBackgroundCache.canvas.drawRect(0, 0,
|
|
satValBackgroundCache.bitmap.getWidth(),
|
|
satValBackgroundCache.bitmap.getHeight(),
|
|
satValPaint);
|
|
|
|
//We set the hue value in our cache to which hue it was drawn with,
|
|
//then we know that if it hasn't changed we can reuse our cached bitmap.
|
|
satValBackgroundCache.value = hue;
|
|
|
|
}
|
|
|
|
// We draw our bitmap from the cached, if the hue has changed
|
|
// then it was just recreated otherwise the old one will be used.
|
|
canvas.drawBitmap(satValBackgroundCache.bitmap, null, rect, null);
|
|
|
|
Point p = satValToPoint(sat, val);
|
|
|
|
satValTrackerPaint.setColor(0xff000000);
|
|
canvas.drawCircle(p.x, p.y, circleTrackerRadiusPx - DrawingUtils.dpToPx(getContext(), 1), satValTrackerPaint);
|
|
|
|
satValTrackerPaint.setColor(0xffdddddd);
|
|
canvas.drawCircle(p.x, p.y, circleTrackerRadiusPx, satValTrackerPaint);
|
|
|
|
}
|
|
|
|
private void drawHuePanel(Canvas canvas) {
|
|
final Rect rect = hueRect;
|
|
|
|
if (BORDER_WIDTH_PX > 0) {
|
|
borderPaint.setColor(borderColor);
|
|
|
|
canvas.drawRect(rect.left - BORDER_WIDTH_PX,
|
|
rect.top - BORDER_WIDTH_PX,
|
|
rect.right + BORDER_WIDTH_PX,
|
|
rect.bottom + BORDER_WIDTH_PX,
|
|
borderPaint);
|
|
}
|
|
|
|
if (hueBackgroundCache == null) {
|
|
hueBackgroundCache = new BitmapCache();
|
|
hueBackgroundCache.bitmap = Bitmap.createBitmap(rect.width(), rect.height(), Config.ARGB_8888);
|
|
hueBackgroundCache.canvas = new Canvas(hueBackgroundCache.bitmap);
|
|
|
|
int[] hueColors = new int[(int) (rect.height() + 0.5f)];
|
|
|
|
// Generate array of all colors, will be drawn as individual lines.
|
|
float h = 360f;
|
|
for (int i = 0; i < hueColors.length; i++) {
|
|
hueColors[i] = Color.HSVToColor(new float[]{h, 1f, 1f});
|
|
h -= 360f / hueColors.length;
|
|
}
|
|
|
|
// Time to draw the hue color gradient,
|
|
// its drawn as individual lines which
|
|
// will be quite many when the resolution is high
|
|
// and/or the panel is large.
|
|
Paint linePaint = new Paint();
|
|
linePaint.setStrokeWidth(0);
|
|
for (int i = 0; i < hueColors.length; i++) {
|
|
linePaint.setColor(hueColors[i]);
|
|
hueBackgroundCache.canvas.drawLine(0, i, hueBackgroundCache.bitmap.getWidth(), i, linePaint);
|
|
}
|
|
}
|
|
|
|
canvas.drawBitmap(hueBackgroundCache.bitmap, null, rect, null);
|
|
|
|
Point p = hueToPoint(hue);
|
|
|
|
RectF r = new RectF();
|
|
r.left = rect.left - sliderTrackerOffsetPx;
|
|
r.right = rect.right + sliderTrackerOffsetPx;
|
|
r.top = p.y - (sliderTrackerSizePx / 2);
|
|
r.bottom = p.y + (sliderTrackerSizePx / 2);
|
|
|
|
canvas.drawRoundRect(r, 2, 2, hueAlphaTrackerPaint);
|
|
}
|
|
|
|
private void drawAlphaPanel(Canvas canvas) {
|
|
/*
|
|
* Will be drawn with hw acceleration, very fast.
|
|
* Also the AlphaPatternDrawable is backed by a bitmap
|
|
* generated only once if the size does not change.
|
|
*/
|
|
|
|
if (!showAlphaPanel || alphaRect == null || alphaPatternDrawable == null) return;
|
|
|
|
final Rect rect = alphaRect;
|
|
|
|
if (BORDER_WIDTH_PX > 0) {
|
|
borderPaint.setColor(borderColor);
|
|
canvas.drawRect(rect.left - BORDER_WIDTH_PX,
|
|
rect.top - BORDER_WIDTH_PX,
|
|
rect.right + BORDER_WIDTH_PX,
|
|
rect.bottom + BORDER_WIDTH_PX,
|
|
borderPaint);
|
|
}
|
|
|
|
alphaPatternDrawable.draw(canvas);
|
|
|
|
float[] hsv = new float[]{hue, sat, val};
|
|
int color = Color.HSVToColor(hsv);
|
|
int acolor = Color.HSVToColor(0, hsv);
|
|
|
|
alphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
|
|
color, acolor, TileMode.CLAMP);
|
|
|
|
alphaPaint.setShader(alphaShader);
|
|
|
|
canvas.drawRect(rect, alphaPaint);
|
|
|
|
if (alphaSliderText != null && !alphaSliderText.equals("")) {
|
|
canvas.drawText(alphaSliderText, rect.centerX(), rect.centerY() + DrawingUtils.dpToPx(getContext(), 4), alphaTextPaint);
|
|
}
|
|
|
|
Point p = alphaToPoint(alpha);
|
|
|
|
RectF r = new RectF();
|
|
r.left = p.x - (sliderTrackerSizePx / 2);
|
|
r.right = p.x + (sliderTrackerSizePx / 2);
|
|
r.top = rect.top - sliderTrackerOffsetPx;
|
|
r.bottom = rect.bottom + sliderTrackerOffsetPx;
|
|
|
|
canvas.drawRoundRect(r, 2, 2, hueAlphaTrackerPaint);
|
|
}
|
|
|
|
private Point hueToPoint(float hue) {
|
|
|
|
final Rect rect = hueRect;
|
|
final float height = rect.height();
|
|
|
|
Point p = new Point();
|
|
|
|
p.y = (int) (height - (hue * height / 360f) + rect.top);
|
|
p.x = rect.left;
|
|
|
|
return p;
|
|
}
|
|
|
|
private Point satValToPoint(float sat, float val) {
|
|
|
|
final Rect rect = satValRect;
|
|
final float height = rect.height();
|
|
final float width = rect.width();
|
|
|
|
Point p = new Point();
|
|
|
|
p.x = (int) (sat * width + rect.left);
|
|
p.y = (int) ((1f - val) * height + rect.top);
|
|
|
|
return p;
|
|
}
|
|
|
|
private Point alphaToPoint(int alpha) {
|
|
|
|
final Rect rect = alphaRect;
|
|
final float width = rect.width();
|
|
|
|
Point p = new Point();
|
|
|
|
p.x = (int) (width - (alpha * width / 0xff) + rect.left);
|
|
p.y = rect.top;
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
private float[] pointToSatVal(float x, float y) {
|
|
|
|
final Rect rect = satValRect;
|
|
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;
|
|
}
|
|
|
|
private float pointToHue(float y) {
|
|
|
|
final Rect rect = hueRect;
|
|
|
|
float height = rect.height();
|
|
|
|
if (y < rect.top) {
|
|
y = 0f;
|
|
} else if (y > rect.bottom) {
|
|
y = height;
|
|
} else {
|
|
y = y - rect.top;
|
|
}
|
|
|
|
float hue = 360f - (y * 360f / height);
|
|
|
|
return hue;
|
|
}
|
|
|
|
private int pointToAlpha(int x) {
|
|
|
|
final Rect rect = alphaRect;
|
|
final int width = rect.width();
|
|
|
|
if (x < rect.left) {
|
|
x = 0;
|
|
} else if (x > rect.right) {
|
|
x = width;
|
|
} else {
|
|
x = x - rect.left;
|
|
}
|
|
|
|
return 0xff - (x * 0xff / width);
|
|
|
|
}
|
|
|
|
@Override public boolean onTouchEvent(MotionEvent event) {
|
|
|
|
try{
|
|
this.getParent().requestDisallowInterceptTouchEvent( true );
|
|
}catch(Throwable ignored){
|
|
}
|
|
|
|
boolean update = false;
|
|
|
|
switch (event.getAction()) {
|
|
|
|
case MotionEvent.ACTION_DOWN:
|
|
startTouchPoint = new Point((int) event.getX(), (int) event.getY());
|
|
update = moveTrackersIfNeeded(event);
|
|
break;
|
|
case MotionEvent.ACTION_MOVE:
|
|
update = moveTrackersIfNeeded(event);
|
|
break;
|
|
case MotionEvent.ACTION_UP:
|
|
startTouchPoint = null;
|
|
update = moveTrackersIfNeeded(event);
|
|
break;
|
|
}
|
|
|
|
if (update) {
|
|
if (onColorChangedListener != null) {
|
|
onColorChangedListener.onColorChanged(Color.HSVToColor(alpha, new float[]{hue, sat, val}));
|
|
}
|
|
invalidate();
|
|
return true;
|
|
}
|
|
|
|
return super.onTouchEvent(event);
|
|
}
|
|
|
|
private boolean moveTrackersIfNeeded(MotionEvent event) {
|
|
if (startTouchPoint == null) {
|
|
return false;
|
|
}
|
|
|
|
boolean update = false;
|
|
|
|
int startX = startTouchPoint.x;
|
|
int startY = startTouchPoint.y;
|
|
|
|
if (hueRect.contains(startX, startY)) {
|
|
hue = pointToHue(event.getY());
|
|
|
|
update = true;
|
|
} else if (satValRect.contains(startX, startY)) {
|
|
float[] result = pointToSatVal(event.getX(), event.getY());
|
|
|
|
sat = result[0];
|
|
val = result[1];
|
|
|
|
update = true;
|
|
} else if (alphaRect != null && alphaRect.contains(startX, startY)) {
|
|
alpha = pointToAlpha((int) event.getX());
|
|
|
|
update = true;
|
|
}
|
|
|
|
return update;
|
|
}
|
|
|
|
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
int finalWidth;
|
|
int finalHeight;
|
|
|
|
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
|
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
|
|
|
int widthAllowed = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
|
|
int heightAllowed =
|
|
MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop();
|
|
|
|
if (widthMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.EXACTLY) {
|
|
//A exact value has been set in either direction, we need to stay within this size.
|
|
|
|
if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
|
|
//The with has been specified exactly, we need to adopt the height to fit.
|
|
int h = (widthAllowed - panelSpacingPx - huePanelWidthPx);
|
|
|
|
if (showAlphaPanel) {
|
|
h += panelSpacingPx + alphaPanelHeightPx;
|
|
}
|
|
|
|
if (h > heightAllowed) {
|
|
//We can't fit the view in this container, set the size to whatever was allowed.
|
|
finalHeight = heightAllowed;
|
|
} else {
|
|
finalHeight = h;
|
|
}
|
|
|
|
finalWidth = widthAllowed;
|
|
|
|
} else if (heightMode == MeasureSpec.EXACTLY && widthMode != MeasureSpec.EXACTLY) {
|
|
//The height has been specified exactly, we need to stay within this height and adopt the width.
|
|
|
|
int w = (heightAllowed + panelSpacingPx + huePanelWidthPx);
|
|
|
|
if (showAlphaPanel) {
|
|
w -= (panelSpacingPx + alphaPanelHeightPx);
|
|
}
|
|
|
|
if (w > widthAllowed) {
|
|
//we can't fit within this container, set the size to whatever was allowed.
|
|
finalWidth = widthAllowed;
|
|
} else {
|
|
finalWidth = w;
|
|
}
|
|
|
|
finalHeight = heightAllowed;
|
|
|
|
} else {
|
|
//If we get here the dev has set the width and height to exact sizes. For example match_parent or 300dp.
|
|
//This will mean that the sat/val panel will not be square but it doesn't matter. It will work anyway.
|
|
//In all other senarios our goal is to make that panel square.
|
|
|
|
//We set the sizes to exactly what we were told.
|
|
finalWidth = widthAllowed;
|
|
finalHeight = heightAllowed;
|
|
}
|
|
|
|
} else {
|
|
//If no exact size has been set we try to make our view as big as possible
|
|
//within the allowed space.
|
|
|
|
//Calculate the needed width to layout using max allowed height.
|
|
int widthNeeded = (heightAllowed + panelSpacingPx + huePanelWidthPx);
|
|
|
|
//Calculate the needed height to layout using max allowed width.
|
|
int heightNeeded = (widthAllowed - panelSpacingPx - huePanelWidthPx);
|
|
|
|
if (showAlphaPanel) {
|
|
widthNeeded -= (panelSpacingPx + alphaPanelHeightPx);
|
|
heightNeeded += panelSpacingPx + alphaPanelHeightPx;
|
|
}
|
|
|
|
boolean widthOk = false;
|
|
boolean heightOk = false;
|
|
|
|
if (widthNeeded <= widthAllowed) {
|
|
widthOk = true;
|
|
}
|
|
|
|
if (heightNeeded <= heightAllowed) {
|
|
heightOk = true;
|
|
}
|
|
|
|
if (widthOk && heightOk) {
|
|
finalWidth = widthAllowed;
|
|
finalHeight = heightNeeded;
|
|
} else if (!heightOk && widthOk) {
|
|
finalHeight = heightAllowed;
|
|
finalWidth = widthNeeded;
|
|
} else if (!widthOk && heightOk) {
|
|
finalHeight = heightNeeded;
|
|
finalWidth = widthAllowed;
|
|
} else {
|
|
finalHeight = heightAllowed;
|
|
finalWidth = widthAllowed;
|
|
}
|
|
|
|
}
|
|
|
|
setMeasuredDimension(finalWidth + getPaddingLeft() + getPaddingRight(),
|
|
finalHeight + getPaddingTop() + getPaddingBottom());
|
|
}
|
|
|
|
private int getPreferredWidth() {
|
|
//Our preferred width and height is 200dp for the square sat / val rectangle.
|
|
int width = DrawingUtils.dpToPx(getContext(), 200);
|
|
|
|
return (width + huePanelWidthPx + panelSpacingPx);
|
|
}
|
|
|
|
private int getPreferredHeight() {
|
|
int height = DrawingUtils.dpToPx(getContext(), 200);
|
|
|
|
if (showAlphaPanel) {
|
|
height += panelSpacingPx + alphaPanelHeightPx;
|
|
}
|
|
return height;
|
|
}
|
|
|
|
@Override public int getPaddingTop() {
|
|
return Math.max(super.getPaddingTop(), mRequiredPadding);
|
|
}
|
|
|
|
@Override public int getPaddingBottom() {
|
|
return Math.max(super.getPaddingBottom(), mRequiredPadding);
|
|
}
|
|
|
|
@Override public int getPaddingLeft() {
|
|
return Math.max(super.getPaddingLeft(), mRequiredPadding);
|
|
}
|
|
|
|
@Override public int getPaddingRight() {
|
|
return Math.max(super.getPaddingRight(), mRequiredPadding);
|
|
}
|
|
|
|
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
super.onSizeChanged(w, h, oldw, oldh);
|
|
|
|
drawingRect = new Rect();
|
|
drawingRect.left = getPaddingLeft();
|
|
drawingRect.right = w - getPaddingRight();
|
|
drawingRect.top = getPaddingTop();
|
|
drawingRect.bottom = h - getPaddingBottom();
|
|
|
|
//The need to be recreated because they depend on the size of the view.
|
|
valShader = null;
|
|
satShader = null;
|
|
alphaShader = null;
|
|
|
|
// Clear those bitmap caches since the size may have changed.
|
|
satValBackgroundCache = null;
|
|
hueBackgroundCache = null;
|
|
|
|
setUpSatValRect();
|
|
setUpHueRect();
|
|
setUpAlphaRect();
|
|
}
|
|
|
|
private void setUpSatValRect() {
|
|
//Calculate the size for the big color rectangle.
|
|
final Rect dRect = drawingRect;
|
|
|
|
int left = dRect.left + BORDER_WIDTH_PX;
|
|
int top = dRect.top + BORDER_WIDTH_PX;
|
|
int bottom = dRect.bottom - BORDER_WIDTH_PX;
|
|
int right = dRect.right - BORDER_WIDTH_PX - panelSpacingPx - huePanelWidthPx;
|
|
|
|
if (showAlphaPanel) {
|
|
bottom -= (alphaPanelHeightPx + panelSpacingPx);
|
|
}
|
|
|
|
satValRect = new Rect(left, top, right, bottom);
|
|
}
|
|
|
|
private void setUpHueRect() {
|
|
//Calculate the size for the hue slider on the left.
|
|
final Rect dRect = drawingRect;
|
|
|
|
int left = dRect.right - huePanelWidthPx + BORDER_WIDTH_PX;
|
|
int top = dRect.top + BORDER_WIDTH_PX;
|
|
int bottom = dRect.bottom - BORDER_WIDTH_PX -
|
|
(showAlphaPanel ? (panelSpacingPx + alphaPanelHeightPx) : 0);
|
|
int right = dRect.right - BORDER_WIDTH_PX;
|
|
|
|
hueRect = new Rect(left, top, right, bottom);
|
|
}
|
|
|
|
private void setUpAlphaRect() {
|
|
|
|
if (!showAlphaPanel) return;
|
|
|
|
final Rect dRect = drawingRect;
|
|
|
|
int left = dRect.left + BORDER_WIDTH_PX;
|
|
int top = dRect.bottom - alphaPanelHeightPx + BORDER_WIDTH_PX;
|
|
int bottom = dRect.bottom - BORDER_WIDTH_PX;
|
|
int right = dRect.right - BORDER_WIDTH_PX;
|
|
|
|
alphaRect = new Rect(left, top, right, bottom);
|
|
|
|
alphaPatternDrawable = new AlphaPatternDrawable(DrawingUtils.dpToPx(getContext(), 4));
|
|
alphaPatternDrawable.setBounds(Math.round(alphaRect.left), Math
|
|
.round(alphaRect.top), Math.round(alphaRect.right), Math
|
|
.round(alphaRect.bottom));
|
|
}
|
|
|
|
/**
|
|
* Set a OnColorChangedListener to get notified when the color
|
|
* selected by the user has changed.
|
|
*
|
|
* @param listener
|
|
* the listener
|
|
*/
|
|
public void setOnColorChangedListener(OnColorChangedListener listener) {
|
|
onColorChangedListener = listener;
|
|
}
|
|
|
|
/**
|
|
* Get the current color this view is showing.
|
|
*
|
|
* @return the current color.
|
|
*/
|
|
public int getColor() {
|
|
return Color.HSVToColor(alpha, new float[]{hue, sat, val});
|
|
}
|
|
|
|
/**
|
|
* Set the color the view should show.
|
|
*
|
|
* @param color
|
|
* The color that should be selected. #argb
|
|
*/
|
|
public void setColor(int color) {
|
|
setColor(color, false);
|
|
}
|
|
|
|
/**
|
|
* Set the color this view should show.
|
|
*
|
|
* @param color
|
|
* The color that should be selected. #argb
|
|
* @param callback
|
|
* If you want to get a callback to your OnColorChangedListener.
|
|
*/
|
|
public void setColor(int color, boolean callback) {
|
|
|
|
int alpha = Color.alpha(color);
|
|
int red = Color.red(color);
|
|
int blue = Color.blue(color);
|
|
int green = Color.green(color);
|
|
|
|
float[] hsv = new float[3];
|
|
|
|
Color.RGBToHSV(red, green, blue, hsv);
|
|
|
|
this.alpha = alpha;
|
|
hue = hsv[0];
|
|
sat = hsv[1];
|
|
val = hsv[2];
|
|
|
|
if (callback && onColorChangedListener != null) {
|
|
onColorChangedListener
|
|
.onColorChanged(Color.HSVToColor(this.alpha, new float[]{hue, sat, val}));
|
|
}
|
|
|
|
invalidate();
|
|
}
|
|
|
|
/**
|
|
* Set if the user is allowed to adjust the alpha panel. Default is false.
|
|
* If it is set to false no alpha will be set.
|
|
*
|
|
* @param visible
|
|
* {@code true} to show the alpha slider
|
|
*/
|
|
public void setAlphaSliderVisible(boolean visible) {
|
|
if (showAlphaPanel != visible) {
|
|
showAlphaPanel = visible;
|
|
|
|
/*
|
|
* Force recreation.
|
|
*/
|
|
valShader = null;
|
|
satShader = null;
|
|
alphaShader = null;
|
|
hueBackgroundCache = null;
|
|
satValBackgroundCache = null;
|
|
|
|
requestLayout();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Set the color of the tracker slider on the hue and alpha panel.
|
|
*
|
|
* @param color
|
|
* a color value
|
|
*/
|
|
public void setSliderTrackerColor(int color) {
|
|
sliderTrackerColor = color;
|
|
hueAlphaTrackerPaint.setColor(sliderTrackerColor);
|
|
invalidate();
|
|
}
|
|
|
|
/**
|
|
* Get color of the tracker slider on the hue and alpha panel.
|
|
*
|
|
* @return the color value
|
|
*/
|
|
public int getSliderTrackerColor() {
|
|
return sliderTrackerColor;
|
|
}
|
|
|
|
/**
|
|
* Set the color of the border surrounding all panels.
|
|
*
|
|
* @param color
|
|
* a color value
|
|
*/
|
|
public void setBorderColor(int color) {
|
|
borderColor = color;
|
|
invalidate();
|
|
}
|
|
|
|
/**
|
|
* Get the color of the border surrounding all panels.
|
|
*/
|
|
public int getBorderColor() {
|
|
return borderColor;
|
|
}
|
|
|
|
/**
|
|
* Set the text that should be shown in the
|
|
* alpha slider. Set to null to disable text.
|
|
*
|
|
* @param res
|
|
* string resource id.
|
|
*/
|
|
public void setAlphaSliderText(int res) {
|
|
String text = getContext().getString(res);
|
|
setAlphaSliderText(text);
|
|
}
|
|
|
|
/**
|
|
* Set the text that should be shown in the
|
|
* alpha slider. Set to null to disable text.
|
|
*
|
|
* @param text
|
|
* Text that should be shown.
|
|
*/
|
|
public void setAlphaSliderText(String text) {
|
|
alphaSliderText = text;
|
|
invalidate();
|
|
}
|
|
|
|
/**
|
|
* Get the current value of the text
|
|
* that will be shown in the alpha
|
|
* slider.
|
|
*
|
|
* @return the slider text
|
|
*/
|
|
public String getAlphaSliderText() {
|
|
return alphaSliderText;
|
|
}
|
|
|
|
private class BitmapCache {
|
|
|
|
public Canvas canvas;
|
|
public Bitmap bitmap;
|
|
public float value;
|
|
}
|
|
|
|
public interface OnColorChangedListener {
|
|
|
|
void onColorChanged(int newColor);
|
|
}
|
|
|
|
}
|