From a9d708a70910be4314dc76ff30e55c913fd0045a Mon Sep 17 00:00:00 2001 From: skydoves Date: Wed, 16 Sep 2026 20:41:13 +0900 Subject: [PATCH 1/4] Keep the slide bar selector inside the bounds The returned value of getBoundaryX is used as the left position of the selector, but it was clamped by the half size of the selector. So the selector was placed over the right edge when the position is the maximum. Fixes #116 --- .../colorpickerview/sliders/AbstractSlider.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/colorpickerview/src/main/java/com/skydoves/colorpickerview/sliders/AbstractSlider.java b/colorpickerview/src/main/java/com/skydoves/colorpickerview/sliders/AbstractSlider.java index 9385332..86117d9 100644 --- a/colorpickerview/src/main/java/com/skydoves/colorpickerview/sliders/AbstractSlider.java +++ b/colorpickerview/src/main/java/com/skydoves/colorpickerview/sliders/AbstractSlider.java @@ -220,10 +220,13 @@ public void setSelectorByHalfSelectorPosition( } private float getBoundaryX(float x) { - int maxPos = getWidth() - selector.getWidth() / 2; - if (x >= maxPos) return maxPos; - if (x <= getSelectorSize() / 2f) return 0; - return x - getSelectorSize() / 2f; + // the given x is the center of the selector, but the returned value is used as the left + // position of the selector. So the selector should not be placed over both edges of the slider. + float maxPos = Math.max(0, getWidth() - getSelectorSize()); + float left = x - getSelectorSize() * 0.5f; + if (left >= maxPos) return maxPos; + if (left <= 0) return 0; + return left; } protected int getSelectorSize() { From 9e0d71e7fa0f785a04522106361542deab26b70c Mon Sep 17 00:00:00 2001 From: skydoves Date: Wed, 16 Sep 2026 20:41:13 +0900 Subject: [PATCH 2/4] Sync slide bars with a color picked from a bitmap palette A pixel of a bitmap palette has its own brightness and alpha, but the slide bars kept their own positions and overrode them. So picking a dark pixel notified a bright color, and picking black notified white. Fixes #92 --- .../colorpickerview/ColorPickerView.java | 63 +++++++++++++++++++ .../src/main/res/values/attrs_colorpicker.xml | 2 + 2 files changed, 65 insertions(+) diff --git a/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorPickerView.java b/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorPickerView.java index 1bc5a4d..ef01ff5 100644 --- a/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorPickerView.java +++ b/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorPickerView.java @@ -106,6 +106,7 @@ public class ColorPickerView extends FrameLayout implements LifecycleObserver { private String preferenceName; private boolean selectorPointValidation = true; private boolean resetBrightnessOnLowSaturation = true; + private boolean syncSlidersWithPaletteColor = true; private final ColorPickerPreferenceManager preferenceManager = ColorPickerPreferenceManager.getInstance(getContext()); @@ -183,6 +184,10 @@ private void getAttrs(AttributeSet attrs) { this.resetBrightnessOnLowSaturation = a.getBoolean(R.styleable.ColorPickerView_resetBrightnessOnLowSaturation, resetBrightnessOnLowSaturation); } + if (a.hasValue(R.styleable.ColorPickerView_syncSlidersWithPaletteColor)) { + this.syncSlidersWithPaletteColor = + a.getBoolean(R.styleable.ColorPickerView_syncSlidersWithPaletteColor, syncSlidersWithPaletteColor); + } } finally { a.recycle(); } @@ -290,6 +295,7 @@ protected void onCreateByBuilder(Builder builder) { if (builder.lifecycleOwner != null) setLifecycleOwner(builder.lifecycleOwner); this.selectorPointValidation = builder.selectorPointValidation; this.resetBrightnessOnLowSaturation = builder.resetBrightnessOnLowSaturation; + this.syncSlidersWithPaletteColor = builder.syncSlidersWithPaletteColor; } @SuppressLint("ClickableViewAccessibility") @@ -338,6 +344,8 @@ private boolean onTouchReceived(final MotionEvent event) { } } + syncSlidersWithPaletteColor(pixelColor); + if (actionMode == ActionMode.LAST) { notifyToFlagView(this.selectedPoint); if (event.getAction() == MotionEvent.ACTION_UP) { @@ -349,6 +357,29 @@ private boolean onTouchReceived(final MotionEvent event) { return true; } + /** + * synchronizes the {@link AlphaSlideBar} and the {@link BrightnessSlideBar} with a color, which + * is picked from a bitmap palette. + * + *

A pixel of a bitmap palette already contains its own brightness and alpha, but the slide + * bars keep their own positions. So the notified color would lose the brightness and the alpha + * of the picked pixel without this synchronization. + * + * @param color the picked color from the palette. + */ + private void syncSlidersWithPaletteColor(@ColorInt int color) { + if (!syncSlidersWithPaletteColor || isHuePalette()) return; + + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + if (brightnessSlider != null) { + brightnessSlider.setSelectorByHalfSelectorPosition(hsv[2]); + } + if (alphaSlideBar != null) { + alphaSlideBar.setSelectorByHalfSelectorPosition(Color.alpha(color) / 255f); + } + } + public boolean isHuePalette() { return palette.getDrawable() != null && palette.getDrawable() instanceof ColorHsvPalette; } @@ -662,6 +693,7 @@ public void setSelectorPoint(int x, int y) { selectedColor = color; selectedPoint = new Point(mappedPoint.x, mappedPoint.y); setCoordinate(mappedPoint.x, mappedPoint.y); + syncSlidersWithPaletteColor(color); fireColorListener(getColor(), false); notifyToFlagView(selectedPoint); } @@ -1035,6 +1067,31 @@ public void setResetBrightnessOnLowSaturation(boolean enabled) { this.resetBrightnessOnLowSaturation = enabled; } + /** + * Returns whether the slide bars are synchronized with a color picked from a bitmap palette. + * + * @return true if the synchronization is enabled, false otherwise. + */ + public boolean isSyncSlidersWithPaletteColorEnabled() { + return syncSlidersWithPaletteColor; + } + + /** + * Sets whether the slide bars are synchronized with a color picked from a bitmap palette. + * + *

When enabled (default), picking a pixel from a bitmap palette moves the brightness and the + * alpha slide bars to the brightness and the alpha of that pixel, so the notified color is the + * same as the picked pixel. When disabled, the slide bars keep their positions and they are + * applied to the picked color, as the previous versions did. + * + *

This option does not affect the default HSV palette. + * + * @param enabled true to enable the synchronization, false to disable. + */ + public void setSyncSlidersWithPaletteColor(boolean enabled) { + this.syncSlidersWithPaletteColor = enabled; + } + /** * sets the {@link LifecycleOwner}. * @@ -1098,6 +1155,7 @@ public static class Builder { private LifecycleOwner lifecycleOwner; private boolean selectorPointValidation = true; private boolean resetBrightnessOnLowSaturation = true; + private boolean syncSlidersWithPaletteColor = true; public Builder(Context context) { this.context = context; @@ -1203,6 +1261,11 @@ public Builder setResetBrightnessOnLowSaturation(boolean enabled) { return this; } + public Builder setSyncSlidersWithPaletteColor(boolean enabled) { + this.syncSlidersWithPaletteColor = enabled; + return this; + } + public ColorPickerView build() { ColorPickerView colorPickerView = new ColorPickerView(context); colorPickerView.onCreateByBuilder(this); diff --git a/colorpickerview/src/main/res/values/attrs_colorpicker.xml b/colorpickerview/src/main/res/values/attrs_colorpicker.xml index 6c8e53e..c60e93c 100644 --- a/colorpickerview/src/main/res/values/attrs_colorpicker.xml +++ b/colorpickerview/src/main/res/values/attrs_colorpicker.xml @@ -45,6 +45,8 @@ + + From d2337c0b5ba55b757c64d46e6cffa3b987db1b1f Mon Sep 17 00:00:00 2001 From: skydoves Date: Wed, 16 Sep 2026 20:41:13 +0900 Subject: [PATCH 3/4] Add getRgbHexCode and clarify the hex code format getHexCode returns the AARRGGBB form, which is not documented anywhere. getRgbHexCode returns the RRGGBB form for the cases the alpha channel is not needed. Fixes #123 --- .../colorpickerview/ColorEnvelope.java | 19 ++++++++++++++++++- .../skydoves/colorpickerview/ColorUtils.java | 12 ++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorEnvelope.java b/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorEnvelope.java index e0249c9..54603e2 100644 --- a/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorEnvelope.java +++ b/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorEnvelope.java @@ -24,11 +24,13 @@ public class ColorEnvelope { @ColorInt private int color; private String hexCode; + private String rgbHexCode; private int[] argb; public ColorEnvelope(@ColorInt int color) { this.color = color; this.hexCode = ColorUtils.getHexCode(color); + this.rgbHexCode = ColorUtils.getRgbHexCode(color); this.argb = ColorUtils.getColorARGB(color); } @@ -42,7 +44,10 @@ public ColorEnvelope(@ColorInt int color) { } /** - * gets envelope's hex code value. + * gets envelope's hex code value, which is formatted as AARRGGBB (alpha, red, green, blue). + * + *

For example, an opaque red color returns FFFF0000. Use {@link #getRgbHexCode()} if the + * alpha channel is not needed. * * @return hex code. */ @@ -50,6 +55,18 @@ public String getHexCode() { return hexCode; } + /** + * gets envelope's hex code value without the alpha channel, which is formatted as RRGGBB (red, + * green, blue). + * + *

For example, an opaque red color returns FF0000. + * + * @return hex code without the alpha channel. + */ + public String getRgbHexCode() { + return rgbHexCode; + } + /** * gets envelope's argb color. * diff --git a/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorUtils.java b/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorUtils.java index 9c7f768..f2f3782 100644 --- a/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorUtils.java +++ b/colorpickerview/src/main/java/com/skydoves/colorpickerview/ColorUtils.java @@ -22,13 +22,21 @@ /** ColorUtils a util class for changing the form of colors. */ class ColorUtils { - /** changes color to string hex code. */ + /** changes color to string hex code, which is formatted as AARRGGBB. */ public static String getHexCode(@ColorInt int color) { int a = Color.alpha(color); int r = Color.red(color); int g = Color.green(color); int b = Color.blue(color); - return String.format(Locale.getDefault(), "%02X%02X%02X%02X", a, r, g, b); + return String.format(Locale.ROOT, "%02X%02X%02X%02X", a, r, g, b); + } + + /** changes color to string hex code without the alpha channel, which is formatted as RRGGBB. */ + public static String getRgbHexCode(@ColorInt int color) { + int r = Color.red(color); + int g = Color.green(color); + int b = Color.blue(color); + return String.format(Locale.ROOT, "%02X%02X%02X", r, g, b); } /** changes color to argb integer array. */ From d9042bb5e7c528f943d8446231ece4bdbc2d313e Mon Sep 17 00:00:00 2001 From: skydoves Date: Wed, 16 Sep 2026 20:41:13 +0900 Subject: [PATCH 4/4] Avoid creating a zero sized bitmap on AlphaTileView AlphaTileView crashed with IllegalArgumentException when its size was changed to zero. Also updates the ABI dump file. --- colorpickerview/api/colorpickerview.api | 14 ++++++++++++++ .../skydoves/colorpickerview/AlphaTileView.java | 11 +++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/colorpickerview/api/colorpickerview.api b/colorpickerview/api/colorpickerview.api index 941f046..8684fe1 100644 --- a/colorpickerview/api/colorpickerview.api +++ b/colorpickerview/api/colorpickerview.api @@ -21,6 +21,7 @@ public class com/skydoves/colorpickerview/ColorEnvelope { public fun getArgb ()[I public fun getColor ()I public fun getHexCode ()Ljava/lang/String; + public fun getRgbHexCode ()Ljava/lang/String; } public class com/skydoves/colorpickerview/ColorHsvPalette : android/graphics/drawable/BitmapDrawable { @@ -138,6 +139,9 @@ public class com/skydoves/colorpickerview/ColorPickerView : android/widget/Frame public fun getSelectorX ()F public fun getSelectorY ()F public fun isHuePalette ()Z + public fun isResetBrightnessOnLowSaturationEnabled ()Z + public fun isSelectorPointValidationEnabled ()Z + public fun isSyncSlidersWithPaletteColorEnabled ()Z public fun moveSelectorPoint (III)V protected fun onCreateByBuilder (Lcom/skydoves/colorpickerview/ColorPickerView$Builder;)V public fun onDestroy ()V @@ -147,6 +151,7 @@ public class com/skydoves/colorpickerview/ColorPickerView : android/widget/Frame public fun selectByHsvColor (I)V public fun selectByHsvColorRes (I)V public fun selectCenter ()V + public fun selectWhite ()V public fun setActionMode (Lcom/skydoves/colorpickerview/ActionMode;)V public fun setColorListener (Lcom/skydoves/colorpickerview/listeners/ColorPickerViewListener;)V public fun setCoordinate (II)V @@ -160,8 +165,11 @@ public class com/skydoves/colorpickerview/ColorPickerView : android/widget/Frame public fun setPaletteDrawable (Landroid/graphics/drawable/Drawable;)V public fun setPreferenceName (Ljava/lang/String;)V public fun setPureColor (I)V + public fun setResetBrightnessOnLowSaturation (Z)V public fun setSelectorDrawable (Landroid/graphics/drawable/Drawable;)V public fun setSelectorPoint (II)V + public fun setSelectorPointValidation (Z)V + public fun setSyncSlidersWithPaletteColor (Z)V } public class com/skydoves/colorpickerview/ColorPickerView$Builder { @@ -181,9 +189,12 @@ public class com/skydoves/colorpickerview/ColorPickerView$Builder { public fun setLifecycleOwner (Landroidx/lifecycle/LifecycleOwner;)Lcom/skydoves/colorpickerview/ColorPickerView$Builder; public fun setPaletteDrawable (Landroid/graphics/drawable/Drawable;)Lcom/skydoves/colorpickerview/ColorPickerView$Builder; public fun setPreferenceName (Ljava/lang/String;)Lcom/skydoves/colorpickerview/ColorPickerView$Builder; + public fun setResetBrightnessOnLowSaturation (Z)Lcom/skydoves/colorpickerview/ColorPickerView$Builder; public fun setSelectorAlpha (F)Lcom/skydoves/colorpickerview/ColorPickerView$Builder; public fun setSelectorDrawable (Landroid/graphics/drawable/Drawable;)Lcom/skydoves/colorpickerview/ColorPickerView$Builder; + public fun setSelectorPointValidation (Z)Lcom/skydoves/colorpickerview/ColorPickerView$Builder; public fun setSelectorSize (I)Lcom/skydoves/colorpickerview/ColorPickerView$Builder; + public fun setSyncSlidersWithPaletteColor (Z)Lcom/skydoves/colorpickerview/ColorPickerView$Builder; public fun setWidth (I)Lcom/skydoves/colorpickerview/ColorPickerView$Builder; } @@ -278,15 +289,18 @@ public class com/skydoves/colorpickerview/preference/ColorPickerPreferenceManage public fun getColor (Ljava/lang/String;I)I protected fun getColorName (Ljava/lang/String;)Ljava/lang/String; public static fun getInstance (Landroid/content/Context;)Lcom/skydoves/colorpickerview/preference/ColorPickerPreferenceManager; + public static fun getInstance (Landroid/content/Context;Ljava/lang/String;)Lcom/skydoves/colorpickerview/preference/ColorPickerPreferenceManager; public fun getSelectorPosition (Ljava/lang/String;Landroid/graphics/Point;)Landroid/graphics/Point; protected fun getSelectorXName (Ljava/lang/String;)Ljava/lang/String; protected fun getSelectorYName (Ljava/lang/String;)Ljava/lang/String; + public fun getSharedPreferences ()Landroid/content/SharedPreferences; public fun restoreColorPickerData (Lcom/skydoves/colorpickerview/ColorPickerView;)V public fun saveColorPickerData (Lcom/skydoves/colorpickerview/ColorPickerView;)V public fun setAlphaSliderPosition (Ljava/lang/String;I)Lcom/skydoves/colorpickerview/preference/ColorPickerPreferenceManager; public fun setBrightnessSliderPosition (Ljava/lang/String;I)Lcom/skydoves/colorpickerview/preference/ColorPickerPreferenceManager; public fun setColor (Ljava/lang/String;I)Lcom/skydoves/colorpickerview/preference/ColorPickerPreferenceManager; public fun setSelectorPosition (Ljava/lang/String;Landroid/graphics/Point;)Lcom/skydoves/colorpickerview/preference/ColorPickerPreferenceManager; + public fun setSharedPreferenceName (Landroid/content/Context;Ljava/lang/String;)Lcom/skydoves/colorpickerview/preference/ColorPickerPreferenceManager; } public class com/skydoves/colorpickerview/sliders/AlphaSlideBar { diff --git a/colorpickerview/src/main/java/com/skydoves/colorpickerview/AlphaTileView.java b/colorpickerview/src/main/java/com/skydoves/colorpickerview/AlphaTileView.java index 4d746c8..7e83166 100644 --- a/colorpickerview/src/main/java/com/skydoves/colorpickerview/AlphaTileView.java +++ b/colorpickerview/src/main/java/com/skydoves/colorpickerview/AlphaTileView.java @@ -89,9 +89,14 @@ private void getAttrs(AttributeSet attrs) { @Override protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { super.onSizeChanged(width, height, oldWidth, oldHeight); + if (width <= 0 || height <= 0) { + backgroundBitmap = null; + return; + } + AlphaTileDrawable drawable = builder.build(); backgroundBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - if (backgroundBitmap != null && !backgroundBitmap.isRecycled()) { + if (!backgroundBitmap.isRecycled()) { Canvas backgroundCanvas = new Canvas(backgroundBitmap); drawable.setBounds(0, 0, backgroundCanvas.getWidth(), backgroundCanvas.getHeight()); drawable.draw(backgroundCanvas); @@ -101,7 +106,9 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - canvas.drawBitmap(backgroundBitmap, 0, 0, null); + if (backgroundBitmap != null && !backgroundBitmap.isRecycled()) { + canvas.drawBitmap(backgroundBitmap, 0, 0, null); + } canvas.drawRect(0, 0, getWidth(), getMeasuredHeight(), colorPaint); }