From a9516dabd83407af26090ff0f12e2361bbfa759e Mon Sep 17 00:00:00 2001 From: Absinthe Date: Sat, 12 Aug 2023 14:19:06 +0800 Subject: [PATCH] Replace color-picker. --- app/build.gradle.kts | 17 +- app/libs/color-picker.aar | Bin 58732 -> 0 bytes .../view/home/ColorPickerDialogBuilder.java | 2 - build.gradle.kts | 1 - color-picker/.gitignore | 1 + color-picker/build.gradle | 24 + color-picker/proguard-rules.pro | 17 + .../flask/colorpicker/ApplicationTest.java | 13 + color-picker/src/main/AndroidManifest.xml | 5 + .../com/flask/colorpicker/ColorCircle.java | 54 ++ .../colorpicker/ColorCircleDrawable.java | 39 ++ .../colorpicker/ColorPickerPreference.java | 155 +++++ .../flask/colorpicker/ColorPickerView.java | 572 ++++++++++++++++++ .../colorpicker/OnColorChangedListener.java | 5 + .../colorpicker/OnColorSelectedListener.java | 5 + .../java/com/flask/colorpicker/Utils.java | 40 ++ .../builder/ColorPickerClickListener.java | 10 + .../builder/ColorPickerDialogBuilder.java | 296 +++++++++ .../builder/ColorWheelRendererBuilder.java | 18 + .../colorpicker/builder/PaintBuilder.java | 82 +++ .../renderer/AbsColorWheelRenderer.java | 34 ++ .../renderer/ColorWheelRenderOption.java | 10 + .../renderer/ColorWheelRenderer.java | 17 + .../renderer/FlowerColorWheelRenderer.java | 50 ++ .../renderer/SimpleColorWheelRenderer.java | 46 ++ .../colorpicker/slider/AbsCustomSlider.java | 189 ++++++ .../flask/colorpicker/slider/AlphaSlider.java | 100 +++ .../colorpicker/slider/LightnessSlider.java | 74 +++ .../slider/OnValueChangedListener.java | 5 + .../src/main/res/layout/color_edit.xml | 5 + .../src/main/res/layout/color_preview.xml | 7 + .../src/main/res/layout/color_selector.xml | 12 + .../src/main/res/layout/color_widget.xml | 7 + color-picker/src/main/res/values/attrs.xml | 25 + color-picker/src/main/res/values/dimens.xml | 10 + color-picker/src/main/res/values/styles.xml | 11 + settings.gradle.kts | 4 +- 37 files changed, 1950 insertions(+), 12 deletions(-) delete mode 100644 app/libs/color-picker.aar create mode 100755 color-picker/.gitignore create mode 100755 color-picker/build.gradle create mode 100755 color-picker/proguard-rules.pro create mode 100755 color-picker/src/androidTest/java/com/flask/colorpicker/ApplicationTest.java create mode 100755 color-picker/src/main/AndroidManifest.xml create mode 100755 color-picker/src/main/java/com/flask/colorpicker/ColorCircle.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/ColorCircleDrawable.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/ColorPickerPreference.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/ColorPickerView.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/OnColorChangedListener.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/OnColorSelectedListener.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/Utils.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/builder/ColorPickerClickListener.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/builder/ColorPickerDialogBuilder.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/builder/ColorWheelRendererBuilder.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/builder/PaintBuilder.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/renderer/AbsColorWheelRenderer.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/renderer/ColorWheelRenderOption.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/renderer/ColorWheelRenderer.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/renderer/FlowerColorWheelRenderer.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/renderer/SimpleColorWheelRenderer.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/slider/AbsCustomSlider.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/slider/AlphaSlider.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/slider/LightnessSlider.java create mode 100755 color-picker/src/main/java/com/flask/colorpicker/slider/OnValueChangedListener.java create mode 100755 color-picker/src/main/res/layout/color_edit.xml create mode 100755 color-picker/src/main/res/layout/color_preview.xml create mode 100755 color-picker/src/main/res/layout/color_selector.xml create mode 100755 color-picker/src/main/res/layout/color_widget.xml create mode 100755 color-picker/src/main/res/values/attrs.xml create mode 100755 color-picker/src/main/res/values/dimens.xml create mode 100755 color-picker/src/main/res/values/styles.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a1b8e457..18901ac2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,7 +7,6 @@ plugins { kotlin("android") id("com.google.devtools.ksp") id("kotlin-parcelize") - id("sdk-editor") id("dev.rikka.tools.materialthemebuilder") } @@ -15,7 +14,7 @@ val verName = "2.5.4" val verCode = 2050400 android { - compileSdk = 33 + compileSdk = 34 ndkVersion = "25.0.8775105" defaultConfig { @@ -39,6 +38,8 @@ android { } buildFeatures { + aidl = true + buildConfig = true viewBinding = true } @@ -88,7 +89,7 @@ android { } } - packagingOptions { + packaging { resources { excludes += "META-INF/**" excludes += "okhttp3/**" @@ -117,7 +118,7 @@ repositories { mavenCentral() } -val optimizeReleaseRes = task("optimizeReleaseRes").doLast { +val optimizeReleaseRes: Task = task("optimizeReleaseRes").doLast { val aapt2 = File( androidComponents.sdkComponents.sdkDirectory.get().asFile, "build-tools/${project.android.buildToolsVersion}/aapt2" @@ -161,7 +162,7 @@ configurations.all { dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) - implementation(files("libs/color-picker.aar")) + implementation(project(":color-picker")) implementation(files("libs/IceBox-SDK-1.0.6.aar")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") @@ -208,10 +209,10 @@ dependencies { ksp("com.github.bumptech.glide:compiler:4.15.1") implementation("com.google.code.gson:gson:2.9.0") - implementation("com.google.zxing:core:3.5.1") + implementation("com.google.zxing:core:3.5.2") implementation("com.blankj:utilcodex:1.31.1") - implementation("com.tencent:mmkv-static:1.3.0") - implementation("com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.11") + implementation("com.tencent:mmkv-static:1.3.1") + implementation("com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.14") implementation("com.github.heruoxin.Delegated-Scopes-Manager:client:master-SNAPSHOT") implementation("com.github.topjohnwu.libsu:core:5.2.0") implementation("com.github.thegrizzlylabs:sardine-android:0.8") diff --git a/app/libs/color-picker.aar b/app/libs/color-picker.aar deleted file mode 100644 index c70d8034addb8d2d1d6715b7c490fa9193c3c1bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58732 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 diff --git a/app/src/main/java/com/absinthe/anywhere_/view/home/ColorPickerDialogBuilder.java b/app/src/main/java/com/absinthe/anywhere_/view/home/ColorPickerDialogBuilder.java index 5a814d34..73039021 100644 --- a/app/src/main/java/com/absinthe/anywhere_/view/home/ColorPickerDialogBuilder.java +++ b/app/src/main/java/com/absinthe/anywhere_/view/home/ColorPickerDialogBuilder.java @@ -26,9 +26,7 @@ import com.flask.colorpicker.renderer.ColorWheelRenderer; import com.flask.colorpicker.slider.AlphaSlider; import com.flask.colorpicker.slider.LightnessSlider; -import com.iwhys.sdkeditor.domain.ReplaceClass; -@ReplaceClass("colorpicker:0.0.15") public class ColorPickerDialogBuilder { private final AnywhereDialogBuilder builder; private final LinearLayout pickerContainer; diff --git a/build.gradle.kts b/build.gradle.kts index e5a11954..1496dcd6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,6 @@ buildscript { classpath("com.android.tools.build:gradle:8.1.0") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0") classpath("com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.0-1.0.13") - classpath("com.github.iwhys:sdk-editor-plugin:1.1.7") classpath("dev.rikka.tools.materialthemebuilder:gradle-plugin:1.4.0") } } diff --git a/color-picker/.gitignore b/color-picker/.gitignore new file mode 100755 index 00000000..796b96d1 --- /dev/null +++ b/color-picker/.gitignore @@ -0,0 +1 @@ +/build diff --git a/color-picker/build.gradle b/color-picker/build.gradle new file mode 100755 index 00000000..206fbeb8 --- /dev/null +++ b/color-picker/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.android.library' + +android { + compileSdk 34 + + defaultConfig { + namespace "com.flask.colorpicker" + minSdkVersion 23 + targetSdkVersion 33 + versionCode 18 + versionName "0.0.16" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.6.1' +} diff --git a/color-picker/proguard-rules.pro b/color-picker/proguard-rules.pro new file mode 100755 index 00000000..73f71370 --- /dev/null +++ b/color-picker/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/flask/Documents/android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/color-picker/src/androidTest/java/com/flask/colorpicker/ApplicationTest.java b/color-picker/src/androidTest/java/com/flask/colorpicker/ApplicationTest.java new file mode 100755 index 00000000..9aca1fc2 --- /dev/null +++ b/color-picker/src/androidTest/java/com/flask/colorpicker/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.flask.colorpicker; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/color-picker/src/main/AndroidManifest.xml b/color-picker/src/main/AndroidManifest.xml new file mode 100755 index 00000000..804b8ac1 --- /dev/null +++ b/color-picker/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/color-picker/src/main/java/com/flask/colorpicker/ColorCircle.java b/color-picker/src/main/java/com/flask/colorpicker/ColorCircle.java new file mode 100755 index 00000000..ba28baa6 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/ColorCircle.java @@ -0,0 +1,54 @@ +package com.flask.colorpicker; + +import android.graphics.Color; + +public class ColorCircle { + private float x, y; + private float[] hsv = new float[3]; + private float[] hsvClone; + private int color; + + public ColorCircle(float x, float y, float[] hsv) { + set(x, y, hsv); + } + + public double sqDist(float x, float y) { + double dx = this.x - x; + double dy = this.y - y; + return dx * dx + dy * dy; + } + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + public float[] getHsv() { + return hsv; + } + + public float[] getHsvWithLightness(float lightness) { + if (hsvClone == null) + hsvClone = hsv.clone(); + hsvClone[0] = hsv[0]; + hsvClone[1] = hsv[1]; + hsvClone[2] = lightness; + return hsvClone; + } + + public void set(float x, float y, float[] hsv) { + this.x = x; + this.y = y; + this.hsv[0] = hsv[0]; + this.hsv[1] = hsv[1]; + this.hsv[2] = hsv[2]; + this.color = Color.HSVToColor(this.hsv); + } + + public int getColor() { + return color; + } +} \ No newline at end of file diff --git a/color-picker/src/main/java/com/flask/colorpicker/ColorCircleDrawable.java b/color-picker/src/main/java/com/flask/colorpicker/ColorCircleDrawable.java new file mode 100755 index 00000000..9284f063 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/ColorCircleDrawable.java @@ -0,0 +1,39 @@ +package com.flask.colorpicker; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.ColorDrawable; + +import com.flask.colorpicker.builder.PaintBuilder; + +public class ColorCircleDrawable extends ColorDrawable { + private float strokeWidth; + private Paint strokePaint = PaintBuilder.newPaint().style(Paint.Style.STROKE).stroke(strokeWidth).color(0xff9e9e9e).build(); + private Paint fillPaint = PaintBuilder.newPaint().style(Paint.Style.FILL).color(0).build(); + private Paint fillBackPaint = PaintBuilder.newPaint().shader(PaintBuilder.createAlphaPatternShader(26)).build(); + + public ColorCircleDrawable(int color) { + super(color); + } + + @Override + public void draw(Canvas canvas) { + canvas.drawColor(0); + + int width = canvas.getWidth(); + float radius = width / 2f; + strokeWidth = radius / 8f; + + this.strokePaint.setStrokeWidth(strokeWidth); + this.fillPaint.setColor(getColor()); + canvas.drawCircle(radius, radius, radius - strokeWidth, fillBackPaint); + canvas.drawCircle(radius, radius, radius - strokeWidth, fillPaint); + canvas.drawCircle(radius, radius, radius - strokeWidth, strokePaint); + } + + @Override + public void setColor(int color) { + super.setColor(color); + invalidateSelf(); + } +} diff --git a/color-picker/src/main/java/com/flask/colorpicker/ColorPickerPreference.java b/color-picker/src/main/java/com/flask/colorpicker/ColorPickerPreference.java new file mode 100755 index 00000000..6ef7b590 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/ColorPickerPreference.java @@ -0,0 +1,155 @@ +package com.flask.colorpicker; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.preference.Preference; +import androidx.annotation.NonNull; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; + +import com.flask.colorpicker.builder.ColorPickerClickListener; +import com.flask.colorpicker.builder.ColorPickerDialogBuilder; + +public class ColorPickerPreference extends Preference { + + protected boolean alphaSlider; + protected boolean lightSlider; + protected boolean border; + + protected int selectedColor = 0; + + protected ColorPickerView.WHEEL_TYPE wheelType; + protected int density; + + private boolean pickerColorEdit; + private String pickerTitle; + private String pickerButtonCancel; + private String pickerButtonOk; + + protected ImageView colorIndicator; + + public ColorPickerPreference(Context context) { + super(context); + } + + public ColorPickerPreference(Context context, AttributeSet attrs) { + super(context, attrs); + initWith(context, attrs); + } + + public ColorPickerPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initWith(context, attrs); + } + + private void initWith(Context context, AttributeSet attrs) { + final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ColorPickerPreference); + + try { + alphaSlider = typedArray.getBoolean(R.styleable.ColorPickerPreference_alphaSlider, false); + lightSlider = typedArray.getBoolean(R.styleable.ColorPickerPreference_lightnessSlider, false); + border = typedArray.getBoolean(R.styleable.ColorPickerPreference_border, true); + + density = typedArray.getInt(R.styleable.ColorPickerPreference_density, 8); + wheelType = ColorPickerView.WHEEL_TYPE.indexOf(typedArray.getInt(R.styleable.ColorPickerPreference_wheelType, 0)); + + selectedColor = typedArray.getInt(R.styleable.ColorPickerPreference_initialColor, 0xffffffff); + + pickerColorEdit = typedArray.getBoolean(R.styleable.ColorPickerPreference_pickerColorEdit, true); + pickerTitle = typedArray.getString(R.styleable.ColorPickerPreference_pickerTitle); + if (pickerTitle==null) + pickerTitle = "Choose color"; + + pickerButtonCancel = typedArray.getString(R.styleable.ColorPickerPreference_pickerButtonCancel); + if (pickerButtonCancel==null) + pickerButtonCancel = "cancel"; + + pickerButtonOk = typedArray.getString(R.styleable.ColorPickerPreference_pickerButtonOk); + if (pickerButtonOk==null) + pickerButtonOk = "ok"; + + } finally { + typedArray.recycle(); + } + + setWidgetLayoutResource(R.layout.color_widget); + } + + + @Override + protected void onBindView(@NonNull View view) { + super.onBindView(view); + + int tmpColor = isEnabled() + ? selectedColor + : darken(selectedColor, .5f); + + colorIndicator = (ImageView) view.findViewById(R.id.color_indicator); + + ColorCircleDrawable colorChoiceDrawable = null; + Drawable currentDrawable = colorIndicator.getDrawable(); + if (currentDrawable != null && currentDrawable instanceof ColorCircleDrawable) + colorChoiceDrawable = (ColorCircleDrawable) currentDrawable; + + if (colorChoiceDrawable == null) + colorChoiceDrawable = new ColorCircleDrawable(tmpColor); + + colorIndicator.setImageDrawable(colorChoiceDrawable); + } + + public void setValue(int value) { + if (callChangeListener(value)) { + selectedColor = value; + persistInt(value); + notifyChanged(); + } + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + setValue(restoreValue ? getPersistedInt(0) : (Integer) defaultValue); + } + + @Override + protected void onClick() { + ColorPickerDialogBuilder builder = ColorPickerDialogBuilder + .with(getContext()) + .setTitle(pickerTitle) + .initialColor(selectedColor) + .showBorder(border) + .wheelType(wheelType) + .density(density) + .showColorEdit(pickerColorEdit) + .setPositiveButton(pickerButtonOk, new ColorPickerClickListener() { + @Override + public void onClick(DialogInterface dialog, int selectedColorFromPicker, Integer[] allColors) { + setValue(selectedColorFromPicker); + } + }) + .setNegativeButton(pickerButtonCancel, null); + + if (!alphaSlider && !lightSlider) builder.noSliders(); + else if (!alphaSlider) builder.lightnessSliderOnly(); + else if (!lightSlider) builder.alphaSliderOnly(); + + builder + .build() + .show(); + } + + public static int darken(int color, float factor) { + int a = Color.alpha(color); + int r = Color.red(color); + int g = Color.green(color); + int b = Color.blue(color); + + return Color.argb(a, + Math.max((int)(r * factor), 0), + Math.max((int)(g * factor), 0), + Math.max((int)(b * factor), 0)); + } +} diff --git a/color-picker/src/main/java/com/flask/colorpicker/ColorPickerView.java b/color-picker/src/main/java/com/flask/colorpicker/ColorPickerView.java new file mode 100755 index 00000000..bad746c0 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/ColorPickerView.java @@ -0,0 +1,572 @@ +package com.flask.colorpicker; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.flask.colorpicker.builder.ColorWheelRendererBuilder; +import com.flask.colorpicker.builder.PaintBuilder; +import com.flask.colorpicker.renderer.ColorWheelRenderOption; +import com.flask.colorpicker.renderer.ColorWheelRenderer; +import com.flask.colorpicker.slider.AlphaSlider; +import com.flask.colorpicker.slider.LightnessSlider; + +import java.util.ArrayList; + +public class ColorPickerView extends View { + private static final float STROKE_RATIO = 1.5f; + + private Bitmap colorWheel; + private Canvas colorWheelCanvas; + private Bitmap currentColor; + private Canvas currentColorCanvas; + private boolean showBorder; + private int density = 8; + + private float lightness = 1; + private float alpha = 1; + private int backgroundColor = 0x00000000; + + private Integer initialColors[] = new Integer[]{null, null, null, null, null}; + private int colorSelection = 0; + private Integer initialColor; + private Integer pickerColorEditTextColor; + private Paint colorWheelFill = PaintBuilder.newPaint().color(0).build(); + private Paint selectorStroke = PaintBuilder.newPaint().color(0).build(); + private Paint alphaPatternPaint = PaintBuilder.newPaint().build(); + private ColorCircle currentColorCircle; + + private ArrayList colorChangedListeners = new ArrayList<>(); + private ArrayList listeners = new ArrayList<>(); + + private LightnessSlider lightnessSlider; + private AlphaSlider alphaSlider; + private EditText colorEdit; + private TextWatcher colorTextChange = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + try { + int color = Color.parseColor(s.toString()); + + // set the color without changing the edit text preventing stack overflow + setColor(color, false); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void afterTextChanged(Editable s) { + } + }; + private LinearLayout colorPreview; + + private ColorWheelRenderer renderer; + + private int alphaSliderViewId, lightnessSliderViewId; + + public ColorPickerView(Context context) { + super(context); + initWith(context, null); + } + + public ColorPickerView(Context context, AttributeSet attrs) { + super(context, attrs); + initWith(context, attrs); + } + + public ColorPickerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initWith(context, attrs); + } + + @TargetApi(21) + public ColorPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initWith(context, attrs); + } + + private void initWith(Context context, AttributeSet attrs) { + final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ColorPickerPreference); + + density = typedArray.getInt(R.styleable.ColorPickerPreference_density, 10); + initialColor = typedArray.getInt(R.styleable.ColorPickerPreference_initialColor, 0xffffffff); + + pickerColorEditTextColor = typedArray.getInt(R.styleable.ColorPickerPreference_pickerColorEditTextColor, 0xffffffff); + + WHEEL_TYPE wheelType = WHEEL_TYPE.indexOf(typedArray.getInt(R.styleable.ColorPickerPreference_wheelType, 0)); + ColorWheelRenderer renderer = ColorWheelRendererBuilder.getRenderer(wheelType); + + alphaSliderViewId = typedArray.getResourceId(R.styleable.ColorPickerPreference_alphaSliderView, 0); + lightnessSliderViewId = typedArray.getResourceId(R.styleable.ColorPickerPreference_lightnessSliderView, 0); + + setRenderer(renderer); + setDensity(density); + setInitialColor(initialColor, true); + + typedArray.recycle(); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + updateColorWheel(); + currentColorCircle = findNearestByColor(initialColor); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (alphaSliderViewId != 0) + setAlphaSlider((AlphaSlider) getRootView().findViewById(alphaSliderViewId)); + if (lightnessSliderViewId != 0) + setLightnessSlider((LightnessSlider) getRootView().findViewById(lightnessSliderViewId)); + + updateColorWheel(); + currentColorCircle = findNearestByColor(initialColor); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + updateColorWheel(); + } + + private void updateColorWheel() { + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + + if (height < width) + width = height; + if (width <= 0) + return; + if (colorWheel == null || colorWheel.getWidth() != width) { + colorWheel = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888); + colorWheelCanvas = new Canvas(colorWheel); + alphaPatternPaint.setShader(PaintBuilder.createAlphaPatternShader(26)); + } + if (currentColor == null || currentColor.getWidth() != width) { + currentColor = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888); + currentColorCanvas = new Canvas(currentColor); + } + drawColorWheel(); + invalidate(); + } + + private void drawColorWheel() { + colorWheelCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + currentColorCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + + if (renderer == null) return; + + float half = colorWheelCanvas.getWidth() / 2f; + float strokeWidth = STROKE_RATIO * (1f + ColorWheelRenderer.GAP_PERCENTAGE); + float maxRadius = half - strokeWidth - half / density; + float cSize = maxRadius / (density - 1) / 2; + + ColorWheelRenderOption colorWheelRenderOption = renderer.getRenderOption(); + colorWheelRenderOption.density = this.density; + colorWheelRenderOption.maxRadius = maxRadius; + colorWheelRenderOption.cSize = cSize; + colorWheelRenderOption.strokeWidth = strokeWidth; + colorWheelRenderOption.alpha = alpha; + colorWheelRenderOption.lightness = lightness; + colorWheelRenderOption.targetCanvas = colorWheelCanvas; + + renderer.initWith(colorWheelRenderOption); + renderer.draw(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int width = 0; + if (widthMode == MeasureSpec.UNSPECIFIED) + width = widthMeasureSpec; + else if (widthMode == MeasureSpec.AT_MOST) + width = MeasureSpec.getSize(widthMeasureSpec); + else if (widthMode == MeasureSpec.EXACTLY) + width = MeasureSpec.getSize(widthMeasureSpec); + + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int height = 0; + if (heightMode == MeasureSpec.UNSPECIFIED) + height = heightMeasureSpec; + else if (heightMode == MeasureSpec.AT_MOST) + height = MeasureSpec.getSize(heightMeasureSpec); + else if (heightMode == MeasureSpec.EXACTLY) + height = MeasureSpec.getSize(heightMeasureSpec); + int squareDimen = width; + if (height < width) + squareDimen = height; + setMeasuredDimension(squareDimen, squareDimen); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: { + int lastSelectedColor = getSelectedColor(); + currentColorCircle = findNearestByPosition(event.getX(), event.getY()); + int selectedColor = getSelectedColor(); + + callOnColorChangedListeners(lastSelectedColor, selectedColor); + + initialColor = selectedColor; + setColorToSliders(selectedColor); + updateColorWheel(); + invalidate(); + break; + } + case MotionEvent.ACTION_UP: { + int selectedColor = getSelectedColor(); + if (listeners != null) { + for (OnColorSelectedListener listener : listeners) { + try { + listener.onColorSelected(selectedColor); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + setColorToSliders(selectedColor); + setColorText(selectedColor); + setColorPreviewColor(selectedColor); + invalidate(); + break; + } + } + return true; + } + + protected void callOnColorChangedListeners(int oldColor, int newColor) { + if (colorChangedListeners != null && oldColor != newColor) { + for (OnColorChangedListener listener : colorChangedListeners) { + try { + listener.onColorChanged(newColor); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawColor(backgroundColor); + + float maxRadius = canvas.getWidth() / (1f + ColorWheelRenderer.GAP_PERCENTAGE); + float size = maxRadius / density / 2; + if (colorWheel != null && currentColorCircle != null) { + colorWheelFill.setColor(Color.HSVToColor(currentColorCircle.getHsvWithLightness(this.lightness))); + colorWheelFill.setAlpha((int) (alpha * 0xff)); + + // a separate canvas is used to erase an issue with the alpha pattern around the edges + // draw circle slightly larger than it needs to be, then erase edges to proper dimensions + currentColorCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + 4, alphaPatternPaint); + currentColorCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + 4, colorWheelFill); + + selectorStroke = PaintBuilder.newPaint().color(0xffffffff).style(Paint.Style.STROKE).stroke(size * (STROKE_RATIO - 1)).xPerMode(PorterDuff.Mode.CLEAR).build(); + + if (showBorder) colorWheelCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + (selectorStroke.getStrokeWidth() / 2f), selectorStroke); + canvas.drawBitmap(colorWheel, 0, 0, null); + + currentColorCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + (selectorStroke.getStrokeWidth() / 2f), selectorStroke); + canvas.drawBitmap(currentColor, 0, 0, null); + } + } + + private ColorCircle findNearestByPosition(float x, float y) { + ColorCircle near = null; + double minDist = Double.MAX_VALUE; + + for (ColorCircle colorCircle : renderer.getColorCircleList()) { + double dist = colorCircle.sqDist(x, y); + if (minDist > dist) { + minDist = dist; + near = colorCircle; + } + } + + return near; + } + + private ColorCircle findNearestByColor(int color) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + ColorCircle near = null; + double minDiff = Double.MAX_VALUE; + double x = hsv[1] * Math.cos(hsv[0] * Math.PI / 180); + double y = hsv[1] * Math.sin(hsv[0] * Math.PI / 180); + + for (ColorCircle colorCircle : renderer.getColorCircleList()) { + float[] hsv1 = colorCircle.getHsv(); + double x1 = hsv1[1] * Math.cos(hsv1[0] * Math.PI / 180); + double y1 = hsv1[1] * Math.sin(hsv1[0] * Math.PI / 180); + double dx = x - x1; + double dy = y - y1; + double dist = dx * dx + dy * dy; + if (dist < minDiff) { + minDiff = dist; + near = colorCircle; + } + } + + return near; + } + + public int getSelectedColor() { + int color = 0; + if (currentColorCircle != null) + color = Utils.colorAtLightness(currentColorCircle.getColor(), this.lightness); + return Utils.adjustAlpha(this.alpha, color); + } + + public Integer[] getAllColors() { + return initialColors; + } + + public void setInitialColors(Integer[] colors, int selectedColor) { + this.initialColors = colors; + this.colorSelection = selectedColor; + Integer initialColor = this.initialColors[this.colorSelection]; + if (initialColor == null) initialColor = 0xffffffff; + setInitialColor(initialColor, true); + } + + public void setInitialColor(int color, boolean updateText) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + + this.alpha = Utils.getAlphaPercent(color); + this.lightness = hsv[2]; + this.initialColors[this.colorSelection] = color; + this.initialColor = color; + setColorPreviewColor(color); + setColorToSliders(color); + if (this.colorEdit != null && updateText) + setColorText(color); + currentColorCircle = findNearestByColor(color); + } + + public void setLightness(float lightness) { + int lastSelectedColor = getSelectedColor(); + + this.lightness = lightness; + if (currentColorCircle != null) { + this.initialColor = Color.HSVToColor(Utils.alphaValueAsInt(this.alpha), currentColorCircle.getHsvWithLightness(lightness)); + if (this.colorEdit != null) + this.colorEdit.setText(Utils.getHexString(this.initialColor, this.alphaSlider != null)); + if (this.alphaSlider != null && this.initialColor != null) + this.alphaSlider.setColor(this.initialColor); + + callOnColorChangedListeners(lastSelectedColor, this.initialColor); + + updateColorWheel(); + invalidate(); + } + } + + public void setColor(int color, boolean updateText) { + setInitialColor(color, updateText); + updateColorWheel(); + invalidate(); + } + + public void setAlphaValue(float alpha) { + int lastSelectedColor = getSelectedColor(); + + this.alpha = alpha; + this.initialColor = Color.HSVToColor(Utils.alphaValueAsInt(this.alpha), currentColorCircle.getHsvWithLightness(this.lightness)); + if (this.colorEdit != null) + this.colorEdit.setText(Utils.getHexString(this.initialColor, this.alphaSlider != null)); + if (this.lightnessSlider != null && this.initialColor != null) + this.lightnessSlider.setColor(this.initialColor); + + callOnColorChangedListeners(lastSelectedColor, this.initialColor); + + updateColorWheel(); + invalidate(); + } + + public void addOnColorChangedListener(OnColorChangedListener listener) { + this.colorChangedListeners.add(listener); + } + + public void addOnColorSelectedListener(OnColorSelectedListener listener) { + this.listeners.add(listener); + } + + public void setLightnessSlider(LightnessSlider lightnessSlider) { + this.lightnessSlider = lightnessSlider; + if (lightnessSlider != null) { + this.lightnessSlider.setColorPicker(this); + this.lightnessSlider.setColor(getSelectedColor()); + } + } + + public void setAlphaSlider(AlphaSlider alphaSlider) { + this.alphaSlider = alphaSlider; + if (alphaSlider != null) { + this.alphaSlider.setColorPicker(this); + this.alphaSlider.setColor(getSelectedColor()); + } + } + + public void setColorEdit(EditText colorEdit) { + this.colorEdit = colorEdit; + if (this.colorEdit != null) { + this.colorEdit.setVisibility(View.VISIBLE); + this.colorEdit.addTextChangedListener(colorTextChange); + setColorEditTextColor(pickerColorEditTextColor); + } + } + + public void setColorEditTextColor(int argb) { + this.pickerColorEditTextColor = argb; + if (colorEdit != null) + colorEdit.setTextColor(argb); + } + + public void setDensity(int density) { + this.density = Math.max(2, density); + invalidate(); + } + + public void setRenderer(ColorWheelRenderer renderer) { + this.renderer = renderer; + invalidate(); + } + + public void setColorPreview(LinearLayout colorPreview, Integer selectedColor) { + if (colorPreview == null) + return; + this.colorPreview = colorPreview; + if (selectedColor == null) + selectedColor = 0; + int children = colorPreview.getChildCount(); + if (children == 0 || colorPreview.getVisibility() != View.VISIBLE) + return; + + for (int i = 0; i < children; i++) { + View childView = colorPreview.getChildAt(i); + if (!(childView instanceof LinearLayout)) + continue; + LinearLayout childLayout = (LinearLayout) childView; + if (i == selectedColor) { + childLayout.setBackgroundColor(Color.WHITE); + } + ImageView childImage = (ImageView) childLayout.findViewById(R.id.image_preview); + childImage.setClickable(true); + childImage.setTag(i); + childImage.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (v == null) + return; + Object tag = v.getTag(); + if (tag == null || !(tag instanceof Integer)) + return; + setSelectedColor((int) tag); + } + }); + } + } + + public void setSelectedColor(int previewNumber) { + if (initialColors == null || initialColors.length < previewNumber) + return; + this.colorSelection = previewNumber; + setHighlightedColor(previewNumber); + Integer color = initialColors[previewNumber]; + if (color == null) + return; + setColor(color, true); + } + + public void setShowBorder(boolean showBorder) { + this.showBorder = showBorder; + } + + private void setHighlightedColor(int previewNumber) { + int children = colorPreview.getChildCount(); + if (children == 0 || colorPreview.getVisibility() != View.VISIBLE) + return; + + for (int i = 0; i < children; i++) { + View childView = colorPreview.getChildAt(i); + if (!(childView instanceof LinearLayout)) + continue; + LinearLayout childLayout = (LinearLayout) childView; + if (i == previewNumber) { + childLayout.setBackgroundColor(Color.WHITE); + } else { + childLayout.setBackgroundColor(Color.TRANSPARENT); + } + } + } + + private void setColorPreviewColor(int newColor) { + if (colorPreview == null || initialColors == null || colorSelection > initialColors.length || initialColors[colorSelection] == null) + return; + + int children = colorPreview.getChildCount(); + if (children == 0 || colorPreview.getVisibility() != View.VISIBLE) + return; + + View childView = colorPreview.getChildAt(colorSelection); + if (!(childView instanceof LinearLayout)) + return; + LinearLayout childLayout = (LinearLayout) childView; + ImageView childImage = (ImageView) childLayout.findViewById(R.id.image_preview); + childImage.setImageDrawable(new ColorCircleDrawable(newColor)); + } + + private void setColorText(int argb) { + if (colorEdit == null) + return; + colorEdit.setText(Utils.getHexString(argb, this.alphaSlider != null)); + } + + private void setColorToSliders(int selectedColor) { + if (lightnessSlider != null) + lightnessSlider.setColor(selectedColor); + if (alphaSlider != null) + alphaSlider.setColor(selectedColor); + } + + public enum WHEEL_TYPE { + FLOWER, CIRCLE; + + public static WHEEL_TYPE indexOf(int index) { + switch (index) { + case 0: + return FLOWER; + case 1: + return CIRCLE; + } + return FLOWER; + } + } +} diff --git a/color-picker/src/main/java/com/flask/colorpicker/OnColorChangedListener.java b/color-picker/src/main/java/com/flask/colorpicker/OnColorChangedListener.java new file mode 100755 index 00000000..eda2a53d --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/OnColorChangedListener.java @@ -0,0 +1,5 @@ +package com.flask.colorpicker; + +public interface OnColorChangedListener { + void onColorChanged(int selectedColor); +} diff --git a/color-picker/src/main/java/com/flask/colorpicker/OnColorSelectedListener.java b/color-picker/src/main/java/com/flask/colorpicker/OnColorSelectedListener.java new file mode 100755 index 00000000..dbf8f723 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/OnColorSelectedListener.java @@ -0,0 +1,5 @@ +package com.flask.colorpicker; + +public interface OnColorSelectedListener { + void onColorSelected(int selectedColor); +} diff --git a/color-picker/src/main/java/com/flask/colorpicker/Utils.java b/color-picker/src/main/java/com/flask/colorpicker/Utils.java new file mode 100755 index 00000000..8e92c5eb --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/Utils.java @@ -0,0 +1,40 @@ +package com.flask.colorpicker; + +import android.graphics.Color; + +/** + * Created by Charles Andersons on 4/17/15. + */ +public class Utils { + public static float getAlphaPercent(int argb) { + return Color.alpha(argb) / 255f; + } + + public static int alphaValueAsInt(float alpha) { + return Math.round(alpha * 255); + } + + public static int adjustAlpha(float alpha, int color) { + return alphaValueAsInt(alpha) << 24 | (0x00ffffff & color); + } + + public static int colorAtLightness(int color, float lightness) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + hsv[2] = lightness; + return Color.HSVToColor(hsv); + } + + public static float lightnessOfColor(int color) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + return hsv[2]; + } + + public static String getHexString(int color, boolean showAlpha) { + int base = showAlpha ? 0xFFFFFFFF : 0xFFFFFF; + String format = showAlpha ? "#%08X" : "#%06X"; + return String.format(format, (base & color)).toUpperCase(); + } + +} diff --git a/color-picker/src/main/java/com/flask/colorpicker/builder/ColorPickerClickListener.java b/color-picker/src/main/java/com/flask/colorpicker/builder/ColorPickerClickListener.java new file mode 100755 index 00000000..35e70e92 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/builder/ColorPickerClickListener.java @@ -0,0 +1,10 @@ +package com.flask.colorpicker.builder; + +import android.content.DialogInterface; + +/** + * Created by Charles Anderson on 4/17/15. + */ +public interface ColorPickerClickListener { + void onClick(DialogInterface d, int lastSelectedColor, Integer[] allColors); +} diff --git a/color-picker/src/main/java/com/flask/colorpicker/builder/ColorPickerDialogBuilder.java b/color-picker/src/main/java/com/flask/colorpicker/builder/ColorPickerDialogBuilder.java new file mode 100755 index 00000000..aa7cb428 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/builder/ColorPickerDialogBuilder.java @@ -0,0 +1,296 @@ +package com.flask.colorpicker.builder; + +import androidx.appcompat.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.text.InputFilter; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.flask.colorpicker.ColorPickerView; +import com.flask.colorpicker.OnColorChangedListener; +import com.flask.colorpicker.OnColorSelectedListener; +import com.flask.colorpicker.R; +import com.flask.colorpicker.Utils; +import com.flask.colorpicker.renderer.ColorWheelRenderer; +import com.flask.colorpicker.slider.AlphaSlider; +import com.flask.colorpicker.slider.LightnessSlider; + +public class ColorPickerDialogBuilder { + private AlertDialog.Builder builder; + private LinearLayout pickerContainer; + private ColorPickerView colorPickerView; + private LightnessSlider lightnessSlider; + private AlphaSlider alphaSlider; + private EditText colorEdit; + private LinearLayout colorPreview; + + private boolean isLightnessSliderEnabled = true; + private boolean isAlphaSliderEnabled = true; + private boolean isBorderEnabled = true; + private boolean isColorEditEnabled = false; + private boolean isPreviewEnabled = false; + private int pickerCount = 1; + private int defaultMargin = 0; + private int defaultMarginTop = 0; + private Integer[] initialColor = new Integer[]{null, null, null, null, null}; + + private ColorPickerDialogBuilder(Context context) { + this(context, 0); + } + + private ColorPickerDialogBuilder(Context context, int theme) { + defaultMargin = getDimensionAsPx(context, R.dimen.default_slider_margin); + defaultMarginTop = getDimensionAsPx(context, R.dimen.default_margin_top); + + builder = new AlertDialog.Builder(context, theme); + pickerContainer = new LinearLayout(context); + pickerContainer.setOrientation(LinearLayout.VERTICAL); + pickerContainer.setGravity(Gravity.CENTER_HORIZONTAL); + pickerContainer.setPadding(defaultMargin, defaultMarginTop, defaultMargin, 0); + + LinearLayout.LayoutParams layoutParamsForColorPickerView = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); + layoutParamsForColorPickerView.weight = 1; + colorPickerView = new ColorPickerView(context); + + pickerContainer.addView(colorPickerView, layoutParamsForColorPickerView); + + builder.setView(pickerContainer); + } + + public static ColorPickerDialogBuilder with(Context context) { + return new ColorPickerDialogBuilder(context); + } + + public static ColorPickerDialogBuilder with(Context context, int theme) { + return new ColorPickerDialogBuilder(context, theme); + } + + public ColorPickerDialogBuilder setTitle(String title) { + builder.setTitle(title); + return this; + } + + public ColorPickerDialogBuilder setTitle(int titleId) { + builder.setTitle(titleId); + return this; + } + + public ColorPickerDialogBuilder initialColor(int initialColor) { + this.initialColor[0] = initialColor; + return this; + } + + public ColorPickerDialogBuilder initialColors(int[] initialColor) { + for (int i = 0; i < initialColor.length && i < this.initialColor.length; i++) { + this.initialColor[i] = initialColor[i]; + } + return this; + } + + public ColorPickerDialogBuilder wheelType(ColorPickerView.WHEEL_TYPE wheelType) { + ColorWheelRenderer renderer = ColorWheelRendererBuilder.getRenderer(wheelType); + colorPickerView.setRenderer(renderer); + return this; + } + + public ColorPickerDialogBuilder density(int density) { + colorPickerView.setDensity(density); + return this; + } + + public ColorPickerDialogBuilder setOnColorChangedListener(OnColorChangedListener onColorChangedListener) { + colorPickerView.addOnColorChangedListener(onColorChangedListener); + return this; + } + + public ColorPickerDialogBuilder setOnColorSelectedListener(OnColorSelectedListener onColorSelectedListener) { + colorPickerView.addOnColorSelectedListener(onColorSelectedListener); + return this; + } + + public ColorPickerDialogBuilder setPositiveButton(CharSequence text, final ColorPickerClickListener onClickListener) { + builder.setPositiveButton(text, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + positiveButtonOnClick(dialog, onClickListener); + } + }); + return this; + } + + public ColorPickerDialogBuilder setPositiveButton(int textId, final ColorPickerClickListener onClickListener) { + builder.setPositiveButton(textId, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + positiveButtonOnClick(dialog, onClickListener); + } + }); + return this; + } + + public ColorPickerDialogBuilder setNegativeButton(CharSequence text, DialogInterface.OnClickListener onClickListener) { + builder.setNegativeButton(text, onClickListener); + return this; + } + + public ColorPickerDialogBuilder setNegativeButton(int textId, DialogInterface.OnClickListener onClickListener) { + builder.setNegativeButton(textId, onClickListener); + return this; + } + + public ColorPickerDialogBuilder noSliders() { + isLightnessSliderEnabled = false; + isAlphaSliderEnabled = false; + return this; + } + + public ColorPickerDialogBuilder alphaSliderOnly() { + isLightnessSliderEnabled = false; + isAlphaSliderEnabled = true; + return this; + } + + public ColorPickerDialogBuilder lightnessSliderOnly() { + isLightnessSliderEnabled = true; + isAlphaSliderEnabled = false; + return this; + } + + public ColorPickerDialogBuilder showAlphaSlider(boolean showAlpha) { + isAlphaSliderEnabled = showAlpha; + return this; + } + + public ColorPickerDialogBuilder showLightnessSlider(boolean showLightness) { + isLightnessSliderEnabled = showLightness; + return this; + } + + public ColorPickerDialogBuilder showBorder(boolean showBorder) { + isBorderEnabled = showBorder; + return this; + } + + public ColorPickerDialogBuilder showColorEdit(boolean showEdit) { + isColorEditEnabled = showEdit; + return this; + } + + public ColorPickerDialogBuilder setColorEditTextColor(int argb) { + colorPickerView.setColorEditTextColor(argb); + return this; + } + + public ColorPickerDialogBuilder showColorPreview(boolean showPreview) { + isPreviewEnabled = showPreview; + if (!showPreview) + pickerCount = 1; + return this; + } + + public ColorPickerDialogBuilder setPickerCount(int pickerCount) throws IndexOutOfBoundsException { + if (pickerCount < 1 || pickerCount > 5) + throw new IndexOutOfBoundsException("Picker Can Only Support 1-5 Colors"); + this.pickerCount = pickerCount; + if (this.pickerCount > 1) + this.isPreviewEnabled = true; + return this; + } + + public AlertDialog build() { + Context context = builder.getContext(); + colorPickerView.setInitialColors(initialColor, getStartOffset(initialColor)); + colorPickerView.setShowBorder(isBorderEnabled); + + if (isLightnessSliderEnabled) { + LinearLayout.LayoutParams layoutParamsForLightnessBar = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getDimensionAsPx(context, R.dimen.default_slider_height)); + lightnessSlider = new LightnessSlider(context); + lightnessSlider.setLayoutParams(layoutParamsForLightnessBar); + pickerContainer.addView(lightnessSlider); + colorPickerView.setLightnessSlider(lightnessSlider); + lightnessSlider.setColor(getStartColor(initialColor)); + lightnessSlider.setShowBorder(isBorderEnabled); + } + if (isAlphaSliderEnabled) { + LinearLayout.LayoutParams layoutParamsForAlphaBar = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getDimensionAsPx(context, R.dimen.default_slider_height)); + alphaSlider = new AlphaSlider(context); + alphaSlider.setLayoutParams(layoutParamsForAlphaBar); + pickerContainer.addView(alphaSlider); + colorPickerView.setAlphaSlider(alphaSlider); + alphaSlider.setColor(getStartColor(initialColor)); + alphaSlider.setShowBorder(isBorderEnabled); + } + if (isColorEditEnabled) { + LinearLayout.LayoutParams layoutParamsForColorEdit = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + colorEdit = (EditText) View.inflate(context, R.layout.color_edit, null); + colorEdit.setFilters(new InputFilter[]{new InputFilter.AllCaps()}); + colorEdit.setSingleLine(); + colorEdit.setVisibility(View.GONE); + + // limit number of characters to hexColors + int maxLength = isAlphaSliderEnabled ? 9 : 7; + colorEdit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)}); + + pickerContainer.addView(colorEdit, layoutParamsForColorEdit); + + colorEdit.setText(Utils.getHexString(getStartColor(initialColor), isAlphaSliderEnabled)); + colorPickerView.setColorEdit(colorEdit); + } + if (isPreviewEnabled) { + colorPreview = (LinearLayout) View.inflate(context, R.layout.color_preview, null); + colorPreview.setVisibility(View.GONE); + pickerContainer.addView(colorPreview); + + if (initialColor.length == 0) { + ImageView colorImage = (ImageView) View.inflate(context, R.layout.color_selector, null); + colorImage.setImageDrawable(new ColorDrawable(Color.WHITE)); + } else { + for (int i = 0; i < initialColor.length && i < this.pickerCount; i++) { + if (initialColor[i] == null) + break; + LinearLayout colorLayout = (LinearLayout) View.inflate(context, R.layout.color_selector, null); + ImageView colorImage = (ImageView) colorLayout.findViewById(R.id.image_preview); + colorImage.setImageDrawable(new ColorDrawable(initialColor[i])); + colorPreview.addView(colorLayout); + } + } + colorPreview.setVisibility(View.VISIBLE); + colorPickerView.setColorPreview(colorPreview, getStartOffset(initialColor)); + } + + return builder.create(); + } + + private Integer getStartOffset(Integer[] colors) { + Integer start = 0; + for (int i = 0; i < colors.length; i++) { + if (colors[i] == null) { + return start; + } + start = (i + 1) / 2; + } + return start; + } + + private int getStartColor(Integer[] colors) { + Integer startColor = getStartOffset(colors); + return startColor == null ? Color.WHITE : colors[startColor]; + } + + private static int getDimensionAsPx(Context context, int rid) { + return (int) (context.getResources().getDimension(rid) + .5f); + } + + private void positiveButtonOnClick(DialogInterface dialog, ColorPickerClickListener onClickListener) { + int selectedColor = colorPickerView.getSelectedColor(); + Integer[] allColors = colorPickerView.getAllColors(); + onClickListener.onClick(dialog, selectedColor, allColors); + } +} diff --git a/color-picker/src/main/java/com/flask/colorpicker/builder/ColorWheelRendererBuilder.java b/color-picker/src/main/java/com/flask/colorpicker/builder/ColorWheelRendererBuilder.java new file mode 100755 index 00000000..cc816ee9 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/builder/ColorWheelRendererBuilder.java @@ -0,0 +1,18 @@ +package com.flask.colorpicker.builder; + +import com.flask.colorpicker.ColorPickerView; +import com.flask.colorpicker.renderer.ColorWheelRenderer; +import com.flask.colorpicker.renderer.FlowerColorWheelRenderer; +import com.flask.colorpicker.renderer.SimpleColorWheelRenderer; + +public class ColorWheelRendererBuilder { + public static ColorWheelRenderer getRenderer(ColorPickerView.WHEEL_TYPE wheelType) { + switch (wheelType) { + case CIRCLE: + return new SimpleColorWheelRenderer(); + case FLOWER: + return new FlowerColorWheelRenderer(); + } + throw new IllegalArgumentException("wrong WHEEL_TYPE"); + } +} \ No newline at end of file diff --git a/color-picker/src/main/java/com/flask/colorpicker/builder/PaintBuilder.java b/color-picker/src/main/java/com/flask/colorpicker/builder/PaintBuilder.java new file mode 100755 index 00000000..a8bb6a5f --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/builder/PaintBuilder.java @@ -0,0 +1,82 @@ +package com.flask.colorpicker.builder; + +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Shader; + +public class PaintBuilder { + public static PaintHolder newPaint() { + return new PaintHolder(); + } + + public static class PaintHolder { + private Paint paint; + + private PaintHolder() { + this.paint = new Paint(Paint.ANTI_ALIAS_FLAG); + } + + public PaintHolder color(int color) { + this.paint.setColor(color); + return this; + } + + public PaintHolder antiAlias(boolean flag) { + this.paint.setAntiAlias(flag); + return this; + } + + public PaintHolder style(Paint.Style style) { + this.paint.setStyle(style); + return this; + } + + public PaintHolder mode(PorterDuff.Mode mode) { + this.paint.setXfermode(new PorterDuffXfermode(mode)); + return this; + } + + public PaintHolder stroke(float width) { + this.paint.setStrokeWidth(width); + return this; + } + + public PaintHolder xPerMode(PorterDuff.Mode mode) { + this.paint.setXfermode(new PorterDuffXfermode(mode)); + return this; + } + + public PaintHolder shader(Shader shader) { + this.paint.setShader(shader); + return this; + } + + public Paint build() { + return this.paint; + } + } + + public static Shader createAlphaPatternShader(int size) { + size /= 2; + size = Math.max(8, size * 2); + return new BitmapShader(createAlphaBackgroundPattern(size), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); + } + + private static Bitmap createAlphaBackgroundPattern(int size) { + Paint alphaPatternPaint = PaintBuilder.newPaint().build(); + Bitmap bm = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bm); + int s = Math.round(size / 2f); + for (int i = 0; i < 2; i++) + for (int j = 0; j < 2; j++) { + if ((i + j) % 2 == 0) alphaPatternPaint.setColor(0xffffffff); + else alphaPatternPaint.setColor(0xffd0d0d0); + c.drawRect(i * s, j * s, (i + 1) * s, (j + 1) * s, alphaPatternPaint); + } + return bm; + } +} diff --git a/color-picker/src/main/java/com/flask/colorpicker/renderer/AbsColorWheelRenderer.java b/color-picker/src/main/java/com/flask/colorpicker/renderer/AbsColorWheelRenderer.java new file mode 100755 index 00000000..9ce0f980 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/renderer/AbsColorWheelRenderer.java @@ -0,0 +1,34 @@ +package com.flask.colorpicker.renderer; + +import com.flask.colorpicker.ColorCircle; + +import java.util.ArrayList; +import java.util.List; + +public abstract class AbsColorWheelRenderer implements ColorWheelRenderer { + protected ColorWheelRenderOption colorWheelRenderOption; + protected List colorCircleList = new ArrayList<>(); + + public void initWith(ColorWheelRenderOption colorWheelRenderOption) { + this.colorWheelRenderOption = colorWheelRenderOption; + this.colorCircleList.clear(); + } + + @Override + public ColorWheelRenderOption getRenderOption() { + if (colorWheelRenderOption == null) colorWheelRenderOption = new ColorWheelRenderOption(); + return colorWheelRenderOption; + } + + public List getColorCircleList() { + return colorCircleList; + } + + protected int getAlphaValueAsInt() { + return Math.round(colorWheelRenderOption.alpha * 255); + } + + protected int calcTotalCount(float radius, float size) { + return Math.max(1, (int) ((1f - GAP_PERCENTAGE) * Math.PI / (Math.asin(size / radius)) + 0.5f)); + } +} \ No newline at end of file diff --git a/color-picker/src/main/java/com/flask/colorpicker/renderer/ColorWheelRenderOption.java b/color-picker/src/main/java/com/flask/colorpicker/renderer/ColorWheelRenderOption.java new file mode 100755 index 00000000..6a8c7eb1 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/renderer/ColorWheelRenderOption.java @@ -0,0 +1,10 @@ +package com.flask.colorpicker.renderer; + +import android.graphics.Canvas; + +public class ColorWheelRenderOption { + public int density; + public float maxRadius; + public float cSize, strokeWidth, alpha, lightness; + public Canvas targetCanvas; +} \ No newline at end of file diff --git a/color-picker/src/main/java/com/flask/colorpicker/renderer/ColorWheelRenderer.java b/color-picker/src/main/java/com/flask/colorpicker/renderer/ColorWheelRenderer.java new file mode 100755 index 00000000..180fcda2 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/renderer/ColorWheelRenderer.java @@ -0,0 +1,17 @@ +package com.flask.colorpicker.renderer; + +import com.flask.colorpicker.ColorCircle; + +import java.util.List; + +public interface ColorWheelRenderer { + float GAP_PERCENTAGE = 0.025f; + + void draw(); + + ColorWheelRenderOption getRenderOption(); + + void initWith(ColorWheelRenderOption colorWheelRenderOption); + + List getColorCircleList(); +} diff --git a/color-picker/src/main/java/com/flask/colorpicker/renderer/FlowerColorWheelRenderer.java b/color-picker/src/main/java/com/flask/colorpicker/renderer/FlowerColorWheelRenderer.java new file mode 100755 index 00000000..02927af6 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/renderer/FlowerColorWheelRenderer.java @@ -0,0 +1,50 @@ +package com.flask.colorpicker.renderer; + +import android.graphics.Color; +import android.graphics.Paint; + +import com.flask.colorpicker.ColorCircle; +import com.flask.colorpicker.builder.PaintBuilder; + +public class FlowerColorWheelRenderer extends AbsColorWheelRenderer { + private Paint selectorFill = PaintBuilder.newPaint().build(); + private float[] hsv = new float[3]; + private float sizeJitter = 1.2f; + + @Override + public void draw() { + final int setSize = colorCircleList.size(); + int currentCount = 0; + float half = colorWheelRenderOption.targetCanvas.getWidth() / 2f; + int density = colorWheelRenderOption.density; + float strokeWidth = colorWheelRenderOption.strokeWidth; + float maxRadius = colorWheelRenderOption.maxRadius; + float cSize = colorWheelRenderOption.cSize; + + for (int i = 0; i < density; i++) { + float p = (float) i / (density - 1); // 0~1 + float jitter = (i - density / 2f) / density; // -0.5 ~ 0.5 + float radius = maxRadius * p; + float size = Math.max(1.5f + strokeWidth, cSize + (i == 0 ? 0 : cSize * sizeJitter * jitter)); + int total = Math.min(calcTotalCount(radius, size), density * 2); + + for (int j = 0; j < total; j++) { + double angle = Math.PI * 2 * j / total + (Math.PI / total) * ((i + 1) % 2); + float x = half + (float) (radius * Math.cos(angle)); + float y = half + (float) (radius * Math.sin(angle)); + hsv[0] = (float) (angle * 180 / Math.PI); + hsv[1] = radius / maxRadius; + hsv[2] = colorWheelRenderOption.lightness; + selectorFill.setColor(Color.HSVToColor(hsv)); + selectorFill.setAlpha(getAlphaValueAsInt()); + + colorWheelRenderOption.targetCanvas.drawCircle(x, y, size - strokeWidth, selectorFill); + + if (currentCount >= setSize) { + colorCircleList.add(new ColorCircle(x, y, hsv)); + } else colorCircleList.get(currentCount).set(x, y, hsv); + currentCount++; + } + } + } +} \ No newline at end of file diff --git a/color-picker/src/main/java/com/flask/colorpicker/renderer/SimpleColorWheelRenderer.java b/color-picker/src/main/java/com/flask/colorpicker/renderer/SimpleColorWheelRenderer.java new file mode 100755 index 00000000..46a3769d --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/renderer/SimpleColorWheelRenderer.java @@ -0,0 +1,46 @@ +package com.flask.colorpicker.renderer; + +import android.graphics.Color; +import android.graphics.Paint; + +import com.flask.colorpicker.ColorCircle; +import com.flask.colorpicker.builder.PaintBuilder; + +public class SimpleColorWheelRenderer extends AbsColorWheelRenderer { + private Paint selectorFill = PaintBuilder.newPaint().build(); + private float[] hsv = new float[3]; + + @Override + public void draw() { + final int setSize = colorCircleList.size(); + int currentCount = 0; + float half = colorWheelRenderOption.targetCanvas.getWidth() / 2f; + int density = colorWheelRenderOption.density; + float maxRadius = colorWheelRenderOption.maxRadius; + + for (int i = 0; i < density; i++) { + float p = (float) i / (density - 1); // 0~1 + float radius = maxRadius * p; + float size = colorWheelRenderOption.cSize; + int total = calcTotalCount(radius, size); + + for (int j = 0; j < total; j++) { + double angle = Math.PI * 2 * j / total + (Math.PI / total) * ((i + 1) % 2); + float x = half + (float) (radius * Math.cos(angle)); + float y = half + (float) (radius * Math.sin(angle)); + hsv[0] = (float) (angle * 180 / Math.PI); + hsv[1] = radius / maxRadius; + hsv[2] = colorWheelRenderOption.lightness; + selectorFill.setColor(Color.HSVToColor(hsv)); + selectorFill.setAlpha(getAlphaValueAsInt()); + + colorWheelRenderOption.targetCanvas.drawCircle(x, y, size - colorWheelRenderOption.strokeWidth, selectorFill); + + if (currentCount >= setSize) + colorCircleList.add(new ColorCircle(x, y, hsv)); + else colorCircleList.get(currentCount).set(x, y, hsv); + currentCount++; + } + } + } +} \ No newline at end of file diff --git a/color-picker/src/main/java/com/flask/colorpicker/slider/AbsCustomSlider.java b/color-picker/src/main/java/com/flask/colorpicker/slider/AbsCustomSlider.java new file mode 100755 index 00000000..76edec4e --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/slider/AbsCustomSlider.java @@ -0,0 +1,189 @@ +package com.flask.colorpicker.slider; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import androidx.annotation.DimenRes; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import com.flask.colorpicker.R; + +public abstract class AbsCustomSlider extends View { + protected Bitmap bitmap; + protected Canvas bitmapCanvas; + protected Bitmap bar; + protected Canvas barCanvas; + protected OnValueChangedListener onValueChangedListener; + protected int barOffsetX; + protected int handleRadius = 20; + protected int barHeight = 5; + protected float value = 1; + protected boolean showBorder = false; + + private boolean inVerticalOrientation = false; + + public AbsCustomSlider(Context context) { + super(context); + init(context, null); + } + + public AbsCustomSlider(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public AbsCustomSlider(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + TypedArray styledAttrs = context.getTheme().obtainStyledAttributes( + attrs, R.styleable.AbsCustomSlider, 0, 0); + try { + inVerticalOrientation = styledAttrs.getBoolean( + R.styleable.AbsCustomSlider_inVerticalOrientation, inVerticalOrientation); + } finally { + styledAttrs.recycle(); + } + } + + protected void updateBar() { + handleRadius = getDimension(R.dimen.default_slider_handler_radius); + barHeight = getDimension(R.dimen.default_slider_bar_height); + barOffsetX = handleRadius; + + if (bar == null) + createBitmaps(); + drawBar(barCanvas); + invalidate(); + } + + protected void createBitmaps() { + int width; + int height; + if (inVerticalOrientation) { + width = getHeight(); + height = getWidth(); + } else { + width = getWidth(); + height = getHeight(); + } + + bar = Bitmap.createBitmap(Math.max(width - barOffsetX * 2, 1), barHeight, Bitmap.Config.ARGB_8888); + barCanvas = new Canvas(bar); + + if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) { + if (bitmap != null) bitmap.recycle(); + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmapCanvas = new Canvas(bitmap); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + int width; + int height; + if (inVerticalOrientation) { + width = getHeight(); + height = getWidth(); + + canvas.rotate(-90); + canvas.translate(-width, 0); + } else { + width = getWidth(); + height = getHeight(); + } + + if (bar != null && bitmapCanvas != null) { + bitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + bitmapCanvas.drawBitmap(bar, barOffsetX, (height - bar.getHeight()) / 2, null); + + float x = handleRadius + value * (width - handleRadius * 2); + float y = height / 2f; + drawHandle(bitmapCanvas, x, y); + canvas.drawBitmap(bitmap, 0, 0, null); + } + } + + protected abstract void drawBar(Canvas barCanvas); + + protected abstract void onValueChanged(float value); + + protected abstract void drawHandle(Canvas canvas, float x, float y); + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + updateBar(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int width = 0; + if (widthMode == MeasureSpec.UNSPECIFIED) + width = widthMeasureSpec; + else if (widthMode == MeasureSpec.AT_MOST) + width = MeasureSpec.getSize(widthMeasureSpec); + else if (widthMode == MeasureSpec.EXACTLY) + width = MeasureSpec.getSize(widthMeasureSpec); + + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int height = 0; + if (heightMode == MeasureSpec.UNSPECIFIED) + height = heightMeasureSpec; + else if (heightMode == MeasureSpec.AT_MOST) + height = MeasureSpec.getSize(heightMeasureSpec); + else if (heightMode == MeasureSpec.EXACTLY) + height = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(width, height); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: { + if (bar != null) { + if (inVerticalOrientation) { + value = 1 - (event.getY() - barOffsetX) / bar.getWidth(); + } else { + value = (event.getX() - barOffsetX) / bar.getWidth(); + } + value = Math.max(0, Math.min(value, 1)); + onValueChanged(value); + invalidate(); + } + break; + } + case MotionEvent.ACTION_UP: { + onValueChanged(value); + if (onValueChangedListener != null) + onValueChangedListener.onValueChanged(value); + invalidate(); + } + } + return true; + } + + protected int getDimension(@DimenRes int id) { + return getResources().getDimensionPixelSize(id); + } + + public void setShowBorder(boolean showBorder) { + this.showBorder = showBorder; + } + + public void setOnValueChangedListener(OnValueChangedListener onValueChangedListener) { + this.onValueChangedListener = onValueChangedListener; + } +} \ No newline at end of file diff --git a/color-picker/src/main/java/com/flask/colorpicker/slider/AlphaSlider.java b/color-picker/src/main/java/com/flask/colorpicker/slider/AlphaSlider.java new file mode 100755 index 00000000..cf3db827 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/slider/AlphaSlider.java @@ -0,0 +1,100 @@ +package com.flask.colorpicker.slider; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.util.AttributeSet; + +import com.flask.colorpicker.ColorPickerView; +import com.flask.colorpicker.Utils; +import com.flask.colorpicker.builder.PaintBuilder; + +public class AlphaSlider extends AbsCustomSlider { + public int color; + private Paint alphaPatternPaint = PaintBuilder.newPaint().build(); + private Paint barPaint = PaintBuilder.newPaint().build(); + private Paint solid = PaintBuilder.newPaint().build(); + private Paint clearingStroke = PaintBuilder.newPaint().color(0xffffffff).xPerMode(PorterDuff.Mode.CLEAR).build(); + + private Paint clearStroke = PaintBuilder.newPaint().build(); + private Bitmap clearBitmap; + private Canvas clearBitmapCanvas; + + private ColorPickerView colorPicker; + + public AlphaSlider(Context context) { + super(context); + } + + public AlphaSlider(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AlphaSlider(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void createBitmaps() { + super.createBitmaps(); + alphaPatternPaint.setShader(PaintBuilder.createAlphaPatternShader(barHeight * 2)); + clearBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888); + clearBitmapCanvas = new Canvas(clearBitmap); + } + + @Override + protected void drawBar(Canvas barCanvas) { + int width = barCanvas.getWidth(); + int height = barCanvas.getHeight(); + + barCanvas.drawRect(0, 0, width, height, alphaPatternPaint); + int l = Math.max(2, width / 256); + for (int x = 0; x <= width; x += l) { + float alpha = (float) x / (width - 1); + barPaint.setColor(color); + barPaint.setAlpha(Math.round(alpha * 255)); + barCanvas.drawRect(x, 0, x + l, height, barPaint); + } + } + + @Override + protected void onValueChanged(float value) { + if (colorPicker != null) + colorPicker.setAlphaValue(value); + } + + @Override + protected void drawHandle(Canvas canvas, float x, float y) { + solid.setColor(color); + solid.setAlpha(Math.round(value * 255)); + if (showBorder) canvas.drawCircle(x, y, handleRadius, clearingStroke); + if (value < 1) { + // this fixes the same artifact issue from ColorPickerView + // happens when alpha pattern is drawn underneath a circle with the same size + clearBitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + clearBitmapCanvas.drawCircle(x, y, handleRadius * 0.75f + 4, alphaPatternPaint); + clearBitmapCanvas.drawCircle(x, y, handleRadius * 0.75f + 4, solid); + + clearStroke = PaintBuilder.newPaint().color(0xffffffff).style(Paint.Style.STROKE).stroke(6).xPerMode(PorterDuff.Mode.CLEAR).build(); + clearBitmapCanvas.drawCircle(x, y, handleRadius * 0.75f + (clearStroke.getStrokeWidth() / 2), clearStroke); + canvas.drawBitmap(clearBitmap, 0, 0, null); + } else { + canvas.drawCircle(x, y, handleRadius * 0.75f, solid); + } + } + + public void setColorPicker(ColorPickerView colorPicker) { + this.colorPicker = colorPicker; + } + + public void setColor(int color) { + this.color = color; + this.value = Utils.getAlphaPercent(color); + if (bar != null) { + updateBar(); + invalidate(); + } + } +} \ No newline at end of file diff --git a/color-picker/src/main/java/com/flask/colorpicker/slider/LightnessSlider.java b/color-picker/src/main/java/com/flask/colorpicker/slider/LightnessSlider.java new file mode 100755 index 00000000..58de0b21 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/slider/LightnessSlider.java @@ -0,0 +1,74 @@ +package com.flask.colorpicker.slider; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.util.AttributeSet; + +import com.flask.colorpicker.ColorPickerView; +import com.flask.colorpicker.Utils; +import com.flask.colorpicker.builder.PaintBuilder; + +public class LightnessSlider extends AbsCustomSlider { + private int color; + private Paint barPaint = PaintBuilder.newPaint().build(); + private Paint solid = PaintBuilder.newPaint().build(); + private Paint clearingStroke = PaintBuilder.newPaint().color(0xffffffff).xPerMode(PorterDuff.Mode.CLEAR).build(); + + private ColorPickerView colorPicker; + + public LightnessSlider(Context context) { + super(context); + } + + public LightnessSlider(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LightnessSlider(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void drawBar(Canvas barCanvas) { + int width = barCanvas.getWidth(); + int height = barCanvas.getHeight(); + + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + int l = Math.max(2, width / 256); + for (int x = 0; x <= width; x += l) { + hsv[2] = (float) x / (width - 1); + barPaint.setColor(Color.HSVToColor(hsv)); + barCanvas.drawRect(x, 0, x + l, height, barPaint); + } + } + + @Override + protected void onValueChanged(float value) { + if (colorPicker != null) + colorPicker.setLightness(value); + } + + @Override + protected void drawHandle(Canvas canvas, float x, float y) { + solid.setColor(Utils.colorAtLightness(color, value)); + if (showBorder) canvas.drawCircle(x, y, handleRadius, clearingStroke); + canvas.drawCircle(x, y, handleRadius * 0.75f, solid); + } + + public void setColorPicker(ColorPickerView colorPicker) { + this.colorPicker = colorPicker; + } + + public void setColor(int color) { + this.color = color; + this.value = Utils.lightnessOfColor(color); + if (bar != null) { + updateBar(); + invalidate(); + } + } +} \ No newline at end of file diff --git a/color-picker/src/main/java/com/flask/colorpicker/slider/OnValueChangedListener.java b/color-picker/src/main/java/com/flask/colorpicker/slider/OnValueChangedListener.java new file mode 100755 index 00000000..68b263a8 --- /dev/null +++ b/color-picker/src/main/java/com/flask/colorpicker/slider/OnValueChangedListener.java @@ -0,0 +1,5 @@ +package com.flask.colorpicker.slider; + +public interface OnValueChangedListener { + void onValueChanged(float value); +} \ No newline at end of file diff --git a/color-picker/src/main/res/layout/color_edit.xml b/color-picker/src/main/res/layout/color_edit.xml new file mode 100755 index 00000000..53707c89 --- /dev/null +++ b/color-picker/src/main/res/layout/color_edit.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/color-picker/src/main/res/layout/color_preview.xml b/color-picker/src/main/res/layout/color_preview.xml new file mode 100755 index 00000000..22c05514 --- /dev/null +++ b/color-picker/src/main/res/layout/color_preview.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/color-picker/src/main/res/layout/color_selector.xml b/color-picker/src/main/res/layout/color_selector.xml new file mode 100755 index 00000000..b9ef85a7 --- /dev/null +++ b/color-picker/src/main/res/layout/color_selector.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/color-picker/src/main/res/layout/color_widget.xml b/color-picker/src/main/res/layout/color_widget.xml new file mode 100755 index 00000000..9d0f6f72 --- /dev/null +++ b/color-picker/src/main/res/layout/color_widget.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/color-picker/src/main/res/values/attrs.xml b/color-picker/src/main/res/values/attrs.xml new file mode 100755 index 00000000..45887f99 --- /dev/null +++ b/color-picker/src/main/res/values/attrs.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/color-picker/src/main/res/values/dimens.xml b/color-picker/src/main/res/values/dimens.xml new file mode 100755 index 00000000..d3737543 --- /dev/null +++ b/color-picker/src/main/res/values/dimens.xml @@ -0,0 +1,10 @@ + + 36dp + 24dp + 4dp + 10dp + 24dp + 40dp + 36dp + 20dp + diff --git a/color-picker/src/main/res/values/styles.xml b/color-picker/src/main/res/values/styles.xml new file mode 100755 index 00000000..afdeddf1 --- /dev/null +++ b/color-picker/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index b37359b6..b2d38b70 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,7 @@ include(":app") +include(":color-picker") + rootProject.apply { name = "Anywhere-" buildFileName = "build.gradle.kts" -} \ No newline at end of file +}