diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bacdf84a..f5a00c7f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,9 +6,7 @@ plugins { id("com.android.application") kotlin("android") id("com.google.devtools.ksp") - id("com.ke.gson.plugin") id("kotlin-parcelize") - id("sdk-editor") id("dev.rikka.tools.materialthemebuilder") } @@ -16,7 +14,7 @@ val verName = "2.5.4" val verCode = 2050400 android { - compileSdk = 33 + compileSdk = 34 ndkVersion = "25.0.8775105" defaultConfig { @@ -40,6 +38,8 @@ android { } buildFeatures { + aidl = true + buildConfig = true viewBinding = true } @@ -68,8 +68,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } androidComponents.onVariants { v -> @@ -89,7 +89,7 @@ android { } } - packagingOptions { + packaging { resources { excludes += "META-INF/**" excludes += "okhttp3/**" @@ -118,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" @@ -147,7 +147,7 @@ val optimizeReleaseRes = task("optimizeReleaseRes").doLast { } } -tasks.whenTaskAdded { +tasks.configureEach { if (name == "optimizeReleaseResources") { finalizedBy(optimizeReleaseRes) } @@ -162,10 +162,10 @@ 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.2") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") implementation("com.github.zhaobozhen.libraries:me:1.1.4") implementation("com.github.zhaobozhen.libraries:utils:1.1.4") @@ -186,20 +186,20 @@ dependencies { implementation("androidx.lifecycle:lifecycle-common-java8:${lifecycleVersion}") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${lifecycleVersion}") - implementation("androidx.browser:browser:1.5.0") + implementation("androidx.browser:browser:1.6.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0") - implementation("androidx.viewpager2:viewpager2:1.1.0-beta01") - implementation("androidx.recyclerview:recyclerview:1.3.0") + implementation("androidx.viewpager2:viewpager2:1.1.0-beta02") + implementation("androidx.recyclerview:recyclerview:1.3.1") implementation("androidx.drawerlayout:drawerlayout:1.2.0") //KTX implementation("androidx.collection:collection-ktx:1.2.0") implementation("androidx.activity:activity-ktx:1.7.2") - implementation("androidx.fragment:fragment-ktx:1.6.0") + implementation("androidx.fragment:fragment-ktx:1.6.1") implementation("androidx.palette:palette-ktx:1.0.0") implementation("androidx.core:core-ktx:1.10.1") - implementation("androidx.preference:preference-ktx:1.2.0") + implementation("androidx.preference:preference-ktx:1.2.1") //Google implementation("com.google.android.material:material:1.9.0") @@ -209,15 +209,16 @@ 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.tencent:mmkv-static:1.3.1") implementation("com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.11") implementation("com.github.heruoxin.Delegated-Scopes-Manager:client:master-SNAPSHOT") - implementation("com.github.topjohnwu.libsu:core:5.1.0") + implementation("com.github.topjohnwu.libsu:core:5.2.0") implementation("com.github.thegrizzlylabs:sardine-android:0.8") implementation("com.jonathanfinerty.once:once:1.3.1") implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3") + implementation("com.jakewharton.timber:timber:5.0.1") //UX implementation("com.drakeet.about:about:2.5.2") @@ -225,7 +226,7 @@ dependencies { implementation("com.drakeet.drawer:drawer:1.0.3") implementation("com.github.sephiroth74:android-target-tooltip:2.0.4") implementation("com.leinardi.android:speed-dial:3.3.0") - implementation("me.zhanghai.android.fastscroll:library:1.2.0") + implementation("me.zhanghai.android.fastscroll:library:1.3.0") val shizukuVersion = "12.2.0" // required by Shizuku and Sui @@ -236,7 +237,7 @@ dependencies { implementation("dev.rikka.rikkax.appcompat:appcompat:1.6.1") implementation("dev.rikka.rikkax.core:core:1.4.1") implementation("dev.rikka.rikkax.material:material:2.7.0") - implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.1") + implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.2") implementation("dev.rikka.rikkax.widget:borderview:1.1.0") implementation("dev.rikka.rikkax.preference:simplemenu-preference:1.0.3") implementation("dev.rikka.rikkax.insets:insets:1.3.0") @@ -247,7 +248,7 @@ dependencies { implementation("com.squareup.okhttp3:okhttp:4.11.0") implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:converter-gson:2.9.0") - implementation("com.squareup.okio:okio:3.3.0") + implementation("com.squareup.okio:okio:3.5.0") //Rx implementation("io.reactivex.rxjava2:rxandroid:2.1.1") diff --git a/app/libs/color-picker.aar b/app/libs/color-picker.aar deleted file mode 100644 index c70d8034..00000000 Binary files a/app/libs/color-picker.aar and /dev/null differ diff --git a/app/src/main/java/com/absinthe/anywhere_/adapter/applist/AppListAdapter.kt b/app/src/main/java/com/absinthe/anywhere_/adapter/applist/AppListAdapter.kt index f854ec88..28a4173b 100644 --- a/app/src/main/java/com/absinthe/anywhere_/adapter/applist/AppListAdapter.kt +++ b/app/src/main/java/com/absinthe/anywhere_/adapter/applist/AppListAdapter.kt @@ -1,6 +1,7 @@ package com.absinthe.anywhere_.adapter.applist import android.graphics.Color +import android.view.View import androidx.core.content.ContextCompat import com.absinthe.anywhere_.R import com.absinthe.anywhere_.constants.Const @@ -88,7 +89,7 @@ class AppListAdapter(mode: Int) : fun onClick(bean: AppListBean, which: Int) } - override fun getPopupText(position: Int): String { + override fun getPopupText(view: View, position: Int): CharSequence { return data[position].appName.ifEmpty { " " }.first().toString() } } diff --git a/app/src/main/java/com/absinthe/anywhere_/adapter/cloud/CloudRulesAdapter.kt b/app/src/main/java/com/absinthe/anywhere_/adapter/cloud/CloudRulesAdapter.kt index 7935a9ee..4ea372b3 100644 --- a/app/src/main/java/com/absinthe/anywhere_/adapter/cloud/CloudRulesAdapter.kt +++ b/app/src/main/java/com/absinthe/anywhere_/adapter/cloud/CloudRulesAdapter.kt @@ -1,5 +1,6 @@ package com.absinthe.anywhere_.adapter.cloud +import android.view.View import com.absinthe.anywhere_.R import com.absinthe.anywhere_.model.cloud.RuleEntity import com.chad.library.adapter.base.BaseQuickAdapter @@ -15,7 +16,7 @@ class CloudRulesAdapter : holder.setText(R.id.tv_contributor, item.contributor) } - override fun getPopupText(position: Int): String { + override fun getPopupText(view: View, position: Int): CharSequence { return data[position].name.ifEmpty { " " }.first().toString() } diff --git a/app/src/main/java/com/absinthe/anywhere_/ui/about/AboutActivity.kt b/app/src/main/java/com/absinthe/anywhere_/ui/about/AboutActivity.kt index 7203c756..3d4553d8 100644 --- a/app/src/main/java/com/absinthe/anywhere_/ui/about/AboutActivity.kt +++ b/app/src/main/java/com/absinthe/anywhere_/ui/about/AboutActivity.kt @@ -20,6 +20,7 @@ import com.absinthe.anywhere_.utils.handler.URLSchemeHandler import com.absinthe.anywhere_.utils.manager.DialogManager.showDebugDialog import com.absinthe.anywhere_.utils.manager.URLManager import com.absinthe.libraries.me.Absinthe +import com.absinthe.libraries.utils.utils.UiUtils import com.blankj.utilcode.util.AppUtils import com.drakeet.about.* import com.drakeet.about.provided.GlideImageLoader @@ -292,6 +293,7 @@ class AboutActivity : AbsAboutActivity() { private fun initView() { findViewById(R.id.toolbar)?.background = null + UiUtils.setSystemBarStyle(window) } override fun onCreateOptionsMenu(menu: Menu): Boolean { 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 28535583..ac18213b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,11 +7,9 @@ buildscript { maven("https://jitpack.io") } dependencies { - classpath("com.android.tools.build:gradle:7.4.2") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22") - classpath("com.google.devtools.ksp:symbol-processing-gradle-plugin:1.8.22-1.0.11") - classpath("com.github.iwhys:sdk-editor-plugin:1.1.7") - classpath("com.github.LianjiaTech:gson-plugin:2.1.0") + 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("dev.rikka.tools.materialthemebuilder:gradle-plugin:1.4.0") } } @@ -20,6 +18,7 @@ allprojects { repositories { google() maven("https://jitpack.io") + mavenCentral() } } 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..6236a209 --- /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']) + api 'androidx.appcompat:appcompat:1.1.0' +} 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/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/gradle.properties b/gradle.properties index 9eb5a4ae..f268b35b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,9 @@ org.gradle.parallel=true org.gradle.caching=true org.gradle.configureondemand=true +android.enableJetifier=true android.useAndroidX=true android.nonTransitiveRClass=true +android.enableR8.fullMode=false kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 98debb84..84a0b92f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists 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 +}