Improve color picker

* Add RGB sliders to manipulate only the red, green or blue color
  component.

* Add hexadecimal input field to let user specify the ARGB value.
This commit is contained in:
Peter Osterlund 2020-03-28 10:29:59 +01:00
parent 47b093446c
commit 4dcc24cee7
7 changed files with 292 additions and 79 deletions

View File

@ -49,6 +49,23 @@ class AHSVColor {
hsv[0] = oldHue;
}
/** Set red (0), green (1) or blue (2) color component. */
void setRGBComponent(int component, int value) {
int c = getARGB();
switch (component) {
case 0:
c = (c & 0xff00ffff) | (value << 16);
break;
case 1:
c = (c & 0xffff00ff) | (value << 8);
break;
case 2:
c = (c & 0xffffff00) | value;
break;
}
setARGB(c);
}
/** Get hue,sat,val values. */
float[] getHSV() {
return new float[]{hsv[0], hsv[1], hsv[2]};
@ -63,4 +80,15 @@ class AHSVColor {
int getARGB() {
return Color.HSVToColor(alpha, hsv);
}
/** Get red (0), green (1), or blue (2) color component. */
int getRGBComponent(int component) {
int c = getARGB();
switch (component) {
case 0: return Color.red(c);
case 1: return Color.green(c);
case 2: return Color.blue(c);
default: throw new RuntimeException("Internal error");
}
}
}

View File

@ -18,10 +18,13 @@ package net.margaritov.preference.colorpicker;
import org.petero.droidfish.R;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;
public class ColorPickerDialog
@ -32,7 +35,7 @@ public class ColorPickerDialog
View.OnClickListener {
private ColorPickerView mColorPicker;
private EditText colorCode;
private ColorPickerPanelView mOldColor;
private ColorPickerPanelView mNewColor;
@ -52,9 +55,7 @@ public class ColorPickerDialog
}
private void init(int color) {
// To fight color banding.
getWindow().setFormat(PixelFormat.RGBA_8888);
setUp(color, color);
}
@ -71,26 +72,49 @@ public class ColorPickerDialog
+ additionalInfo + "'");
mColorPicker = findViewById(R.id.color_picker_view);
colorCode = findViewById(R.id.color_code);
mOldColor = findViewById(R.id.old_color_panel);
mNewColor = findViewById(R.id.new_color_panel);
((LinearLayout) mOldColor.getParent()).setPadding(
Math.round(mColorPicker.getDrawingOffset()),
0,
Math.round(mColorPicker.getDrawingOffset()),
0
);
int offs = Math.round(mColorPicker.getDrawingOffset());
((LinearLayout) mOldColor.getParent()).setPadding(offs, 0, offs, 0);
mOldColor.setOnClickListener(this);
mNewColor.setOnClickListener(this);
mColorPicker.setOnColorChangedListener(this);
mOldColor.setColor(oldColor);
colorCode.setOnFocusChangeListener((view, hasFocus) -> {
if (!hasFocus)
applyColorCode();
});
colorCode.setOnEditorActionListener((v, id, event) -> {
colorCode.clearFocus();
String ims = Activity.INPUT_METHOD_SERVICE;
InputMethodManager imm = (InputMethodManager)getContext().getSystemService(ims);
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
return true;
});
mColorPicker.setColor(newColor, true);
}
@Override
public void onColorChanged(int color) {
mNewColor.setColor(color);
colorCode.setText(String.format("%08x", color));
}
private void applyColorCode() {
String txt = colorCode.getText().toString().trim().toLowerCase();
if (txt.length() != 8) // Format must be AARRGGBB
return;
try {
long longVal = Long.parseLong(txt, 16);
int val = (int)longVal;
if (val != mColorPicker.getColor())
mColorPicker.setColor(val, true);
} catch (NumberFormatException ignore) {}
}
/**

View File

@ -30,7 +30,6 @@ import android.view.View;
* @author Daniel Nilsson
*/
public class ColorPickerPanelView extends View {
/** The width in pixels of the border surrounding the color panel. */
private final static float BORDER_WIDTH_PX = 1;
@ -80,12 +79,21 @@ public class ColorPickerPanelView extends View {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
width = chooseSize(widthMode, width, Math.round(120f * mDensity));
height = chooseSize(heightMode, height, 0);
setMeasuredDimension(width, height);
}
int chooseSize(int mode, int size, int preferred) {
if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY)
return size;
return preferred; // MeasureSpec.UNSPECIFIED
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
@ -110,7 +118,6 @@ public class ColorPickerPanelView extends View {
mColorRect = new RectF(left,top, right, bottom);
mAlphaPattern = new AlphaPatternDrawable((int)(5 * mDensity));
mAlphaPattern.setBounds(Math.round(mColorRect.left),
Math.round(mColorRect.top),
Math.round(mColorRect.right),

View File

@ -18,6 +18,7 @@ package net.margaritov.preference.colorpicker;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.RectF;
@ -40,6 +41,9 @@ public class ColorPickerView extends View {
/** The height in dp of the alpha panel */
private float ALPHA_PANEL_HEIGHT = 20f;
/** The width or height in dp of one of the red/green/blue panels. */
private float RGB_PANEL_SIZE = 30f;
/** The distance in dp between the different color panels. */
private float PANEL_SPACING = 10f;
@ -56,9 +60,13 @@ public class ColorPickerView extends View {
/** Distance form the edges of the view of where we are allowed to draw. */
private RectF mDrawingRect;
/** Side of the satValPanel square. */
private float satValSide;
private GradientPanel satValPanel;
private GradientPanel huePanel;
private GradientPanel alphaPanel;
private GradientPanel[] rgbPanel = new GradientPanel[3];
private Point mStartTouchPoint = null;
@ -83,6 +91,7 @@ public class ColorPickerView extends View {
mDensity = getContext().getResources().getDisplayMetrics().density;
HUE_PANEL_WIDTH *= mDensity;
ALPHA_PANEL_HEIGHT *= mDensity;
RGB_PANEL_SIZE *= mDensity;
PANEL_SPACING *= mDensity;
mDrawingOffset = Math.max(5, BORDER_WIDTH_PX) * mDensity * 1.5f;
@ -93,6 +102,11 @@ public class ColorPickerView extends View {
setFocusableInTouchMode(true);
}
/** Return true if the current orientation is landscape. */
private boolean landScapeView() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
}
@Override
protected void onDraw(Canvas canvas) {
if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0)
@ -104,6 +118,9 @@ public class ColorPickerView extends View {
huePanel.draw(canvas);
if (alphaPanel != null)
alphaPanel.draw(canvas);
for (int i = 0; i < 3; i++)
if (rgbPanel[i] != null)
rgbPanel[i].draw(canvas);
}
@Override
@ -137,7 +154,8 @@ public class ColorPickerView extends View {
if (mStartTouchPoint == null)
return false;
for (GradientPanel pnl : new GradientPanel[]{satValPanel, huePanel, alphaPanel}) {
for (GradientPanel pnl : new GradientPanel[]{satValPanel, huePanel, alphaPanel,
rgbPanel[0], rgbPanel[1], rgbPanel[2]}) {
if (pnl != null && pnl.contains(mStartTouchPoint)) {
Point curPnt = new Point((int)event.getX(),
(int)event.getY());
@ -153,51 +171,56 @@ public class ColorPickerView extends View {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthAllowed = MeasureSpec.getSize(widthMeasureSpec);
int heightAllowed = MeasureSpec.getSize(heightMeasureSpec);
widthAllowed = chooseWidth(widthMode, widthAllowed);
heightAllowed = chooseHeight(heightMode, heightAllowed);
widthAllowed = chooseSize(widthMode, widthAllowed, getPreferredWidth());
heightAllowed = chooseSize(heightMode, heightAllowed, getPreferredHeight());
int width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH);
int height;
if (width > widthAllowed) {
width = widthAllowed;
height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT);
} else {
height = heightAllowed;
}
float side = getSatValSide(widthAllowed, heightAllowed);
float width = side + getExtraWidth();
float height = side + getExtraHeight();
setMeasuredDimension(width, height);
}
private int chooseWidth(int mode, int size) {
if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
return size;
} else { // (mode == MeasureSpec.UNSPECIFIED)
return getPreferredWidth();
}
}
private int chooseHeight(int mode, int size) {
if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
return size;
} else { // (mode == MeasureSpec.UNSPECIFIED)
return getPreferredHeight();
}
int newWidth = widthMode == MeasureSpec.EXACTLY ? widthAllowed : (int)width;
int newHeight = heightMode == MeasureSpec.EXACTLY ? heightAllowed : (int)height;
setMeasuredDimension(newWidth, newHeight);
}
private int getPreferredWidth() {
int width = getPreferredHeight();
width -= PANEL_SPACING + ALPHA_PANEL_HEIGHT;
return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING);
return (int)(200 * mDensity + getExtraWidth());
}
private int getPreferredHeight() {
int height = (int)(200 * mDensity);
height += PANEL_SPACING + ALPHA_PANEL_HEIGHT;
return height;
return (int)(200 * mDensity + getExtraHeight());
}
private int chooseSize(int mode, int size, int preferred) {
if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY)
return size;
return preferred; // MeasureSpec.UNSPECIFIED
}
/** Compute side of satValPanel given total available width/height. */
private float getSatValSide(float width, float height) {
float side1 = width - getExtraWidth();
float side2 = height - getExtraHeight();
return Math.min(side1, side2);
}
/** Amount of space to the right of the satVal panel. */
private float getExtraWidth() {
float ret = PANEL_SPACING + HUE_PANEL_WIDTH;
if (landScapeView())
ret += 3 * (PANEL_SPACING + RGB_PANEL_SIZE);
return ret;
}
/** Amount of space below the satVal panel. */
private float getExtraHeight() {
float ret = PANEL_SPACING + ALPHA_PANEL_HEIGHT;
if (!landScapeView())
ret += 3 * (PANEL_SPACING + RGB_PANEL_SIZE);
return ret;
}
@Override
@ -210,20 +233,23 @@ public class ColorPickerView extends View {
mDrawingRect.top = mDrawingOffset + getPaddingTop();
mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom();
satValSide = getSatValSide(mDrawingRect.width(),
mDrawingRect.height());
setUpSatValPanel();
setUpHuePanel();
setUpAlphaPanel();
setUpRGBPanels();
}
private void setUpSatValPanel() {
RectF dRect = mDrawingRect;
float panelSide = dRect.height() - BORDER_WIDTH_PX * 2;
panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT;
float b = BORDER_WIDTH_PX;
float left = dRect.left + BORDER_WIDTH_PX;
float right = left + panelSide;
float top = dRect.top + BORDER_WIDTH_PX;
float bottom = top + panelSide;
float left = dRect.left + b;
float right = left + satValSide - 2 * b;
float top = dRect.top + b;
float bottom = top + satValSide - 2 * b;
RectF satValRect = new RectF(left,top, right, bottom);
satValPanel = new SatValGradientPanel(satValRect, color, mDensity);
@ -231,11 +257,12 @@ public class ColorPickerView extends View {
private void setUpHuePanel() {
RectF dRect = mDrawingRect;
float b = BORDER_WIDTH_PX;
float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX;
float right = dRect.right - BORDER_WIDTH_PX;
float top = dRect.top + BORDER_WIDTH_PX;
float bottom = dRect.bottom - BORDER_WIDTH_PX - (PANEL_SPACING + ALPHA_PANEL_HEIGHT);
float left = dRect.left + satValSide + PANEL_SPACING + b;
float right = left + HUE_PANEL_WIDTH - 2 * b;
float top = dRect.top + b;
float bottom = top + satValSide - 2 * b;
RectF hueRect = new RectF(left, top, right, bottom);
huePanel = new HueGradientPanel(hueRect, color, mDensity);
@ -243,16 +270,45 @@ public class ColorPickerView extends View {
private void setUpAlphaPanel() {
RectF dRect = mDrawingRect;
float b = BORDER_WIDTH_PX;
float left = dRect.left + BORDER_WIDTH_PX;
float right = dRect.right - BORDER_WIDTH_PX;
float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX;
float bottom = dRect.bottom - BORDER_WIDTH_PX;
float left = dRect.left + b;
float right = dRect.right - b;
float top = dRect.top + satValSide + PANEL_SPACING + b;
float bottom = top + ALPHA_PANEL_HEIGHT - 2 * b;
RectF alphaRect = new RectF(left, top, right, bottom);
alphaPanel = new AlphaGradientPanel(alphaRect, color, mDensity);
}
private void setUpRGBPanels() {
RectF dRect = mDrawingRect;
float b = BORDER_WIDTH_PX;
float w = RGB_PANEL_SIZE;
float s = PANEL_SPACING;
if (!landScapeView()) {
float offs = dRect.top + satValSide + s + ALPHA_PANEL_HEIGHT;
for (int i = 0; i < 3; i++) {
float left = dRect.left + b;
float right = dRect.right - b;
float top = offs + i * (s + w) + s + b;
float bottom = top + w - 2 * b;
RectF rgbRect = new RectF(left, top, right, bottom);
rgbPanel[i] = new RGBGradientPanel(i, rgbRect, color, mDensity, true);
}
} else {
float offs = dRect.left + satValSide + s + HUE_PANEL_WIDTH;
for (int i = 0; i < 3; i++) {
float left = offs + i * (s + w) + s + b;
float right = left + w - 2 * b;
float top = dRect.top + b;
float bottom = top + satValSide - 2 * b;
RectF rgbRect = new RectF(left, top, right, bottom);
rgbPanel[i] = new RGBGradientPanel(i, rgbRect, color, mDensity, false);
}
}
}
/**
* Set a OnColorChangedListener to get notified when the color
* selected by the user has changed.

View File

@ -0,0 +1,80 @@
package net.margaritov.preference.colorpicker;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.Shader;
public class RGBGradientPanel extends GradientPanel {
private final int component; // 0=red, 1=green, 2=blue
private final int colorMask;
private final boolean horizontal;
/** Constructor. */
RGBGradientPanel(int component, RectF rect, AHSVColor color, float density,
boolean horizontal) {
super(rect, color, density, null);
this.component = component;
switch (component) {
case 0: colorMask = 0x00ff0000; break;
case 1: colorMask = 0x0000ff00; break;
case 2: colorMask = 0x000000ff; break;
default: colorMask = 0; break;
}
this.horizontal = horizontal;
}
@Override
protected void setGradientPaint() {
int rgb = color.getARGB();
int color00 = (rgb & ~colorMask) | 0xff000000;
int colorFF = (rgb | colorMask) | 0xff000000;
Shader rgbShader;
if (horizontal) {
rgbShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
color00, colorFF, Shader.TileMode.CLAMP);
} else {
rgbShader = new LinearGradient(rect.left, rect.bottom, rect.left, rect.top,
color00, colorFF, Shader.TileMode.CLAMP);
}
gradientPaint.setShader(rgbShader);
}
protected void drawTracker(Canvas canvas) {
int val = color.getRGBComponent(component);
Point p = rgbComponentToPoint(val);
drawRectangleTracker(canvas, p, horizontal);
}
@Override
void updateColor(Point point) {
int rgbVal = pointToRgbComponent(point);
color.setRGBComponent(component, rgbVal);
}
private Point rgbComponentToPoint(int val) {
if (horizontal) {
float width = rect.width();
return new Point((int)((val * width / 0xff) + rect.left),
(int)rect.top);
} else {
float height = rect.height();
return new Point((int)rect.left,
(int)(rect.bottom - (val * height / 0xff)));
}
}
private int pointToRgbComponent(Point p) {
if (horizontal) {
int width = (int)rect.width();
int x = Math.min(Math.max(p.x - (int)rect.left, 0), width);
return x * 0xff / width;
} else {
int height = (int)rect.height();
int y = Math.min(Math.max((int)rect.bottom - p.y, 0), height);
return y * 0xff / height;
}
}
}

View File

@ -30,8 +30,19 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="1.0"
android:layout_marginBottom="10dp">
<EditText
android:id="@+id/color_code"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text=""
android:typeface="monospace"
android:inputType="text" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -46,8 +57,7 @@
<net.margaritov.preference.colorpicker.ColorPickerPanelView
android:id="@+id/old_color_panel"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:layout_weight="0.5" />
android:layout_height="40dp" />
<TextView
android:layout_width="fill_parent"
@ -61,7 +71,6 @@
<net.margaritov.preference.colorpicker.ColorPickerPanelView
android:id="@+id/new_color_panel"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:layout_weight="0.5" />
android:layout_height="40dp" />
</LinearLayout>
</LinearLayout>

View File

@ -24,9 +24,18 @@
android:id="@+id/color_picker_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:tag="portrait" />
<EditText
android:id="@+id/color_code"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text=""
android:typeface="monospace"
android:inputType="text" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"