/*
* Copyright 2012 Lars Werkman
*
* 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.larswerkman.holocolorpicker;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import tv.piratemedia.lightcontroler.R;
public class SaturationBar extends View {
/*
* Constants used to save/restore the instance state.
*/
private static final String STATE_PARENT = "parent";
private static final String STATE_COLOR = "color";
private static final String STATE_SATURATION = "saturation";
private static final String STATE_ORIENTATION = "orientation";
/**
* Constants used to identify orientation.
*/
private static final boolean ORIENTATION_HORIZONTAL = true;
private static final boolean ORIENTATION_VERTICAL = false;
/**
* Default orientation of the bar.
*/
private static final boolean ORIENTATION_DEFAULT = ORIENTATION_HORIZONTAL;
/**
* The thickness of the bar.
*/
private int mBarThickness;
/**
* The length of the bar.
*/
private int mBarLength;
private int mPreferredBarLength;
/**
* The radius of the pointer.
*/
private int mBarPointerRadius;
/**
* The radius of the halo of the pointer.
*/
private int mBarPointerHaloRadius;
/**
* The position of the pointer on the bar.
*/
private int mBarPointerPosition;
/**
* {@code Paint} instance used to draw the bar.
*/
private Paint mBarPaint;
/**
* {@code Paint} instance used to draw the pointer.
*/
private Paint mBarPointerPaint;
/**
* {@code Paint} instance used to draw the halo of the pointer.
*/
private Paint mBarPointerHaloPaint;
/**
* The rectangle enclosing the bar.
*/
private RectF mBarRect = new RectF();
/**
* {@code Shader} instance used to fill the shader of the paint.
*/
private Shader shader;
/**
* {@code true} if the user clicked on the pointer to start the move mode. <br>
* {@code false} once the user stops touching the screen.
*
* @see #onTouchEvent(android.view.MotionEvent)
*/
private boolean mIsMovingPointer;
/**
* The ARGB value of the currently selected color.
*/
private int mColor;
/**
* An array of floats that can be build into a {@code Color} <br>
* Where we can extract the color from.
*/
private float[] mHSVColor = new float[3];
/**
* Factor used to calculate the position to the Opacity on the bar.
*/
private float mPosToSatFactor;
/**
* Factor used to calculate the Opacity to the postion on the bar.
*/
private float mSatToPosFactor;
/**
* {@code ColorPicker} instance used to control the ColorPicker.
*/
private ColorPicker mPicker = null;
/**
* Used to toggle orientation between vertical and horizontal.
*/
private boolean mOrientation;
/**
* Interface and listener so that changes in SaturationBar are sent
* to the host activity/fragment
*/
private OnSaturationChangedListener onSaturationChangedListener;
/**
* Saturation of the latest entry of the onSaturationChangedListener.
*/
private int oldChangedListenerSaturation;
public interface OnSaturationChangedListener {
public void onSaturationChanged(int saturation);
}
public void setOnSaturationChangedListener(OnSaturationChangedListener listener) {
this.onSaturationChangedListener = listener;
}
public OnSaturationChangedListener getOnSaturationChangedListener() {
return this.onSaturationChangedListener;
}
public SaturationBar(Context context) {
super(context);
init(null, 0);
}
public SaturationBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public SaturationBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
final TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.ColorBars, defStyle, 0);
final Resources b = getContext().getResources();
mBarThickness = a.getDimensionPixelSize(
R.styleable.ColorBars_bar_thickness,
b.getDimensionPixelSize(R.dimen.bar_thickness));
mBarLength = a.getDimensionPixelSize(R.styleable.ColorBars_bar_length,
b.getDimensionPixelSize(R.dimen.bar_length));
mPreferredBarLength = mBarLength;
mBarPointerRadius = a.getDimensionPixelSize(
R.styleable.ColorBars_bar_pointer_radius,
b.getDimensionPixelSize(R.dimen.bar_pointer_radius));
mBarPointerHaloRadius = a.getDimensionPixelSize(
R.styleable.ColorBars_bar_pointer_halo_radius,
b.getDimensionPixelSize(R.dimen.bar_pointer_halo_radius));
mOrientation = a.getBoolean(
R.styleable.ColorBars_bar_orientation_horizontal, ORIENTATION_DEFAULT);
a.recycle();
mBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBarPaint.setShader(shader);
mBarPointerPosition = mBarLength + mBarPointerHaloRadius;
mBarPointerHaloPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBarPointerHaloPaint.setColor(Color.BLACK);
mBarPointerHaloPaint.setAlpha(0x50);
mBarPointerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBarPointerPaint.setColor(0xff81ff00);
mPosToSatFactor = 1 / ((float) mBarLength);
mSatToPosFactor = ((float) mBarLength) / 1;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int intrinsicSize = mPreferredBarLength
+ (mBarPointerHaloRadius * 2);
// Variable orientation
int measureSpec;
if (mOrientation == ORIENTATION_HORIZONTAL) {
measureSpec = widthMeasureSpec;
}
else {
measureSpec = heightMeasureSpec;
}
int lengthMode = MeasureSpec.getMode(measureSpec);
int lengthSize = MeasureSpec.getSize(measureSpec);
int length;
if (lengthMode == MeasureSpec.EXACTLY) {
length = lengthSize;
}
else if (lengthMode == MeasureSpec.AT_MOST) {
length = Math.min(intrinsicSize, lengthSize);
}
else {
length = intrinsicSize;
}
int barPointerHaloRadiusx2 = mBarPointerHaloRadius * 2;
mBarLength = length - barPointerHaloRadiusx2;
if(mOrientation == ORIENTATION_VERTICAL) {
setMeasuredDimension(barPointerHaloRadiusx2,
(mBarLength + barPointerHaloRadiusx2));
}
else {
setMeasuredDimension((mBarLength + barPointerHaloRadiusx2),
barPointerHaloRadiusx2);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Fill the rectangle instance based on orientation
int x1, y1;
if (mOrientation == ORIENTATION_HORIZONTAL) {
x1 = (mBarLength + mBarPointerHaloRadius);
y1 = mBarThickness;
mBarLength = w - (mBarPointerHaloRadius * 2);
mBarRect.set(mBarPointerHaloRadius,
(mBarPointerHaloRadius - (mBarThickness / 2)),
(mBarLength + (mBarPointerHaloRadius)),
(mBarPointerHaloRadius + (mBarThickness / 2)));
}
else {
x1 = mBarThickness;
y1 = (mBarLength + mBarPointerHaloRadius);
mBarLength = h - (mBarPointerHaloRadius * 2);
mBarRect.set((mBarPointerHaloRadius - (mBarThickness / 2)),
mBarPointerHaloRadius,
(mBarPointerHaloRadius + (mBarThickness / 2)),
(mBarLength + (mBarPointerHaloRadius)));
}
// Update variables that depend of mBarLength.
if (!isInEditMode()){
shader = new LinearGradient(mBarPointerHaloRadius, 0,
x1, y1, new int[] {
Color.WHITE,
Color.HSVToColor(0xFF, mHSVColor) }, null,
Shader.TileMode.CLAMP);
} else {
shader = new LinearGradient(mBarPointerHaloRadius, 0,
x1, y1, new int[] {
Color.WHITE, 0xff81ff00 }, null, Shader.TileMode.CLAMP);
Color.colorToHSV(0xff81ff00, mHSVColor);
}
mBarPaint.setShader(shader);
mPosToSatFactor = 1 / ((float) mBarLength);
mSatToPosFactor = ((float) mBarLength) / 1;
float[] hsvColor = new float[3];
Color.colorToHSV(mColor, hsvColor);
if (!isInEditMode()){
mBarPointerPosition = Math.round((mSatToPosFactor * hsvColor[1])
+ mBarPointerHaloRadius);
} else {
mBarPointerPosition = mBarLength + mBarPointerHaloRadius;
}
}
@Override
protected void onDraw(Canvas canvas) {
// Draw the bar.
canvas.drawRect(mBarRect, mBarPaint);
// Calculate the center of the pointer.
int cX, cY;
if (mOrientation == ORIENTATION_HORIZONTAL) {
cX = mBarPointerPosition;
cY = mBarPointerHaloRadius;
}
else {
cX = mBarPointerHaloRadius;
cY = mBarPointerPosition;
}
// Draw the pointer halo.
canvas.drawCircle(cX, cY, mBarPointerHaloRadius, mBarPointerHaloPaint);
// Draw the pointer.
canvas.drawCircle(cX, cY, mBarPointerRadius, mBarPointerPaint);
};
@Override
public boolean onTouchEvent(MotionEvent event) {
getParent().requestDisallowInterceptTouchEvent(true);
// Convert coordinates to our internal coordinate system
float dimen;
if (mOrientation == ORIENTATION_HORIZONTAL) {
dimen = event.getX();
}
else {
dimen = event.getY();
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mIsMovingPointer = true;
// Check whether the user pressed on (or near) the pointer
if (dimen >= (mBarPointerHaloRadius)
&& dimen <= (mBarPointerHaloRadius + mBarLength)) {
mBarPointerPosition = Math.round(dimen);
calculateColor(Math.round(dimen));
mBarPointerPaint.setColor(mColor);
invalidate();
}
break;
case MotionEvent.ACTION_MOVE:
if (mIsMovingPointer) {
// Move the the pointer on the bar.
if (dimen >= mBarPointerHaloRadius
&& dimen <= (mBarPointerHaloRadius + mBarLength)) {
mBarPointerPosition = Math.round(dimen);
calculateColor(Math.round(dimen));
mBarPointerPaint.setColor(mColor);
if (mPicker != null) {
mPicker.setNewCenterColor(mColor);
mPicker.changeValueBarColor(mColor);
mPicker.changeOpacityBarColor(mColor);
}
invalidate();
} else if (dimen < mBarPointerHaloRadius) {
mBarPointerPosition = mBarPointerHaloRadius;
mColor = Color.WHITE;
mBarPointerPaint.setColor(mColor);
if (mPicker != null) {
mPicker.setNewCenterColor(mColor);
mPicker.changeValueBarColor(mColor);
mPicker.changeOpacityBarColor(mColor);
}
invalidate();
} else if (dimen > (mBarPointerHaloRadius + mBarLength)) {
mBarPointerPosition = mBarPointerHaloRadius + mBarLength;
mColor = Color.HSVToColor(mHSVColor);
mBarPointerPaint.setColor(mColor);
if (mPicker != null) {
mPicker.setNewCenterColor(mColor);
mPicker.changeValueBarColor(mColor);
mPicker.changeOpacityBarColor(mColor);
}
invalidate();
}
}
if(onSaturationChangedListener != null && oldChangedListenerSaturation != mColor){
onSaturationChangedListener.onSaturationChanged(mColor);
oldChangedListenerSaturation = mColor;
}
break;
case MotionEvent.ACTION_UP:
mIsMovingPointer = false;
break;
}
return true;
}
/**
* Set the bar color. <br>
* <br>
* Its discouraged to use this method.
*
* @param color
*/
public void setColor(int color) {
int x1, y1;
if(mOrientation == ORIENTATION_HORIZONTAL) {
x1 = (mBarLength + mBarPointerHaloRadius);
y1 = mBarThickness;
}
else {
x1 = mBarThickness;
y1 = (mBarLength + mBarPointerHaloRadius);
}
Color.colorToHSV(color, mHSVColor);
shader = new LinearGradient(mBarPointerHaloRadius, 0,
x1, y1, new int[] {
Color.WHITE, color }, null,
Shader.TileMode.CLAMP);
mBarPaint.setShader(shader);
calculateColor(mBarPointerPosition);
mBarPointerPaint.setColor(mColor);
if (mPicker != null) {
mPicker.setNewCenterColor(mColor);
if(mPicker.hasValueBar())
mPicker.changeValueBarColor(mColor);
else if(mPicker.hasOpacityBar())
mPicker.changeOpacityBarColor(mColor);
}
invalidate();
}
/**
* Set the pointer on the bar. With the opacity value.
*
* @param saturation
* float between 0 > 1
*/
public void setSaturation(float saturation) {
mBarPointerPosition = Math.round((mSatToPosFactor * saturation))
+ mBarPointerHaloRadius;
calculateColor(mBarPointerPosition);
mBarPointerPaint.setColor(mColor);
if (mPicker != null) {
mPicker.setNewCenterColor(mColor);
mPicker.changeValueBarColor(mColor);
mPicker.changeOpacityBarColor(mColor);
}
invalidate();
}
/**
* Calculate the color selected by the pointer on the bar.
*
* @param coord
* Coordinate of the pointer.
*/
private void calculateColor(int coord) {
coord = coord - mBarPointerHaloRadius;
if (coord < 0) {
coord = 0;
} else if (coord > mBarLength) {
coord = mBarLength;
}
mColor = Color.HSVToColor(
new float[] { mHSVColor[0],(mPosToSatFactor * coord),1f });
}
/**
* Get the currently selected color.
*
* @return The ARGB value of the currently selected color.
*/
public int getColor() {
return mColor;
}
/**
* Adds a {@code ColorPicker} instance to the bar. <br>
* <br>
* WARNING: Don't change the color picker. it is done already when the bar
* is added to the ColorPicker
*
* @see ColorPicker#addSVBar(SVBar)
* @param picker
*/
public void setColorPicker(ColorPicker picker) {
mPicker = picker;
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
Bundle state = new Bundle();
state.putParcelable(STATE_PARENT, superState);
state.putFloatArray(STATE_COLOR, mHSVColor);
float[] hsvColor = new float[3];
Color.colorToHSV(mColor, hsvColor);
state.putFloat(STATE_SATURATION, hsvColor[1]);
return state;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
Bundle savedState = (Bundle) state;
Parcelable superState = savedState.getParcelable(STATE_PARENT);
super.onRestoreInstanceState(superState);
setColor(Color.HSVToColor(savedState.getFloatArray(STATE_COLOR)));
setSaturation(savedState.getFloat(STATE_SATURATION));
}
}