/*
* Copyright (C) 2015 Daniel Nilsson
*
* 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.
*
*
*
* Change Log:
*
* 1.1
* - Fixed buggy measure and layout code. You can now make the view any size you want.
* - Optimization of the drawing using a bitmap cache, a lot faster!
* - Support for hardware acceleration for all but the problematic
* part of the view will still be software rendered but much faster!
* See comment in drawSatValPanel() for more info.
* - Support for declaring some variables in xml.
*
* 1.2 - 2015-05-08
* - More bugs in onMeasure() have been fixed, should handle all cases properly now.
* - View automatically saves its state now.
* - Automatic border color depending on current theme.
* - Code cleanup, trackball support removed since they do not exist anymore.
*
* 1.3 - 2015-05-10
* - Fixed hue bar selection did not align with what was shown in the sat/val panel.
* Fixed by replacing the linear gardient used before. Now drawing individual lines
* of different colors. This was expensive so we now use a bitmap cache for the hue
* panel too.
* - Replaced all RectF used in the layout process with Rect since the
* floating point values was causing layout issues (perfect alignment).
*/
package afzkl.development.colorpickerview.view;
import afzkl.development.colorpickerview.R;
import afzkl.development.colorpickerview.drawable.AlphaPatternDrawable;
import android.annotation.SuppressLint;
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.Log;
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.
* @author Daniel Nilsson
*/
public class ColorPickerView extends View{
public interface OnColorChangedListener{
public void onColorChanged(int newColor);
}
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 mHuePanelWidthPx;
/**
* The height in px of the alpha panel
*/
private int mAlphaPanelHeightPx;
/**
* The distance in px between the different
* color panels.
*/
private int mPanelSpacingPx;
/**
* The radius in px of the color palette tracker circle.
*/
private int mCircleTrackerRadiusPx;
/**
* The px which the tracker of the hue or alpha panel
* will extend outside of its bounds.
*/
private int mSliderTrackerOffsetPx;
/**
* Height of slider tracker on hue panel,
* width of slider on alpha panel.
*/
private int mSliderTrackerSizePx;
private Paint mSatValPaint;
private Paint mSatValTrackerPaint;
private Paint mAlphaPaint;
private Paint mAlphaTextPaint;
private Paint mHueAlphaTrackerPaint;
private Paint mBorderPaint;
private Shader mValShader;
private Shader mSatShader;
private Shader mAlphaShader;
/*
* 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 mSatValBackgroundCache;
/* We cache the hue background to since its also very expensive now. */
private BitmapCache mHueBackgroundCache;
/* Current values */
private int mAlpha = 0xff;
private float mHue = 360f;
private float mSat = 0f;
private float mVal = 0f;
private boolean mShowAlphaPanel = false;
private String mAlphaSliderText = null;
private int mSliderTrackerColor = DEFAULT_SLIDER_COLOR;
private int mBorderColor = 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 mDrawingRect;
private Rect mSatValRect;
private Rect mHueRect;
private Rect mAlphaRect;
private Point mStartTouchPoint = null;
private AlphaPatternDrawable mAlphaPattern;
private OnColorChangedListener mListener;
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", mAlpha);
state.putFloat("hue", mHue);
state.putFloat("sat", mSat);
state.putFloat("val", mVal);
state.putBoolean("show_alpha", mShowAlphaPanel);
state.putString("alpha_text", mAlphaSliderText);
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
mAlpha = bundle.getInt("alpha");
mHue = bundle.getFloat("hue");
mSat = bundle.getFloat("sat");
mVal = bundle.getFloat("val");
mShowAlphaPanel = bundle.getBoolean("show_alpha");
mAlphaSliderText = 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);
mShowAlphaPanel = a.getBoolean(R.styleable.ColorPickerView_alphaChannelVisible, false);
mAlphaSliderText = a.getString(R.styleable.ColorPickerView_alphaChannelText);
mSliderTrackerColor = a.getColor(R.styleable.ColorPickerView_sliderColor, 0xFFBDBDBD);
mBorderColor = a.getColor(R.styleable.ColorPickerView_borderColor, 0xFF6E6E6E);
a.recycle();
applyThemeColors(context);
mHuePanelWidthPx = DrawingUtils.dpToPx(getContext(), HUE_PANEL_WDITH_DP);
mAlphaPanelHeightPx = DrawingUtils.dpToPx(getContext(), ALPHA_PANEL_HEIGH_DP);
mPanelSpacingPx = DrawingUtils.dpToPx(getContext(), PANEL_SPACING_DP);
mCircleTrackerRadiusPx = DrawingUtils.dpToPx(getContext(), CIRCLE_TRACKER_RADIUS_DP);
mSliderTrackerSizePx = DrawingUtils.dpToPx(getContext(), SLIDER_TRACKER_SIZE_DP);
mSliderTrackerOffsetPx = DrawingUtils.dpToPx(getContext(), SLIDER_TRACKER_OFFSET_DP);
mRequiredPadding = getResources().getDimensionPixelSize(R.dimen.color_picker_view_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(mBorderColor == DEFAULT_BORDER_COLOR) {
mBorderColor = a.getColor(0, DEFAULT_BORDER_COLOR);
}
if(mSliderTrackerColor == DEFAULT_SLIDER_COLOR) {
mSliderTrackerColor = a.getColor(0, DEFAULT_SLIDER_COLOR);
}
a.recycle();
}
private void initPaintTools(){
mSatValPaint = new Paint();
mSatValTrackerPaint = new Paint();
mHueAlphaTrackerPaint = new Paint();
mAlphaPaint = new Paint();
mAlphaTextPaint = new Paint();
mBorderPaint = new Paint();
mSatValTrackerPaint.setStyle(Style.STROKE);
mSatValTrackerPaint.setStrokeWidth(DrawingUtils.dpToPx(getContext(), 2));
mSatValTrackerPaint.setAntiAlias(true);
mHueAlphaTrackerPaint.setColor(mSliderTrackerColor);
mHueAlphaTrackerPaint.setStyle(Style.STROKE);
mHueAlphaTrackerPaint.setStrokeWidth(DrawingUtils.dpToPx(getContext(), 2));
mHueAlphaTrackerPaint.setAntiAlias(true);
mAlphaTextPaint.setColor(0xff1c1c1c);
mAlphaTextPaint.setTextSize(DrawingUtils.dpToPx(getContext(), 14));
mAlphaTextPaint.setAntiAlias(true);
mAlphaTextPaint.setTextAlign(Align.CENTER);
mAlphaTextPaint.setFakeBoldText(true);
}
@Override
protected void onDraw(Canvas canvas) {
if(mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) {
return;
}
drawSatValPanel(canvas);
drawHuePanel(canvas);
drawAlphaPanel(canvas);
}
private void drawSatValPanel(Canvas canvas){
final Rect rect = mSatValRect;
if(BORDER_WIDTH_PX > 0){
mBorderPaint.setColor(mBorderColor);
canvas.drawRect(mDrawingRect.left, mDrawingRect.top,
rect.right + BORDER_WIDTH_PX,
rect.bottom + BORDER_WIDTH_PX, mBorderPaint);
}
if(mValShader == null) {
//Black gradient has either not been created or the view has been resized.
mValShader = 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(mSatValBackgroundCache == null || mSatValBackgroundCache.value != mHue) {
if(mSatValBackgroundCache == null) {
mSatValBackgroundCache = new BitmapCache();
}
//We create our bitmap in the cache if it doesn't exist.
if(mSatValBackgroundCache.bitmap == null) {
mSatValBackgroundCache.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(mSatValBackgroundCache.canvas == null) {
mSatValBackgroundCache.canvas = new Canvas(mSatValBackgroundCache.bitmap);
}
int rgb = Color.HSVToColor(new float[]{mHue,1f,1f});
mSatShader = new LinearGradient(
rect.left, rect.top, rect.right, rect.top,
0xffffffff, rgb, TileMode.CLAMP);
ComposeShader mShader = new ComposeShader(
mValShader, mSatShader, PorterDuff.Mode.MULTIPLY);
mSatValPaint.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.
mSatValBackgroundCache.canvas.drawRect(0, 0,
mSatValBackgroundCache.bitmap.getWidth(),
mSatValBackgroundCache.bitmap.getHeight(),
mSatValPaint);
//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.
mSatValBackgroundCache.value = mHue;
}
// 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(mSatValBackgroundCache.bitmap, null, rect, null);
Point p = satValToPoint(mSat, mVal);
mSatValTrackerPaint.setColor(0xff000000);
canvas.drawCircle(p.x, p.y,
mCircleTrackerRadiusPx - DrawingUtils.dpToPx(getContext(), 1),
mSatValTrackerPaint);
mSatValTrackerPaint.setColor(0xffdddddd);
canvas.drawCircle(p.x, p.y,
mCircleTrackerRadiusPx, mSatValTrackerPaint);
}
private void drawHuePanel(Canvas canvas){
final Rect rect = mHueRect;
if(BORDER_WIDTH_PX > 0) {
mBorderPaint.setColor(mBorderColor);
canvas.drawRect(rect.left - BORDER_WIDTH_PX,
rect.top - BORDER_WIDTH_PX,
rect.right + BORDER_WIDTH_PX,
rect.bottom + BORDER_WIDTH_PX,
mBorderPaint);
}
if(mHueBackgroundCache == null) {
mHueBackgroundCache = new BitmapCache();
mHueBackgroundCache.bitmap =
Bitmap.createBitmap(rect.width(), rect.height(), Config.ARGB_8888);
mHueBackgroundCache.canvas = new Canvas(mHueBackgroundCache.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]);
mHueBackgroundCache.canvas.drawLine(0, i, mHueBackgroundCache.bitmap.getWidth(), i, linePaint);
}
}
canvas.drawBitmap(mHueBackgroundCache.bitmap, null, rect, null);
Point p = hueToPoint(mHue);
RectF r = new RectF();
r.left = rect.left - mSliderTrackerOffsetPx;
r.right = rect.right + mSliderTrackerOffsetPx;
r.top = p.y - (mSliderTrackerSizePx / 2);
r.bottom = p.y + (mSliderTrackerSizePx / 2);
canvas.drawRoundRect(r, 2, 2, mHueAlphaTrackerPaint);
}
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(!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) return;
final Rect rect = mAlphaRect;
if(BORDER_WIDTH_PX > 0){
mBorderPaint.setColor(mBorderColor);
canvas.drawRect(rect.left - BORDER_WIDTH_PX,
rect.top - BORDER_WIDTH_PX,
rect.right + BORDER_WIDTH_PX,
rect.bottom + BORDER_WIDTH_PX,
mBorderPaint);
}
mAlphaPattern.draw(canvas);
float[] hsv = new float[]{mHue,mSat,mVal};
int color = Color.HSVToColor(hsv);
int acolor = Color.HSVToColor(0, hsv);
mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
color, acolor, TileMode.CLAMP);
mAlphaPaint.setShader(mAlphaShader);
canvas.drawRect(rect, mAlphaPaint);
if(mAlphaSliderText != null && !mAlphaSliderText.equals("")){
canvas.drawText(mAlphaSliderText, rect.centerX(),
rect.centerY() + DrawingUtils.dpToPx(getContext(), 4),
mAlphaTextPaint);
}
Point p = alphaToPoint(mAlpha);
RectF r = new RectF();
r.left = p.x - (mSliderTrackerSizePx / 2);
r.right = p.x + (mSliderTrackerSizePx / 2);
r.top = rect.top - mSliderTrackerOffsetPx;
r.bottom = rect.bottom + mSliderTrackerOffsetPx;
canvas.drawRoundRect(r, 2, 2, mHueAlphaTrackerPaint);
}
private Point hueToPoint(float hue){
final Rect rect = mHueRect;
final float height = rect.height();
Point p = new Point();
p.y = (int) (height - (hue * height / 360f) + rect.top);
p.x = (int) rect.left;
return p;
}
private Point satValToPoint(float sat, float val){
final Rect rect = mSatValRect;
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 = mAlphaRect;
final float width = rect.width();
Point p = new Point();
p.x = (int) (width - (alpha * width / 0xff) + rect.left);
p.y = (int) rect.top;
return p;
}
private float[] pointToSatVal(float x, float y){
final Rect 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;
}
private float pointToHue(float y){
final Rect rect = mHueRect;
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);
Log.d("color-picker-view", "Hue: " + hue);
return hue;
}
private int pointToAlpha(int x){
final Rect rect = mAlphaRect;
final int width = (int) rect.width();
if(x < rect.left){
x = 0;
}
else if(x > rect.right){
x = width;
}
else{
x = x - (int)rect.left;
}
return 0xff - (x * 0xff / width);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean update = false;
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
mStartTouchPoint = new Point((int)event.getX(), (int)event.getY());
update = moveTrackersIfNeeded(event);
break;
case MotionEvent.ACTION_MOVE:
update = moveTrackersIfNeeded(event);
break;
case MotionEvent.ACTION_UP:
mStartTouchPoint = null;
update = moveTrackersIfNeeded(event);
break;
}
if(update){
if(mListener != null){
mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}));
}
invalidate();
return true;
}
return super.onTouchEvent(event);
}
private boolean moveTrackersIfNeeded(MotionEvent event){
if(mStartTouchPoint == null) {
return false;
}
boolean update = false;
int startX = mStartTouchPoint.x;
int startY = mStartTouchPoint.y;
if(mHueRect.contains(startX, startY)){
mHue = pointToHue(event.getY());
update = true;
}
else if(mSatValRect.contains(startX, startY)){
float[] result = pointToSatVal(event.getX(), event.getY());
mSat = result[0];
mVal = result[1];
update = true;
}
else if(mAlphaRect != null && mAlphaRect.contains(startX, startY)){
mAlpha = pointToAlpha((int)event.getX());
update = true;
}
return update;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int finalWidth = 0;
int finalHeight = 0;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthAllowed = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
int heightAllowed = MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop();
//Log.d("color-picker-view", "widthMode: " + modeToString(widthMode) + " heightMode: " + modeToString(heightMode) + " widthAllowed: " + widthAllowed + " heightAllowed: " + heightAllowed);
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 = (int) (widthAllowed - mPanelSpacingPx - mHuePanelWidthPx);
if(mShowAlphaPanel) {
h += mPanelSpacingPx + mAlphaPanelHeightPx;
}
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 = (int) (heightAllowed + mPanelSpacingPx + mHuePanelWidthPx);
if(mShowAlphaPanel) {
w -= (mPanelSpacingPx + mAlphaPanelHeightPx);
}
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 = (int) (heightAllowed + mPanelSpacingPx + mHuePanelWidthPx);
//Calculate the needed height to layout using max allowed width.
int heightNeeded = (int) (widthAllowed - mPanelSpacingPx - mHuePanelWidthPx);
if(mShowAlphaPanel) {
widthNeeded -= (mPanelSpacingPx + mAlphaPanelHeightPx);
heightNeeded += mPanelSpacingPx + mAlphaPanelHeightPx;
}
boolean widthOk = false;
boolean heightOk = false;
if(widthNeeded <= widthAllowed) {
widthOk = true;
}
if(heightNeeded <= heightAllowed) {
heightOk = true;
}
//Log.d("color-picker-view", "Size - Allowed w: " + widthAllowed + " h: " + heightAllowed + " Needed w:" + widthNeeded + " h: " + heightNeeded);
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;
}
}
//Log.d("color-picker-view", "Final Size: " + finalWidth + "x" + finalHeight);
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 (int) (width + mHuePanelWidthPx + mPanelSpacingPx);
}
private int getPreferredHeight(){
int height = DrawingUtils.dpToPx(getContext(), 200);
if(mShowAlphaPanel){
height += mPanelSpacingPx + mAlphaPanelHeightPx;
}
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);
mDrawingRect = new Rect();
mDrawingRect.left = getPaddingLeft();
mDrawingRect.right = w - getPaddingRight();
mDrawingRect.top = getPaddingTop();
mDrawingRect.bottom = h - getPaddingBottom();
//The need to be recreated because they depend on the size of the view.
mValShader = null;
mSatShader = null;
mAlphaShader = null;;
// Clear those bitmap caches since the size may have changed.
mSatValBackgroundCache = null;
mHueBackgroundCache = null;
//Log.d("color-picker-view", "Size: " + w + "x" + h);
setUpSatValRect();
setUpHueRect();
setUpAlphaRect();
}
private void setUpSatValRect(){
//Calculate the size for the big color rectangle.
final Rect dRect = mDrawingRect;
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 - mPanelSpacingPx - mHuePanelWidthPx;
if(mShowAlphaPanel) {
bottom -= (mAlphaPanelHeightPx + mPanelSpacingPx);
}
mSatValRect = new Rect(left,top, right, bottom);
}
private void setUpHueRect(){
//Calculate the size for the hue slider on the left.
final Rect dRect = mDrawingRect;
int left = dRect.right - mHuePanelWidthPx + BORDER_WIDTH_PX;
int top = dRect.top + BORDER_WIDTH_PX;
int bottom = dRect.bottom - BORDER_WIDTH_PX - (mShowAlphaPanel ? (mPanelSpacingPx + mAlphaPanelHeightPx) : 0);
int right = dRect.right - BORDER_WIDTH_PX;
mHueRect = new Rect(left, top, right, bottom);
}
private void setUpAlphaRect(){
if(!mShowAlphaPanel) return;
final Rect dRect = mDrawingRect;
int left = dRect.left + BORDER_WIDTH_PX;
int top = dRect.bottom - mAlphaPanelHeightPx + BORDER_WIDTH_PX;
int bottom = dRect.bottom - BORDER_WIDTH_PX;
int right = dRect.right - BORDER_WIDTH_PX;
mAlphaRect = new Rect(left, top, right, bottom);
mAlphaPattern = new AlphaPatternDrawable(DrawingUtils.dpToPx(getContext(), 5));
mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math
.round(mAlphaRect.top), Math.round(mAlphaRect.right), Math
.round(mAlphaRect.bottom));
}
/**
* Set a OnColorChangedListener to get notified when the color
* selected by the user has changed.
* @param listener
*/
public void setOnColorChangedListener(OnColorChangedListener listener){
mListener = listener;
}
/**
* Get the current color this view is showing.
* @return the current color.
*/
public int getColor(){
return Color.HSVToColor(mAlpha, new float[]{mHue,mSat,mVal});
}
/**
* 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);
mAlpha = alpha;
mHue = hsv[0];
mSat = hsv[1];
mVal = hsv[2];
if(callback && mListener != null){
mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}));
}
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
*/
public void setAlphaSliderVisible(boolean visible){
if(mShowAlphaPanel != visible){
mShowAlphaPanel = visible;
/*
* Force recreation.
*/
mValShader = null;
mSatShader = null;
mAlphaShader = null;
mHueBackgroundCache = null;
mSatValBackgroundCache = null;
requestLayout();
}
}
/**
* Set the color of the tracker slider on the hue and alpha panel.
* @param color
*/
public void setSliderTrackerColor(int color){
mSliderTrackerColor = color;
mHueAlphaTrackerPaint.setColor(mSliderTrackerColor);
invalidate();
}
/**
* Get color of the tracker slider on the hue and alpha panel.
* @return
*/
public int getSliderTrackerColor(){
return mSliderTrackerColor;
}
/**
* Set the color of the border surrounding all panels.
* @param color
*/
public void setBorderColor(int color){
mBorderColor = color;
invalidate();
}
/**
* Get the color of the border surrounding all panels.
*/
public int getBorderColor(){
return mBorderColor;
}
/**
* 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){
mAlphaSliderText = text;
invalidate();
}
/**
* Get the current value of the text
* that will be shown in the alpha
* slider.
* @return
*/
public String getAlphaSliderText(){
return mAlphaSliderText;
}
private class BitmapCache {
public Canvas canvas;
public Bitmap bitmap;
public float value;
}
}