? = null,
diff --git a/app/src/main/java/jp/juggler/subwaytooter/global/AppDatabaseHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/global/AppDatabaseHolder.kt
index 9f8a58e6..c0987d92 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/global/AppDatabaseHolder.kt
+++ b/app/src/main/java/jp/juggler/subwaytooter/global/AppDatabaseHolder.kt
@@ -59,11 +59,11 @@ import jp.juggler.util.LogCategory
// 2021/5/11 59=>60 SavedAccountテーブルに項目追加
// 2021/5/23 60=>61 SavedAccountテーブルに項目追加
-private const val DB_VERSION = 61
-private const val DB_NAME = "app_db"
+const val DB_VERSION = 61
+const val DB_NAME = "app_db"
private val log = LogCategory("AppDatabase")
-val tables = arrayOf(
+val TABLE_LIST = arrayOf(
LogData,
SavedAccount,
ClientInfo,
@@ -87,13 +87,13 @@ private class DBOpenHelper(context: Context) :
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
- for (ti in tables) {
+ for (ti in TABLE_LIST) {
ti.onDBCreate(db)
}
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
- for (ti in tables) {
+ for (ti in TABLE_LIST) {
ti.onDBUpgrade(db, oldVersion, newVersion)
}
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolder.kt
index 24846729..65807cb8 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolder.kt
+++ b/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolder.kt
@@ -17,7 +17,6 @@ import androidx.appcompat.widget.AppCompatButton
import androidx.core.content.ContextCompat
import com.google.android.flexbox.JustifyContent
import jp.juggler.subwaytooter.ActMain
-import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.TimelineItem
import jp.juggler.subwaytooter.api.entity.TootAccountRef
diff --git a/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolderActions.kt b/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolderActions.kt
index 3afa6940..7e985388 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolderActions.kt
+++ b/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolderActions.kt
@@ -3,7 +3,6 @@ package jp.juggler.subwaytooter.itemviewholder
import android.view.View
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.ActMediaViewer
-import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.action.*
import jp.juggler.subwaytooter.actmain.nextPosition
import jp.juggler.subwaytooter.api.entity.*
diff --git a/app/src/main/java/jp/juggler/subwaytooter/mfm/SpanOutputEnv.kt b/app/src/main/java/jp/juggler/subwaytooter/mfm/SpanOutputEnv.kt
index a72ccd9d..4e7d0867 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/mfm/SpanOutputEnv.kt
+++ b/app/src/main/java/jp/juggler/subwaytooter/mfm/SpanOutputEnv.kt
@@ -4,11 +4,10 @@ import android.content.Context
import android.text.Spanned
import android.text.style.RelativeSizeSpan
import jp.juggler.subwaytooter.ActMain
-import jp.juggler.subwaytooter.App1
-import jp.juggler.subwaytooter.pref.PrefB
import jp.juggler.subwaytooter.api.entity.Acct
import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.TootMention
+import jp.juggler.subwaytooter.pref.PrefB
import jp.juggler.subwaytooter.span.HighlightSpan
import jp.juggler.subwaytooter.span.LinkInfo
import jp.juggler.subwaytooter.span.MyClickableSpan
diff --git a/app/src/main/java/jp/juggler/subwaytooter/pref/PrefB.kt b/app/src/main/java/jp/juggler/subwaytooter/pref/PrefB.kt
index 987c8032..fa8378de 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/pref/PrefB.kt
+++ b/app/src/main/java/jp/juggler/subwaytooter/pref/PrefB.kt
@@ -1,6 +1,5 @@
package jp.juggler.subwaytooter.pref
-import android.os.Build
import jp.juggler.subwaytooter.pref.impl.BooleanPref
object PrefB {
diff --git a/app/src/main/java/jp/juggler/subwaytooter/pref/PrefI.kt b/app/src/main/java/jp/juggler/subwaytooter/pref/PrefI.kt
index acf78de0..1654ff8f 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/pref/PrefI.kt
+++ b/app/src/main/java/jp/juggler/subwaytooter/pref/PrefI.kt
@@ -1,7 +1,6 @@
package jp.juggler.subwaytooter.pref
import android.graphics.Color
-import jp.juggler.subwaytooter.drawable.MediaBackgroundDrawable
import jp.juggler.subwaytooter.itemviewholder.AdditionalButtonsPosition
import jp.juggler.subwaytooter.pref.impl.IntPref
@@ -115,5 +114,5 @@ object PrefI {
// const val TTCS_WEEKLY = 0
// const val TTCS_DAILY = 1
- val ipMediaBackground = IntPref("MediaBackground", 1 )
+ val ipMediaBackground = IntPref("MediaBackground", 1)
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/span/SvgEmojiSpan.kt b/app/src/main/java/jp/juggler/subwaytooter/span/SvgEmojiSpan.kt
index 14fcb527..385aaf85 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/span/SvgEmojiSpan.kt
+++ b/app/src/main/java/jp/juggler/subwaytooter/span/SvgEmojiSpan.kt
@@ -8,7 +8,6 @@ import android.text.style.ReplacementSpan
import androidx.annotation.IntRange
import com.caverock.androidsvg.SVG
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
-import jp.juggler.subwaytooter.pref.PrefB
import jp.juggler.util.LogCategory
// 絵文字リソースの種類によって異なるスパンを作る
diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt
index 69ec2c44..ac054579 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt
+++ b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt
@@ -6,11 +6,10 @@ import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.*
-import jp.juggler.subwaytooter.App1
-import jp.juggler.subwaytooter.pref.PrefB
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.mfm.MisskeyMarkdownDecoder
+import jp.juggler.subwaytooter.pref.PrefB
import jp.juggler.subwaytooter.span.*
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.HighlightWord
@@ -469,7 +468,13 @@ object HTMLDecoder {
else -> false
}
- fun canSkipEncode(isBlockParent: Boolean, curr: Node, parent: Node, prev: Node?, next: Node?) = when {
+ fun canSkipEncode(
+ isBlockParent: Boolean,
+ curr: Node,
+ parent: Node,
+ prev: Node?,
+ next: Node?
+ ) = when {
!isBlockParent -> false
curr.tag != TAG_TEXT -> false
curr.text.isNotBlank() -> false
@@ -504,7 +509,10 @@ object HTMLDecoder {
) {
sb.append(options.decodeEmoji(title))
return
- } else if (cssClass == "emoji" && url != null && alt != null && reNotestockEmojiAlt.matches(alt)) {
+ } else if (cssClass == "emoji" && url != null && alt != null && reNotestockEmojiAlt.matches(
+ alt
+ )
+ ) {
// notestock custom emoji
sb.run {
val start = length
@@ -529,7 +537,15 @@ object HTMLDecoder {
sb.append(caption.notEmpty() ?: url)
if (reUrlStart.find(url) != null) {
val span =
- MyClickableSpan(LinkInfo(url = url, ac = null, tag = null, caption = caption, mention = null))
+ MyClickableSpan(
+ LinkInfo(
+ url = url,
+ ac = null,
+ tag = null,
+ caption = caption,
+ mention = null
+ )
+ )
sb.setSpan(span, start, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
sb.append(" ")
@@ -549,7 +565,12 @@ object HTMLDecoder {
val originalFlusher: EncodeSpanEnv.() -> Unit = {
when (tag) {
"s", "strike", "del" -> {
- sb.setSpan(StrikethroughSpan(), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sb.setSpan(
+ StrikethroughSpan(),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
}
"em" -> {
sb.setSpan(
@@ -560,7 +581,12 @@ object HTMLDecoder {
)
}
"strong" -> {
- sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sb.setSpan(
+ StyleSpan(Typeface.BOLD),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
}
"tr" -> {
sb.append("|")
@@ -570,37 +596,122 @@ object HTMLDecoder {
// sb_tmpにレンダリングした分は読み捨てる
}
"h1" -> {
- sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- sb.setSpan(RelativeSizeSpan(1.8f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sb.setSpan(
+ StyleSpan(Typeface.BOLD),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ sb.setSpan(
+ RelativeSizeSpan(1.8f),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
}
"h2" -> {
- sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- sb.setSpan(RelativeSizeSpan(1.6f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sb.setSpan(
+ StyleSpan(Typeface.BOLD),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ sb.setSpan(
+ RelativeSizeSpan(1.6f),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
}
"h3" -> {
- sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- sb.setSpan(RelativeSizeSpan(1.4f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sb.setSpan(
+ StyleSpan(Typeface.BOLD),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ sb.setSpan(
+ RelativeSizeSpan(1.4f),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
}
"h4" -> {
- sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- sb.setSpan(RelativeSizeSpan(1.2f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sb.setSpan(
+ StyleSpan(Typeface.BOLD),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ sb.setSpan(
+ RelativeSizeSpan(1.2f),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
}
"h5" -> {
- sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- sb.setSpan(RelativeSizeSpan(1.0f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sb.setSpan(
+ StyleSpan(Typeface.BOLD),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ sb.setSpan(
+ RelativeSizeSpan(1.0f),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
}
"h6" -> {
- sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- sb.setSpan(RelativeSizeSpan(0.8f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sb.setSpan(
+ StyleSpan(Typeface.BOLD),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ sb.setSpan(
+ RelativeSizeSpan(0.8f),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
}
"pre" -> {
- sb.setSpan(BackgroundColorSpan(0x40808080), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- sb.setSpan(RelativeSizeSpan(0.7f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- sb.setSpan(fontSpan(Typeface.MONOSPACE), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sb.setSpan(
+ BackgroundColorSpan(0x40808080),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ sb.setSpan(
+ RelativeSizeSpan(0.7f),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ sb.setSpan(
+ fontSpan(Typeface.MONOSPACE),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
}
"code" -> {
- sb.setSpan(BackgroundColorSpan(0x40808080), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- sb.setSpan(fontSpan(Typeface.MONOSPACE), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sb.setSpan(
+ BackgroundColorSpan(0x40808080),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ sb.setSpan(
+ fontSpan(Typeface.MONOSPACE),
+ spanStart,
+ sb.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
}
"hr" -> sb.append("----------")
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/ProgressResponseBody.kt b/app/src/main/java/jp/juggler/subwaytooter/util/ProgressResponseBody.kt
index d94b6cdd..79c0003f 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/util/ProgressResponseBody.kt
+++ b/app/src/main/java/jp/juggler/subwaytooter/util/ProgressResponseBody.kt
@@ -29,19 +29,15 @@ class ProgressResponseBody private constructor(
internal val log = LogCategory("ProgressResponseBody")
- // please append this for OkHttpClient.Builder#addInterceptor().
- // ex) builder.addInterceptor( ProgressResponseBody.makeInterceptor() );
- fun makeInterceptor(): Interceptor = object : Interceptor {
- override fun intercept(chain: Interceptor.Chain): Response {
- val originalResponse = chain.proceed(chain.request())
+ fun makeInterceptor(): Interceptor = Interceptor { chain ->
+ val originalResponse = chain.proceed(chain.request())
- val originalBody = originalResponse.body
- ?: error("makeInterceptor: originalResponse.body() returns null.")
+ val originalBody = originalResponse.body
+ ?: error("makeInterceptor: originalResponse.body() returns null.")
- return originalResponse.newBuilder()
- .body(ProgressResponseBody(originalBody))
- .build()
- }
+ originalResponse.newBuilder()
+ .body(ProgressResponseBody(originalBody))
+ .build()
}
@Throws(IOException::class)
diff --git a/app/src/main/java/jp/juggler/util/Json.kt b/app/src/main/java/jp/juggler/util/Json.kt
index c4a21452..71f8b2a9 100644
--- a/app/src/main/java/jp/juggler/util/Json.kt
+++ b/app/src/main/java/jp/juggler/util/Json.kt
@@ -12,7 +12,7 @@ class JsonException : RuntimeException {
private const val CHAR0 = '\u0000'
-private val reDecimal = """(?:\A\-0\z)|[.eE]""".toRegex()
+private val reDecimal = """(?:\A-0\z)|[.eE]""".toRegex()
// Tests if the value should be tried as a decimal.
// It makes no test if there are actual digits.
diff --git a/app/src/main/java/me/drakeet/support/toast/ToastCompat.kt b/app/src/main/java/me/drakeet/support/toast/ToastCompat.kt
index dc8321f2..a58925c6 100644
--- a/app/src/main/java/me/drakeet/support/toast/ToastCompat.kt
+++ b/app/src/main/java/me/drakeet/support/toast/ToastCompat.kt
@@ -92,6 +92,7 @@ class ToastCompat private constructor(
companion object {
+ @SuppressLint("DiscouragedPrivateApi")
private fun setContextCompat(view: View?, contextCreator: () -> Context) {
if (view != null && Build.VERSION.SDK_INT == 25) {
try {
diff --git a/colorpicker/build.gradle b/colorpicker/build.gradle
index 7b8f3126..564db643 100644
--- a/colorpicker/build.gradle
+++ b/colorpicker/build.gradle
@@ -13,9 +13,9 @@ android {
targetSdkVersion target_sdk_version
minSdkVersion min_sdk_version
vectorDrawables.useSupportLibrary = true
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
-
lintOptions {
abortOnError false
}
@@ -39,8 +39,12 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation 'com.google.android.flexbox:flexbox:3.0.0'
-}
-//apply plugin: 'com.getkeepsafe.dexcount'
-//apply from: 'https://raw.githubusercontent.com/jaredrummler/android-artifact-push/master/artifactory/publication.gradle'
-//apply from: 'https://raw.githubusercontent.com/jaredrummler/android-artifact-push/master/maven/gradle-mvn-push.gradle'
\ No newline at end of file
+ testImplementation "junit:junit:$junit_version"
+
+ androidTestImplementation "androidx.test:core:1.4.0"
+
+ androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0-alpha4', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/AlphaPatternDrawable.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/AlphaPatternDrawable.java
deleted file mode 100644
index 6eeb6877..00000000
--- a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/AlphaPatternDrawable.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2017 JRummy Apps Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.jrummyapps.android.colorpicker;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-
-import androidx.annotation.NonNull;
-
-/**
- * This drawable will draw a simple white and gray chessboard pattern.
- * It's the pattern you will often see as a background behind a partly transparent image in many applications.
- */
-class AlphaPatternDrawable extends Drawable {
-
- private final int rectangleSize;
-
- private final Paint paint = new Paint();
- private final Paint paintWhite = new Paint();
- private final Paint paintGray = new Paint();
-
- private int numRectanglesHorizontal;
- private int numRectanglesVertical;
-
- /**
- * Bitmap in which the pattern will be cached.
- * This is so the pattern will not have to be recreated each time draw() gets called.
- * Because recreating the pattern i rather expensive. I will only be recreated if the size changes.
- */
- private Bitmap bitmap;
-
- AlphaPatternDrawable(int rectangleSize) {
- this.rectangleSize = rectangleSize;
- paintWhite.setColor(0xFFFFFFFF);
- paintGray.setColor(0xFFCBCBCB);
- }
-
- @Override
- public void draw(@NonNull Canvas canvas) {
- if (bitmap != null && !bitmap.isRecycled()) {
- canvas.drawBitmap(bitmap, null, getBounds(), paint);
- }
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.UNKNOWN;
- }
-
- @Override
- public void setAlpha(int alpha) {
- throw new UnsupportedOperationException("Alpha is not supported by this drawable.");
- }
-
- @Override
- public void setColorFilter(ColorFilter cf) {
- throw new UnsupportedOperationException("ColorFilter is not supported by this drawable.");
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- super.onBoundsChange(bounds);
- int height = bounds.height();
- int width = bounds.width();
- numRectanglesHorizontal = (int) Math.ceil(width / (float) rectangleSize);
- numRectanglesVertical = (int) Math.ceil(height / (float) rectangleSize);
- generatePatternBitmap();
- }
-
- /**
- * This will generate a bitmap with the pattern as big as the rectangle we were allow to draw on.
- * We do this to chache the bitmap so we don't need to recreate it each time draw() is called since it takes a few milliseconds
- */
- private void generatePatternBitmap() {
- if (getBounds().width() <= 0 || getBounds().height() <= 0) {
- return;
- }
-
- bitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
-
- Rect r = new Rect();
- boolean verticalStartWhite = true;
- for (int i = 0; i <= numRectanglesVertical; i++) {
- boolean isWhite = verticalStartWhite;
- for (int j = 0; j <= numRectanglesHorizontal; j++) {
- r.top = i * rectangleSize;
- r.left = j * rectangleSize;
- r.bottom = r.top + rectangleSize;
- r.right = r.left + rectangleSize;
- canvas.drawRect(r, isWhite ? paintWhite : paintGray);
- isWhite = !isWhite;
- }
- verticalStartWhite = !verticalStartWhite;
- }
- }
-
-}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/AlphaPatternDrawable.kt b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/AlphaPatternDrawable.kt
new file mode 100644
index 00000000..437bb8bf
--- /dev/null
+++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/AlphaPatternDrawable.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 JRummy Apps Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jrummyapps.android.colorpicker
+
+import android.graphics.*
+import android.graphics.drawable.Drawable
+import kotlin.math.ceil
+
+/**
+ * This drawable will draw a simple white and gray chessboard pattern.
+ * It's the pattern you will often see as a background behind a partly transparent image in many applications.
+ */
+internal class AlphaPatternDrawable(private val rectangleSize: Int) : Drawable() {
+ private val paint = Paint()
+ private val paintWhite = Paint().apply { color = Color.WHITE }
+ private val paintGray = Paint().apply { color = -0x343434 }
+ private var numRectanglesHorizontal = 0
+ private var numRectanglesVertical = 0
+
+ /**
+ * Bitmap in which the pattern will be cached.
+ * This is so the pattern will not have to be recreated each time draw() gets called.
+ * Because recreating the pattern i rather expensive. I will only be recreated if the size changes.
+ */
+ private var bitmap: Bitmap? = null
+
+
+ /**
+ * This will generate a bitmap with the pattern as big as the rectangle we were allow to draw on.
+ * We do this to chache the bitmap so we don't need to recreate it each time draw() is called since it takes a few milliseconds
+ */
+ private fun generatePatternBitmap() {
+ if (bounds.width() <= 0 || bounds.height() <= 0) {
+ return
+ }
+ val bitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888)
+ .also { this.bitmap = it }
+ val canvas = Canvas(bitmap)
+ val r = Rect()
+ var verticalStartWhite = true
+ for (i in 0..numRectanglesVertical) {
+ var isWhite = verticalStartWhite
+ for (j in 0..numRectanglesHorizontal) {
+ r.top = i * rectangleSize
+ r.left = j * rectangleSize
+ r.bottom = r.top + rectangleSize
+ r.right = r.left + rectangleSize
+ canvas.drawRect(r, if (isWhite) paintWhite else paintGray)
+ isWhite = !isWhite
+ }
+ verticalStartWhite = !verticalStartWhite
+ }
+ }
+
+ override fun draw(canvas: Canvas) {
+ val bitmap = this.bitmap
+ if (bitmap != null && !bitmap.isRecycled) {
+ canvas.drawBitmap(bitmap, null, bounds, paint)
+ }
+ }
+
+ override fun getOpacity(): Int {
+ return PixelFormat.UNKNOWN
+ }
+
+ override fun setAlpha(alpha: Int) {
+ throw UnsupportedOperationException("Alpha is not supported by this drawable.")
+ }
+
+ override fun setColorFilter(cf: ColorFilter?) {
+ throw UnsupportedOperationException("ColorFilter is not supported by this drawable.")
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+ val height = bounds.height()
+ val width = bounds.width()
+ numRectanglesHorizontal = ceil((width / rectangleSize.toFloat()).toDouble()).toInt()
+ numRectanglesVertical = ceil((height / rectangleSize.toFloat()).toDouble()).toInt()
+ generatePatternBitmap()
+ }
+}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPaletteAdapter.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPaletteAdapter.java
deleted file mode 100644
index 57dd699d..00000000
--- a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPaletteAdapter.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2017 JRummy Apps Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.jrummyapps.android.colorpicker;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ImageView;
-
-import androidx.core.graphics.ColorUtils;
-
-class ColorPaletteAdapter extends BaseAdapter {
-
- /*package*/ final OnColorSelectedListener listener;
- /*package*/ final int[] colors;
- /*package*/ int selectedPosition;
- /*package*/ final int colorShape;
-
- ColorPaletteAdapter(OnColorSelectedListener listener,
- int[] colors,
- int selectedPosition,
- @ColorShape int colorShape) {
- this.listener = listener;
- this.colors = colors;
- this.selectedPosition = selectedPosition;
- this.colorShape = colorShape;
- }
-
- @Override
- public int getCount() {
- return colors.length;
- }
-
- @Override
- public Object getItem(int position) {
- return colors[position];
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final ViewHolder holder;
- if (convertView == null) {
- holder = new ViewHolder(parent.getContext());
- convertView = holder.view;
- } else {
- holder = (ViewHolder) convertView.getTag();
- }
- holder.setup(position);
- return convertView;
- }
-
- void selectNone() {
- selectedPosition = -1;
- notifyDataSetChanged();
- }
-
- interface OnColorSelectedListener {
-
- void onColorSelected(int color);
- }
-
- private final class ViewHolder {
-
- final View view;
- final ColorPanelView colorPanelView;
- final ImageView imageView;
- final int originalBorderColor;
-
- ViewHolder(Context context) {
- int layoutResId;
- if (colorShape == ColorShape.SQUARE) {
- layoutResId = R.layout.cpv_color_item_square;
- } else {
- layoutResId = R.layout.cpv_color_item_circle;
- }
- view = View.inflate(context, layoutResId, null);
- colorPanelView = view.findViewById(R.id.cpv_color_panel_view);
- imageView = view.findViewById(R.id.cpv_color_image_view);
- originalBorderColor = colorPanelView.getBorderColor();
- view.setTag(this);
- }
-
- void setup(int position) {
- int color = colors[position];
- int alpha = Color.alpha(color);
- colorPanelView.setColor(color);
- imageView.setImageResource(selectedPosition == position ? R.drawable.cpv_preset_checked : 0);
- if (alpha != 255) {
- if (alpha <= ColorPickerDialog.ALPHA_THRESHOLD) {
- colorPanelView.setBorderColor(color | 0xFF000000);
- imageView.setColorFilter(/*color | 0xFF000000*/Color.BLACK, PorterDuff.Mode.SRC_IN);
- } else {
- colorPanelView.setBorderColor(originalBorderColor);
- imageView.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);
- }
- } else {
- setColorFilter(position);
- }
- setOnClickListener(position);
- }
-
- private void setOnClickListener(final int position) {
- colorPanelView.setOnClickListener(v -> {
- if (selectedPosition != position) {
- selectedPosition = position;
- notifyDataSetChanged();
- }
- listener.onColorSelected(colors[position]);
- });
- colorPanelView.setOnLongClickListener(v -> {
- colorPanelView.showHint();
- return true;
- });
- }
-
- private void setColorFilter(int position) {
- if (position == selectedPosition && ColorUtils.calculateLuminance(colors[position]) >= 0.65) {
- imageView.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN);
- } else {
- imageView.setColorFilter(null);
- }
- }
-
- }
-
-}
\ No newline at end of file
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPaletteAdapter.kt b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPaletteAdapter.kt
new file mode 100644
index 00000000..4e9889ed
--- /dev/null
+++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPaletteAdapter.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 JRummy Apps Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jrummyapps.android.colorpicker
+
+import android.graphics.Color
+import android.graphics.PorterDuff
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseAdapter
+import android.widget.ImageView
+import androidx.core.graphics.ColorUtils
+
+internal class ColorPaletteAdapter(
+ val colors: IntArray,
+ var selectedPosition: Int,
+ @ColorShape val colorShape: Int,
+ val listener: (Int)->Unit
+) : BaseAdapter() {
+
+ override fun getCount(): Int = colors.size
+ override fun getItem(position: Int): Any = colors[position]
+ override fun getItemId(position: Int): Long = position.toLong()
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup) =
+ (convertView?.tag as? ViewHolder ?: ViewHolder(parent))
+ .apply { bind(position) }
+ .root
+
+ fun selectNone() {
+ selectedPosition = -1
+ notifyDataSetChanged()
+ }
+
+ private inner class ViewHolder(parent: ViewGroup) {
+ val root: View = run {
+ LayoutInflater.from(parent.context).inflate(
+ if (colorShape == ColorShape.SQUARE) {
+ R.layout.cpv_color_item_square
+ } else {
+ R.layout.cpv_color_item_circle
+ },
+ parent,
+ false
+ ).apply { tag = this@ViewHolder }
+ }
+
+ val colorPanelView: ColorPanelView = root.findViewById(R.id.cpv_color_panel_view)
+
+ val imageView: ImageView = root.findViewById(R.id.cpv_color_image_view)
+
+ val originalBorderColor: Int = colorPanelView.borderColor
+
+ fun bind(position: Int) {
+ val color = colors[position]
+ val alpha = Color.alpha(color)
+ colorPanelView.color = color
+ imageView.setImageResource(
+ if (selectedPosition == position)
+ R.drawable.cpv_preset_checked
+ else
+ 0
+ )
+ when {
+ alpha == 255 -> {
+ if (position == selectedPosition && ColorUtils.calculateLuminance(colors[position]) >= 0.65) {
+ imageView.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN)
+ } else {
+ imageView.colorFilter = null
+ }
+ }
+ alpha <= ColorPickerDialog.ALPHA_THRESHOLD -> {
+ colorPanelView.borderColor = color or -0x1000000
+ imageView.setColorFilter( /*color | 0xFF000000*/Color.BLACK,
+ PorterDuff.Mode.SRC_IN
+ )
+ }
+ else -> {
+ colorPanelView.borderColor = originalBorderColor
+ imageView.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
+ }
+ }
+
+ colorPanelView.setOnClickListener {
+ if (selectedPosition != position) {
+ selectedPosition = position
+ notifyDataSetChanged()
+ }
+ listener(colors[position])
+ }
+ colorPanelView.setOnLongClickListener {
+ colorPanelView.showHint()
+ true
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPanelView.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPanelView.java
deleted file mode 100644
index 3567d73c..00000000
--- a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPanelView.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2017 JRummy Apps Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.jrummyapps.android.colorpicker;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Shader;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Parcelable;
-import androidx.annotation.ColorInt;
-import androidx.core.view.GravityCompat;
-import androidx.core.view.ViewCompat;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.Toast;
-import java.util.Locale;
-
-/**
- * This class draws a panel which which will be filled with a color which can be set. It can be used to show the
- * currently selected color which you will get from the {@link ColorPickerView}.
- */
-public class ColorPanelView extends View {
-
- private final static int DEFAULT_BORDER_COLOR = 0xFF6E6E6E;
-
- private Drawable alphaPattern;
- private Paint borderPaint;
- private Paint colorPaint;
- private Paint alphaPaint;
- private Paint originalPaint;
- private Rect drawingRect;
- private Rect colorRect;
- private RectF centerRect = new RectF();
- private boolean showOldColor;
-
- /* The width in pixels of the border surrounding the color panel. */
- private int borderWidthPx;
- private int borderColor = DEFAULT_BORDER_COLOR;
- private int color = Color.BLACK;
- private int shape;
-
- public ColorPanelView(Context context) {
- this(context, null);
- }
-
- public ColorPanelView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ColorPanelView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context, attrs);
- }
-
- @Override public Parcelable onSaveInstanceState() {
- Bundle state = new Bundle();
- state.putParcelable("instanceState", super.onSaveInstanceState());
- state.putInt("color", color);
- return state;
- }
-
- @Override public void onRestoreInstanceState(Parcelable state) {
- if (state instanceof Bundle) {
- Bundle bundle = (Bundle) state;
- color = bundle.getInt("color");
- state = bundle.getParcelable("instanceState");
- }
- super.onRestoreInstanceState(state);
- }
-
- private void init(Context context, AttributeSet attrs) {
- TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPanelView);
- shape = a.getInt(R.styleable.ColorPanelView_cpv_colorShape, ColorShape.CIRCLE);
- showOldColor = a.getBoolean(R.styleable.ColorPanelView_cpv_showOldColor, false);
- if (showOldColor && shape != ColorShape.CIRCLE) {
- throw new IllegalStateException("Color preview is only available in circle mode");
- }
- borderColor = a.getColor(R.styleable.ColorPanelView_cpv_borderColor, DEFAULT_BORDER_COLOR);
- a.recycle();
- if (borderColor == DEFAULT_BORDER_COLOR) {
- // If no specific border color has been set we take the default secondary text color as border/slider color.
- // Thus it will adopt to theme changes automatically.
- final TypedValue value = new TypedValue();
- TypedArray typedArray = context.obtainStyledAttributes(value.data, new int[]{android.R.attr.textColorSecondary});
- borderColor = typedArray.getColor(0, borderColor);
- typedArray.recycle();
- }
- borderWidthPx = DrawingUtils.dpToPx(context, 1);
- borderPaint = new Paint();
- borderPaint.setAntiAlias(true);
- colorPaint = new Paint();
- colorPaint.setAntiAlias(true);
- if (showOldColor) {
- originalPaint = new Paint();
- }
- if (shape == ColorShape.CIRCLE) {
- Bitmap bitmap = ((BitmapDrawable) context.getResources().getDrawable(R.drawable.cpv_alpha)).getBitmap();
- alphaPaint = new Paint();
- alphaPaint.setAntiAlias(true);
- BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
- alphaPaint.setShader(shader);
- }
- }
-
- @Override protected void onDraw(Canvas canvas) {
- borderPaint.setColor(borderColor);
- colorPaint.setColor(color);
- if (shape == ColorShape.SQUARE) {
- if (borderWidthPx > 0) {
- canvas.drawRect(drawingRect, borderPaint);
- }
- if (alphaPattern != null) {
- alphaPattern.draw(canvas);
- }
- canvas.drawRect(colorRect, colorPaint);
- } else if (shape == ColorShape.CIRCLE) {
- final int outerRadius = getMeasuredWidth() / 2;
- if (borderWidthPx > 0) {
- canvas.drawCircle(getMeasuredWidth() / 2f,
- getMeasuredHeight() / 2f,
- outerRadius,
- borderPaint);
- }
- if (Color.alpha(color) < 255) {
- canvas.drawCircle(getMeasuredWidth() / 2f,
- getMeasuredHeight() / 2f,
- outerRadius - borderWidthPx, alphaPaint);
- }
- if (showOldColor) {
- canvas.drawArc(centerRect, 90, 180, true, originalPaint);
- canvas.drawArc(centerRect, 270, 180, true, colorPaint);
- } else {
- canvas.drawCircle(getMeasuredWidth() / 2f,
- getMeasuredHeight() / 2f,
- outerRadius - borderWidthPx,
- colorPaint);
- }
- }
- }
-
- @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (shape == ColorShape.SQUARE) {
- int width = MeasureSpec.getSize(widthMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
- setMeasuredDimension(width, height);
- } else if (shape == ColorShape.CIRCLE) {
- //noinspection SuspiciousNameCombination
- super.onMeasure(widthMeasureSpec, widthMeasureSpec);
- setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
- } else {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- }
-
- @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- if (shape == ColorShape.SQUARE || showOldColor) {
- drawingRect = new Rect();
- drawingRect.left = getPaddingLeft();
- drawingRect.right = w - getPaddingRight();
- drawingRect.top = getPaddingTop();
- drawingRect.bottom = h - getPaddingBottom();
- if (showOldColor) {
- setUpCenterRect();
- } else {
- setUpColorRect();
- }
- }
- }
-
- private void setUpCenterRect() {
- final Rect dRect = drawingRect;
- int left = dRect.left + borderWidthPx;
- int top = dRect.top + borderWidthPx;
- int bottom = dRect.bottom - borderWidthPx;
- int right = dRect.right - borderWidthPx;
- centerRect = new RectF(left, top, right, bottom);
- }
-
- private void setUpColorRect() {
- final Rect dRect = drawingRect;
- int left = dRect.left + borderWidthPx;
- int top = dRect.top + borderWidthPx;
- int bottom = dRect.bottom - borderWidthPx;
- int right = dRect.right - borderWidthPx;
- colorRect = new Rect(left, top, right, bottom);
- alphaPattern = new AlphaPatternDrawable(DrawingUtils.dpToPx(getContext(), 4));
- alphaPattern.setBounds(Math.round(colorRect.left),
- Math.round(colorRect.top),
- Math.round(colorRect.right),
- Math.round(colorRect.bottom));
- }
-
- /**
- * Set the color that should be shown by this view.
- *
- * @param color
- * the color value
- */
- public void setColor(int color) {
- this.color = color;
- invalidate();
- }
-
- /**
- * Get the color currently show by this view.
- *
- * @return the color value
- */
- public int getColor() {
- return color;
- }
-
- /**
- * Set the original color. This is only used for previewing colors.
- *
- * @param color
- * The original color
- */
- public void setOriginalColor(@ColorInt int color) {
- if (originalPaint != null) {
- originalPaint.setColor(color);
- }
- }
-
- /**
- * Set the color of the border surrounding the panel.
- *
- * @param color
- * the color value
- */
- public void setBorderColor(int color) {
- borderColor = color;
- invalidate();
- }
-
- /**
- * @return the color of the border surrounding the panel.
- */
- public int getBorderColor() {
- return borderColor;
- }
-
- /**
- * Set the shape.
- *
- * @param shape
- * Either {@link ColorShape#SQUARE} or {@link ColorShape#CIRCLE}.
- */
- public void setShape(@ColorShape int shape) {
- this.shape = shape;
- invalidate();
- }
-
- /**
- * Get the shape
- *
- * @return Either {@link ColorShape#SQUARE} or {@link ColorShape#CIRCLE}.
- */
- @ColorShape public int getShape() {
- return shape;
- }
-
- /**
- * Show a toast message with the hex color code below the view.
- */
- public void showHint() {
- final int[] screenPos = new int[2];
- final Rect displayFrame = new Rect();
- getLocationOnScreen(screenPos);
- getWindowVisibleDisplayFrame(displayFrame);
- final Context context = getContext();
- final int width = getWidth();
- final int height = getHeight();
- final int midy = screenPos[1] + height / 2;
- int referenceX = screenPos[0] + width / 2;
- if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR) {
- final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
- referenceX = screenWidth - referenceX; // mirror
- }
- StringBuilder hint = new StringBuilder("#");
- if (Color.alpha(color) != 255) {
- hint.append(Integer.toHexString(color).toUpperCase(Locale.ENGLISH));
- } else {
- hint.append(String.format("%06X", 0xFFFFFF & color).toUpperCase(Locale.ENGLISH));
- }
- Toast cheatSheet = Toast.makeText(context, hint.toString(), Toast.LENGTH_SHORT);
- if (midy < displayFrame.height()) {
- // Show along the top; follow action buttons
- cheatSheet.setGravity(Gravity.TOP | GravityCompat.END, referenceX,
- screenPos[1] + height - displayFrame.top);
- } else {
- // Show along the bottom center
- cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
- }
- cheatSheet.show();
- }
-
-}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPanelView.kt b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPanelView.kt
new file mode 100644
index 00000000..5e5d3081
--- /dev/null
+++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPanelView.kt
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2017 JRummy Apps Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jrummyapps.android.colorpicker
+
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.BitmapDrawable
+import android.os.Bundle
+import android.os.Parcelable
+import android.util.AttributeSet
+import android.util.TypedValue
+import android.view.Gravity
+import android.view.View
+import android.widget.Toast
+import androidx.annotation.ColorInt
+import androidx.core.content.ContextCompat
+import androidx.core.view.GravityCompat
+import androidx.core.view.ViewCompat
+
+/**
+ * This class draws a panel which which will be filled with a color which can be set. It can be used to show the
+ * currently selected color which you will get from the [ColorPickerView].
+ */
+class ColorPanelView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+) : View(context, attrs, defStyle) {
+
+ companion object {
+ private const val DEFAULT_BORDER_COLOR = -0x919192
+ }
+
+ /* The width in pixels of the border surrounding the color panel. */
+ private val borderWidthPx = DrawingUtils.dpToPx(context, 1f)
+
+ private val borderPaint = Paint().apply {
+ isAntiAlias = true
+ }
+
+ private val colorPaint = Paint().apply {
+ isAntiAlias = true
+ }
+
+ private val alphaPaint = Paint().apply {
+ val bitmap = (ContextCompat.getDrawable(context, R.drawable.cpv_alpha) as BitmapDrawable)
+ .bitmap
+ shader = BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
+ isAntiAlias = true
+ }
+
+ private val originalPaint = Paint()
+
+ private val centerRect = RectF()
+ private var drawingRect = Rect()
+ private var colorRect = Rect()
+
+ private var alphaPattern = AlphaPatternDrawable(DrawingUtils.dpToPx(context, 4f))
+
+ private var showOldColor = false
+
+ var borderColor = DEFAULT_BORDER_COLOR
+ set(value) {
+ if (field != value) {
+ field = value
+ invalidate()
+ }
+ }
+
+ var color = Color.BLACK
+ set(value) {
+ if (field != value) {
+ field = value
+ invalidate()
+ }
+ }
+
+ @ColorShape
+ private var shape = 0
+ set(value) {
+ if (field != value) {
+ field = value
+ invalidate()
+ }
+ }
+
+
+ init {
+ val a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPanelView)
+ shape = a.getInt(R.styleable.ColorPanelView_cpv_colorShape, ColorShape.CIRCLE)
+ showOldColor = a.getBoolean(R.styleable.ColorPanelView_cpv_showOldColor, false)
+ check(!(showOldColor && shape != ColorShape.CIRCLE)) { "Color preview is only available in circle mode" }
+ borderColor = a.getColor(R.styleable.ColorPanelView_cpv_borderColor, DEFAULT_BORDER_COLOR)
+ a.recycle()
+
+ if (borderColor == DEFAULT_BORDER_COLOR) {
+ // If no specific border color has been set we take the default secondary text color as border/slider color.
+ // Thus it will adopt to theme changes automatically.
+ val value = TypedValue()
+ val typedArray = context.obtainStyledAttributes(
+ value.data,
+ intArrayOf(android.R.attr.textColorSecondary)
+ )
+ borderColor = typedArray.getColor(0, borderColor)
+ typedArray.recycle()
+ }
+
+
+ }
+
+
+ public override fun onSaveInstanceState(): Parcelable {
+ val state = Bundle()
+ state.putParcelable("instanceState", super.onSaveInstanceState())
+ state.putInt("color", color)
+ return state
+ }
+
+ public override fun onRestoreInstanceState(state: Parcelable) {
+ if (state is Bundle) {
+ color = state.getInt("color")
+ super.onRestoreInstanceState(state.getParcelable("instanceState"))
+ } else {
+ super.onRestoreInstanceState(state)
+ }
+ }
+
+
+ override fun onDraw(canvas: Canvas) {
+ borderPaint.color = borderColor
+ colorPaint.color = color
+ if (shape == ColorShape.SQUARE) {
+ if (borderWidthPx > 0) {
+ canvas.drawRect(drawingRect, borderPaint)
+ }
+ alphaPattern.draw(canvas)
+ canvas.drawRect(colorRect, colorPaint)
+ } else if (shape == ColorShape.CIRCLE) {
+ val outerRadius = measuredWidth / 2
+ if (borderWidthPx > 0) {
+ canvas.drawCircle(
+ measuredWidth / 2f,
+ measuredHeight / 2f,
+ outerRadius.toFloat(),
+ borderPaint
+ )
+ }
+ if (Color.alpha(color) < 255) {
+ canvas.drawCircle(
+ measuredWidth / 2f,
+ measuredHeight / 2f, (
+ outerRadius - borderWidthPx).toFloat(), alphaPaint
+ )
+ }
+ if (showOldColor) {
+ canvas.drawArc(centerRect, 90f, 180f, true, originalPaint)
+ canvas.drawArc(centerRect, 270f, 180f, true, colorPaint)
+ } else {
+ canvas.drawCircle(
+ measuredWidth / 2f,
+ measuredHeight / 2f, (
+ outerRadius - borderWidthPx).toFloat(),
+ colorPaint
+ )
+ }
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ when (shape) {
+ ColorShape.SQUARE -> {
+ val width = MeasureSpec.getSize(widthMeasureSpec)
+ val height = MeasureSpec.getSize(heightMeasureSpec)
+ setMeasuredDimension(width, height)
+ }
+ ColorShape.CIRCLE -> {
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec)
+ setMeasuredDimension(measuredWidth, measuredWidth)
+ }
+ else -> {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ }
+ }
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ if (shape == ColorShape.SQUARE || showOldColor) {
+ drawingRect.set(
+ paddingLeft,
+ paddingTop,
+ w - paddingRight,
+ h - paddingBottom
+ )
+ if (showOldColor) {
+ setUpCenterRect()
+ } else {
+ setUpColorRect()
+ }
+ }
+ }
+
+ private fun setUpCenterRect() {
+ val dRect = drawingRect
+ val left = dRect.left + borderWidthPx
+ val top = dRect.top + borderWidthPx
+ val bottom = dRect.bottom - borderWidthPx
+ val right = dRect.right - borderWidthPx
+ centerRect.set(
+ left.toFloat(),
+ top.toFloat(),
+ right.toFloat(),
+ bottom.toFloat()
+ )
+ }
+
+ private fun setUpColorRect() {
+ val left = drawingRect.left + borderWidthPx
+ val top = drawingRect.top + borderWidthPx
+ val bottom = drawingRect.bottom - borderWidthPx
+ val right = drawingRect.right - borderWidthPx
+ colorRect.set(left, top, right, bottom)
+ alphaPattern.setBounds(left, top, right, bottom)
+ }
+
+
+ /**
+ * Set the original color. This is only used for previewing colors.
+ *
+ * @param color
+ * The original color
+ */
+ fun setOriginalColor(@ColorInt color: Int) {
+ originalPaint.color = color
+ }
+
+ /**
+ * Show a toast message with the hex color code below the view.
+ */
+ fun showHint() {
+ val screenPos = IntArray(2)
+ val displayFrame = Rect()
+ getLocationOnScreen(screenPos)
+ getWindowVisibleDisplayFrame(displayFrame)
+ val context = context
+ val width = width
+ val height = height
+ val midy = screenPos[1] + height / 2
+ var referenceX = screenPos[0] + width / 2
+ if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR) {
+ val screenWidth = context.resources.displayMetrics.widthPixels
+ referenceX = screenWidth - referenceX // mirror
+ }
+ val hint = StringBuilder("#")
+ if (Color.alpha(color) != 255) {
+ hint.append(Integer.toHexString(color).uppercase())
+ } else {
+ hint.append(String.format("%06X", 0xFFFFFF and color).uppercase())
+ }
+ val cheatSheet = Toast.makeText(context, hint.toString(), Toast.LENGTH_SHORT)
+ if (midy < displayFrame.height()) {
+ // Show along the top; follow action buttons
+ cheatSheet.setGravity(
+ Gravity.TOP or GravityCompat.END, referenceX,
+ screenPos[1] + height - displayFrame.top
+ )
+ } else {
+ // Show along the bottom center
+ cheatSheet.setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL, 0, height)
+ }
+ cheatSheet.show()
+ }
+}
\ No newline at end of file
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialog.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialog.java
deleted file mode 100644
index 57e0e96a..00000000
--- a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialog.java
+++ /dev/null
@@ -1,906 +0,0 @@
-/*
- * Copyright (C) 2017 JRummy Apps Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.jrummyapps.android.colorpicker;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.InputFilter;
-import android.text.TextWatcher;
-import android.util.TypedValue;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.GridView;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.jrummyapps.android.colorpicker.ColorPickerView.OnColorChangedListener;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.Locale;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.graphics.ColorUtils;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentActivity;
-
-/**
- * A dialog to pick a color.
- *
- * The {@link Activity activity} that shows this dialog should implement {@link ColorPickerDialogListener}
- *
- * Example usage:
- *
- *
- * ColorPickerDialog.newBuilder().show(activity);
- *
- */
-public class ColorPickerDialog
- extends DialogFragment
- implements OnTouchListener, OnColorChangedListener, TextWatcher {
-
- private static final String ARG_ID = "id";
- private static final String ARG_TYPE = "dialogType";
- private static final String ARG_COLOR = "color";
- private static final String ARG_ALPHA = "alpha";
- private static final String ARG_PRESETS = "presets";
- private static final String ARG_ALLOW_PRESETS = "allowPresets";
- private static final String ARG_ALLOW_CUSTOM = "allowCustom";
- private static final String ARG_DIALOG_TITLE = "dialogTitle";
- private static final String ARG_SHOW_COLOR_SHADES = "showColorShades";
- private static final String ARG_COLOR_SHAPE = "colorShape";
-
- public static final int TYPE_CUSTOM = 0;
- public static final int TYPE_PRESETS = 1;
-
- static final int ALPHA_THRESHOLD = 165;
-
- /**
- * Material design colors used as the default color presets
- */
- public static final int[] MATERIAL_COLORS = {
- 0xFFF44336, // RED 500
- 0xFFE91E63, // PINK 500
- 0xFFFF2C93, // LIGHT PINK 500
- 0xFF9C27B0, // PURPLE 500
- 0xFF673AB7, // DEEP PURPLE 500
- 0xFF3F51B5, // INDIGO 500
- 0xFF2196F3, // BLUE 500
- 0xFF03A9F4, // LIGHT BLUE 500
- 0xFF00BCD4, // CYAN 500
- 0xFF009688, // TEAL 500
- 0xFF4CAF50, // GREEN 500
- 0xFF8BC34A, // LIGHT GREEN 500
- 0xFFCDDC39, // LIME 500
- 0xFFFFEB3B, // YELLOW 500
- 0xFFFFC107, // AMBER 500
- 0xFFFF9800, // ORANGE 500
- 0xFF795548, // BROWN 500
- 0xFF607D8B, // BLUE GREY 500
- 0xFF9E9E9E, // GREY 500
- };
-
- /**
- * Create a new Builder for creating a {@link ColorPickerDialog} instance
- *
- * @return The {@link Builder builder} to create the {@link ColorPickerDialog}.
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- ColorPickerDialogListener colorPickerDialogListener;
- FrameLayout rootView;
- int[] presets;
- @ColorInt
- int color;
- int dialogType;
- int dialogId;
- boolean showColorShades;
- int colorShape;
-
- // -- PRESETS --------------------------
- ColorPaletteAdapter adapter;
- LinearLayout shadesLayout;
- SeekBar transparencySeekBar;
- TextView transparencyPercText;
-
- // -- CUSTOM ---------------------------
- ColorPickerView colorPicker;
- ColorPanelView newColorPanel;
- EditText hexEditText;
- boolean showAlphaSlider;
- private boolean fromEditText;
-
- @Override
- public void onAttach(@NonNull Context context) {
- super.onAttach(context);
- if (colorPickerDialogListener == null && context instanceof ColorPickerDialogListener) {
- colorPickerDialogListener = (ColorPickerDialogListener) context;
- }
- }
-
- @NonNull
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- Bundle args = getArguments();
- if (args == null) throw new RuntimeException("onCreateDialog: args is null");
- Context context = getContext();
- if (context == null) throw new RuntimeException("onCreateDialog: context is null");
- Activity activity = getActivity();
- if (activity == null) throw new RuntimeException("onCreateDialog: activity is null");
-
- dialogId = args.getInt(ARG_ID);
- showAlphaSlider = args.getBoolean(ARG_ALPHA);
- showColorShades = args.getBoolean(ARG_SHOW_COLOR_SHADES);
- colorShape = args.getInt(ARG_COLOR_SHAPE);
- if (savedInstanceState == null) {
- color = args.getInt(ARG_COLOR);
- dialogType = args.getInt(ARG_TYPE);
- } else {
- color = savedInstanceState.getInt(ARG_COLOR);
- dialogType = savedInstanceState.getInt(ARG_TYPE);
- }
-
-
- rootView = new FrameLayout(activity);
- if (dialogType == TYPE_CUSTOM) {
- rootView.addView(createPickerView());
- } else if (dialogType == TYPE_PRESETS) {
- rootView.addView(createPresetsView());
- }
-
- AlertDialog.Builder builder = new AlertDialog.Builder(activity)
- .setView(rootView)
- .setPositiveButton(R.string.cpv_select, (dialog, which) ->
- colorPickerDialogListener.onColorSelected(dialogId, color));
-
- int dialogTitleStringRes = args.getInt(ARG_DIALOG_TITLE);
- if (dialogTitleStringRes != 0) {
- builder.setTitle(dialogTitleStringRes);
- }
-
- int neutralButtonStringRes;
- if (dialogType == TYPE_CUSTOM && args.getBoolean(ARG_ALLOW_PRESETS)) {
- neutralButtonStringRes = R.string.cpv_presets;
- } else if (dialogType == TYPE_PRESETS && args.getBoolean(ARG_ALLOW_CUSTOM)) {
- neutralButtonStringRes = R.string.cpv_custom;
- } else {
- neutralButtonStringRes = 0;
- }
-
- if (neutralButtonStringRes != 0) {
- builder.setNeutralButton(neutralButtonStringRes, null);
- }
-
- return builder.create();
- }
-
- @Override
- public void onStart() {
- super.onStart();
- AlertDialog dialog = (AlertDialog) getDialog();
-
- // http://stackoverflow.com/a/16972670/1048340
- //noinspection ConstantConditions
- dialog.getWindow()
- .clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
- WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
- dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
-
- // Do not dismiss the dialog when clicking the neutral button.
- Button neutralButton = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
- if (neutralButton != null) {
- neutralButton.setOnClickListener(v -> {
- rootView.removeAllViews();
- switch (dialogType) {
- case TYPE_CUSTOM:
- dialogType = TYPE_PRESETS;
- ((Button) v).setText(R.string.cpv_custom);
- rootView.addView(createPresetsView());
- break;
- case TYPE_PRESETS:
- dialogType = TYPE_CUSTOM;
- ((Button) v).setText(R.string.cpv_presets);
- rootView.addView(createPickerView());
- }
- });
- }
- }
-
- @Override
- public void onDismiss(@NonNull DialogInterface dialog) {
- super.onDismiss(dialog);
- colorPickerDialogListener.onDialogDismissed(dialogId);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- outState.putInt(ARG_COLOR, color);
- outState.putInt(ARG_TYPE, dialogType);
- super.onSaveInstanceState(outState);
- }
-
- /**
- * Set the callback
- *
- * @param colorPickerDialogListener The callback invoked when a color is selected or the dialog is dismissed.
- */
- public void setColorPickerDialogListener(ColorPickerDialogListener colorPickerDialogListener) {
- this.colorPickerDialogListener = colorPickerDialogListener;
- }
-
- // region Custom Picker
-
- View createPickerView() {
- Bundle args = getArguments();
- if (args == null) throw new RuntimeException("createPickerView: args is null");
- FragmentActivity activity = getActivity();
- if (activity == null) throw new RuntimeException("createPickerView: activity is null");
-
- View contentView = View.inflate(getActivity(), R.layout.cpv_dialog_color_picker, null);
- colorPicker = contentView.findViewById(R.id.cpv_color_picker_view);
- ColorPanelView oldColorPanel = contentView.findViewById(R.id.cpv_color_panel_old);
- newColorPanel = contentView.findViewById(R.id.cpv_color_panel_new);
- ImageView arrowRight = contentView.findViewById(R.id.cpv_arrow_right);
- hexEditText = contentView.findViewById(R.id.cpv_hex);
-
- try {
- final TypedValue value = new TypedValue();
- TypedArray typedArray = activity.obtainStyledAttributes(
- value.data,
- new int[]{android.R.attr.textColorPrimary}
- );
- int arrowColor = typedArray.getColor(0, Color.BLACK);
- typedArray.recycle();
- arrowRight.setColorFilter(arrowColor);
- } catch (Exception ignored) {
- }
-
- colorPicker.setAlphaSliderVisible(showAlphaSlider);
- oldColorPanel.setColor(args.getInt(ARG_COLOR));
- colorPicker.setColor(color, true);
- newColorPanel.setColor(color);
- setHex(color);
-
- if (!showAlphaSlider) {
- hexEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(6)});
- }
-
- newColorPanel.setOnClickListener(v -> {
- if (newColorPanel.getColor() == color) {
- colorPickerDialogListener.onColorSelected(dialogId, color);
- dismiss();
- }
- });
-
- contentView.setOnTouchListener(this);
- colorPicker.setOnColorChangedListener(this);
- hexEditText.addTextChangedListener(this);
-
- hexEditText.setOnFocusChangeListener((v, hasFocus) -> {
- if (hasFocus) {
- InputMethodManager imm = (InputMethodManager) getActivity()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm != null) imm.showSoftInput(hexEditText, InputMethodManager.SHOW_IMPLICIT);
- }
- });
-
- return contentView;
- }
-
- @SuppressLint("ClickableViewAccessibility")
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (v != hexEditText && hexEditText.hasFocus()) {
- hexEditText.clearFocus();
- InputMethodManager imm = (InputMethodManager) getActivity()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm != null) imm.hideSoftInputFromWindow(hexEditText.getWindowToken(), 0);
- hexEditText.clearFocus();
- return true;
- }
- return false;
- }
-
- @Override
- public void onColorChanged(int newColor) {
- color = newColor;
- newColorPanel.setColor(newColor);
- if (!fromEditText) {
- setHex(newColor);
- if (hexEditText.hasFocus()) {
- InputMethodManager imm = (InputMethodManager) getActivity()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm != null) imm.hideSoftInputFromWindow(hexEditText.getWindowToken(), 0);
- hexEditText.clearFocus();
- }
- }
- fromEditText = false;
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
-
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- if (hexEditText.isFocused()) {
- try {
- int color = parseColorString(s.toString());
- if (color != colorPicker.getColor()) {
- fromEditText = true;
- colorPicker.setColor(color, true);
- }
- } catch (NumberFormatException ex) {
- // nothing to do
- }
- }
- }
-
- private void setHex(int color) {
- if (showAlphaSlider) {
- hexEditText.setText(String.format("%08X", (color)));
- } else {
- hexEditText.setText(String.format("%06X", (0xFFFFFF & color)));
- }
- }
-
- private int parseColorString(String colorString) throws NumberFormatException {
- int a, r, g, b = 0;
- if (colorString.startsWith("#")) {
- colorString = colorString.substring(1);
- }
- if (colorString.length() == 0) {
- r = 0;
- a = 255;
- g = 0;
- } else if (colorString.length() <= 2) {
- a = 255;
- r = 0;
- b = Integer.parseInt(colorString, 16);
- g = 0;
- } else if (colorString.length() == 3) {
- a = 255;
- r = Integer.parseInt(colorString.substring(0, 1), 16);
- g = Integer.parseInt(colorString.substring(1, 2), 16);
- b = Integer.parseInt(colorString.substring(2, 3), 16);
- } else if (colorString.length() == 4) {
- a = 255;
- r = Integer.parseInt(colorString.substring(0, 2), 16);
- g = r;
- r = 0;
- b = Integer.parseInt(colorString.substring(2, 4), 16);
- } else if (colorString.length() == 5) {
- a = 255;
- r = Integer.parseInt(colorString.substring(0, 1), 16);
- g = Integer.parseInt(colorString.substring(1, 3), 16);
- b = Integer.parseInt(colorString.substring(3, 5), 16);
- } else if (colorString.length() == 6) {
- a = 255;
- r = Integer.parseInt(colorString.substring(0, 2), 16);
- g = Integer.parseInt(colorString.substring(2, 4), 16);
- b = Integer.parseInt(colorString.substring(4, 6), 16);
- } else if (colorString.length() == 7) {
- a = Integer.parseInt(colorString.substring(0, 1), 16);
- r = Integer.parseInt(colorString.substring(1, 3), 16);
- g = Integer.parseInt(colorString.substring(3, 5), 16);
- b = Integer.parseInt(colorString.substring(5, 7), 16);
- } else if (colorString.length() == 8) {
- a = Integer.parseInt(colorString.substring(0, 2), 16);
- r = Integer.parseInt(colorString.substring(2, 4), 16);
- g = Integer.parseInt(colorString.substring(4, 6), 16);
- b = Integer.parseInt(colorString.substring(6, 8), 16);
- } else {
- b = -1;
- g = -1;
- r = -1;
- a = -1;
- }
- return Color.argb(a, r, g, b);
- }
-
- // -- endregion --
-
- // region Presets Picker
-
- View createPresetsView() {
- View contentView = View.inflate(getActivity(), R.layout.cpv_dialog_presets, null);
- shadesLayout = contentView.findViewById(R.id.shades_layout);
- transparencySeekBar = contentView.findViewById(R.id.transparency_seekbar);
- transparencyPercText = contentView.findViewById(R.id.transparency_text);
- GridView gridView = contentView.findViewById(R.id.gridView);
-
- loadPresets();
-
- if (showColorShades) {
- createColorShades(color);
- } else {
- shadesLayout.setVisibility(View.GONE);
- contentView.findViewById(R.id.shades_divider).setVisibility(View.GONE);
- }
-
- adapter = new ColorPaletteAdapter(newColor -> {
- if (color == newColor) {
- colorPickerDialogListener.onColorSelected(dialogId, color);
- dismiss();
- return;
- }
- color = newColor;
- if (showColorShades) {
- createColorShades(color);
- }
- }, presets, getSelectedItemPosition(), colorShape);
-
- gridView.setAdapter(adapter);
-
- if (showAlphaSlider) {
- setupTransparency();
- } else {
- contentView.findViewById(R.id.transparency_layout).setVisibility(View.GONE);
- contentView.findViewById(R.id.transparency_title).setVisibility(View.GONE);
- }
-
- return contentView;
- }
-
- private void loadPresets() {
- int alpha = Color.alpha(color);
- presets = getArguments().getIntArray(ARG_PRESETS);
- if (presets == null) presets = MATERIAL_COLORS;
- boolean isMaterialColors = presets == MATERIAL_COLORS;
- presets = Arrays.copyOf(presets, presets.length);
- // don't update the original array when modifying alpha
- if (alpha != 255) {
- // add alpha to the presets
- for (int i = 0; i < presets.length; i++) {
- int color = presets[i];
- int red = Color.red(color);
- int green = Color.green(color);
- int blue = Color.blue(color);
- presets[i] = Color.argb(alpha, red, green, blue);
- }
- }
- presets = unshiftIfNotExists(presets, color);
- if (isMaterialColors && presets.length == 19) {
- // Add black to have a total of 20 colors if the current color is in the material color palette
- presets = pushIfNotExists(presets, Color.argb(alpha, 0, 0, 0));
- }
- }
-
- void createColorShades(@ColorInt final int color) {
- final int[] colorShades = getColorShades(color);
-
- if (shadesLayout.getChildCount() != 0) {
- for (int i = 0; i < shadesLayout.getChildCount(); i++) {
- FrameLayout layout = (FrameLayout) shadesLayout.getChildAt(i);
- final ColorPanelView cpv = layout.findViewById(R.id.cpv_color_panel_view);
- ImageView iv = layout.findViewById(R.id.cpv_color_image_view);
- cpv.setColor(colorShades[i]);
- cpv.setTag(false);
- iv.setImageDrawable(null);
- }
- return;
- }
-
- final int horizontalPadding = getResources()
- .getDimensionPixelSize(R.dimen.cpv_item_horizontal_padding);
-
- for (final int colorShade : colorShades) {
- int layoutResId;
- if (colorShape == ColorShape.SQUARE) {
- layoutResId = R.layout.cpv_color_item_square;
- } else {
- layoutResId = R.layout.cpv_color_item_circle;
- }
-
- final View view = View.inflate(getActivity(), layoutResId, null);
- final ColorPanelView colorPanelView = view.findViewById(R.id.cpv_color_panel_view);
-
- ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) colorPanelView
- .getLayoutParams();
- params.leftMargin = params.rightMargin = horizontalPadding;
- colorPanelView.setLayoutParams(params);
- colorPanelView.setColor(colorShade);
- shadesLayout.addView(view);
-
- colorPanelView.post(() -> {
- // The color is black when rotating the dialog. This is a dirty fix. WTF!?
- colorPanelView.setColor(colorShade);
- });
-
- colorPanelView.setOnClickListener(v -> {
- if (v.getTag() instanceof Boolean && (Boolean) v.getTag()) {
- colorPickerDialogListener.onColorSelected(dialogId, ColorPickerDialog.this.color);
- dismiss();
- return; // already selected
- }
- ColorPickerDialog.this.color = colorPanelView.getColor();
- adapter.selectNone();
- for (int i = 0; i < shadesLayout.getChildCount(); i++) {
- FrameLayout layout = (FrameLayout) shadesLayout.getChildAt(i);
- ColorPanelView cpv = layout.findViewById(R.id.cpv_color_panel_view);
- ImageView iv = layout.findViewById(R.id.cpv_color_image_view);
- iv.setImageResource(cpv == v ? R.drawable.cpv_preset_checked : 0);
- if (cpv == v && ColorUtils.calculateLuminance(cpv.getColor()) >= 0.65 ||
- Color.alpha(cpv.getColor()) <= ALPHA_THRESHOLD) {
- iv.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN);
- } else {
- iv.setColorFilter(null);
- }
- cpv.setTag(cpv == v);
- }
- });
- colorPanelView.setOnLongClickListener(v -> {
- colorPanelView.showHint();
- return true;
- });
- }
- }
-
- private int shadeColor(@ColorInt int color, double percent) {
- String hex = String.format("#%06X", (0xFFFFFF & color));
- long f = Long.parseLong(hex.substring(1), 16);
- double t = percent < 0 ? 0 : 255;
- double p = percent < 0 ? percent * -1 : percent;
- long R = f >> 16;
- long G = f >> 8 & 0x00FF;
- long B = f & 0x0000FF;
- int alpha = Color.alpha(color);
- int red = (int) (Math.round((t - R) * p) + R);
- int green = (int) (Math.round((t - G) * p) + G);
- int blue = (int) (Math.round((t - B) * p) + B);
- return Color.argb(alpha, red, green, blue);
- }
-
- private int[] getColorShades(@ColorInt int color) {
- return new int[]{
- shadeColor(color, 0.9),
- shadeColor(color, 0.7),
- shadeColor(color, 0.5),
- shadeColor(color, 0.333),
- shadeColor(color, 0.166),
- shadeColor(color, -0.125),
- shadeColor(color, -0.25),
- shadeColor(color, -0.375),
- shadeColor(color, -0.5),
- shadeColor(color, -0.675),
- shadeColor(color, -0.7),
- shadeColor(color, -0.775),
- };
- }
-
- private void setupTransparency() {
- int progress = 255 - Color.alpha(color);
- transparencySeekBar.setMax(255);
- transparencySeekBar.setProgress(progress);
- int percentage = (int) ((double) progress * 100 / 255);
- transparencyPercText.setText(String.format(Locale.ENGLISH, "%d%%", percentage));
- transparencySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- int percentage = (int) ((double) progress * 100 / 255);
- transparencyPercText.setText(String.format(Locale.ENGLISH, "%d%%", percentage));
- int alpha = 255 - progress;
- // update items in GridView:
- for (int i = 0; i < adapter.colors.length; i++) {
- int color = adapter.colors[i];
- int red = Color.red(color);
- int green = Color.green(color);
- int blue = Color.blue(color);
- adapter.colors[i] = Color.argb(alpha, red, green, blue);
- }
- adapter.notifyDataSetChanged();
- // update shades:
- for (int i = 0; i < shadesLayout.getChildCount(); i++) {
- FrameLayout layout = (FrameLayout) shadesLayout.getChildAt(i);
- ColorPanelView cpv = layout.findViewById(R.id.cpv_color_panel_view);
- ImageView iv = layout.findViewById(R.id.cpv_color_image_view);
- if (layout.getTag() == null) {
- // save the original border color
- layout.setTag(cpv.getBorderColor());
- }
- int color = cpv.getColor();
- color = Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
- if (alpha <= ALPHA_THRESHOLD) {
- cpv.setBorderColor(color | 0xFF000000);
- } else {
- cpv.setBorderColor((int) layout.getTag());
- }
- if (cpv.getTag() != null && (Boolean) cpv.getTag()) {
- // The alpha changed on the selected shaded color. Update the checkmark color filter.
- if (alpha <= ALPHA_THRESHOLD) {
- iv.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN);
- } else {
- if (ColorUtils.calculateLuminance(color) >= 0.65) {
- iv.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN);
- } else {
- iv.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);
- }
- }
- }
- cpv.setColor(color);
- }
- // update color:
- int red = Color.red(color);
- int green = Color.green(color);
- int blue = Color.blue(color);
- color = Color.argb(alpha, red, green, blue);
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
-
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
-
- }
- });
- }
-
- private int[] unshiftIfNotExists(int[] array, int value) {
- boolean present = false;
- for (int i : array) {
- if (i == value) {
- present = true;
- break;
- }
- }
- if (present) {
- return array;
- }
- int[] newArray = new int[array.length + 1];
- newArray[0] = value;
- System.arraycopy(array, 0, newArray, 1, newArray.length - 1);
- return newArray;
- }
-
- private int[] pushIfNotExists(int[] array, int value) {
- boolean present = false;
- for (int i : array) {
- if (i == value) {
- present = true;
- break;
- }
- }
- if (!present) {
- int[] newArray = new int[array.length + 1];
- newArray[newArray.length - 1] = value;
- System.arraycopy(array, 0, newArray, 0, newArray.length - 1);
- return newArray;
- }
- return array;
- }
-
- private int getSelectedItemPosition() {
- for (int i = 0; i < presets.length; i++) {
- if (presets[i] == color) {
- return i;
- }
- }
- return -1;
- }
-
- // endregion
-
- // region Builder
-
- @SuppressWarnings("WeakerAccess")
- public static final class Builder {
-
- @StringRes
- int dialogTitle = R.string.cpv_default_title;
- @DialogType
- int dialogType = TYPE_PRESETS;
- int[] presets = MATERIAL_COLORS;
- @ColorInt
- int color = Color.BLACK;
- int dialogId = 0;
- boolean showAlphaSlider = false;
- boolean allowPresets = true;
- boolean allowCustom = true;
- boolean showColorShades = true;
- @ColorShape
- int colorShape = ColorShape.CIRCLE;
-
- /*package*/ Builder() {
-
- }
-
- /**
- * Set the dialog title string resource id
- *
- * @param dialogTitle The string resource used for the dialog title
- * @return This builder object for chaining method calls
- */
- public Builder setDialogTitle(@StringRes int dialogTitle) {
- this.dialogTitle = dialogTitle;
- return this;
- }
-
- /**
- * Set which dialog view to show.
- *
- * @param dialogType Either {@link ColorPickerDialog#TYPE_CUSTOM} or {@link ColorPickerDialog#TYPE_PRESETS}.
- * @return This builder object for chaining method calls
- */
- public Builder setDialogType(@DialogType int dialogType) {
- this.dialogType = dialogType;
- return this;
- }
-
- /**
- * Set the colors used for the presets
- *
- * @param presets An array of color ints.
- * @return This builder object for chaining method calls
- */
- public Builder setPresets(@NonNull int[] presets) {
- this.presets = presets;
- return this;
- }
-
- /**
- * Set the original color
- *
- * @param color The default color for the color picker
- * @return This builder object for chaining method calls
- */
- public Builder setColor(int color) {
- this.color = color;
- return this;
- }
-
- /**
- * Set the dialog id used for callbacks
- *
- * @param dialogId The id that is sent back to the {@link ColorPickerDialogListener}.
- * @return This builder object for chaining method calls
- */
- public Builder setDialogId(int dialogId) {
- this.dialogId = dialogId;
- return this;
- }
-
- /**
- * Show the alpha slider
- *
- * @param showAlphaSlider {@code true} to show the alpha slider. Currently only supported with
- * the {@link ColorPickerView}.
- * @return This builder object for chaining method calls
- */
- public Builder setShowAlphaSlider(boolean showAlphaSlider) {
- this.showAlphaSlider = showAlphaSlider;
- return this;
- }
-
- /**
- * Show/Hide a neutral button to select preset colors.
- *
- * @param allowPresets {@code false} to disable showing the presets button.
- * @return This builder object for chaining method calls
- */
- public Builder setAllowPresets(boolean allowPresets) {
- this.allowPresets = allowPresets;
- return this;
- }
-
- /**
- * Show/Hide the neutral button to select a custom color.
- *
- * @param allowCustom {@code false} to disable showing the custom button.
- * @return This builder object for chaining method calls
- */
- public Builder setAllowCustom(boolean allowCustom) {
- this.allowCustom = allowCustom;
- return this;
- }
-
- /**
- * Show/Hide the color shades in the presets picker
- *
- * @param showColorShades {@code false} to hide the color shades.
- * @return This builder object for chaining method calls
- */
- public Builder setShowColorShades(boolean showColorShades) {
- this.showColorShades = showColorShades;
- return this;
- }
-
- /**
- * Set the shape of the color panel view.
- *
- * @param colorShape Either {@link ColorShape#CIRCLE} or {@link ColorShape#SQUARE}.
- * @return This builder object for chaining method calls
- */
- public Builder setColorShape(int colorShape) {
- this.colorShape = colorShape;
- return this;
- }
-
- /**
- * Create the {@link ColorPickerDialog} instance.
- *
- * @return A new {@link ColorPickerDialog}.
- * @see #show(FragmentActivity)
- */
- public ColorPickerDialog create() {
- ColorPickerDialog dialog = new ColorPickerDialog();
- Bundle args = new Bundle();
- args.putInt(ARG_ID, dialogId);
- args.putInt(ARG_TYPE, dialogType);
- args.putInt(ARG_COLOR, color);
- args.putIntArray(ARG_PRESETS, presets);
- args.putBoolean(ARG_ALPHA, showAlphaSlider);
- args.putBoolean(ARG_ALLOW_CUSTOM, allowCustom);
- args.putBoolean(ARG_ALLOW_PRESETS, allowPresets);
- args.putInt(ARG_DIALOG_TITLE, dialogTitle);
- args.putBoolean(ARG_SHOW_COLOR_SHADES, showColorShades);
- args.putInt(ARG_COLOR_SHAPE, colorShape);
- dialog.setArguments(args);
- return dialog;
- }
-
- /**
- * Create and show the {@link ColorPickerDialog} created with this builder.
- *
- * @param activity The current activity.
- */
- public void show(FragmentActivity activity) {
- create().show(activity.getSupportFragmentManager(), "color-picker-dialog");
- }
- }
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({TYPE_CUSTOM, TYPE_PRESETS})
- public @interface DialogType {
-
- }
-
- // endregion
-
-}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialog.kt b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialog.kt
new file mode 100644
index 00000000..590021bb
--- /dev/null
+++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialog.kt
@@ -0,0 +1,760 @@
+/*
+ * Copyright (C) 2017 JRummy Apps Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jrummyapps.android.colorpicker
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.Dialog
+import android.content.Context
+import android.content.DialogInterface
+import android.graphics.Color
+import android.graphics.PorterDuff
+import android.os.Bundle
+import android.text.Editable
+import android.text.InputFilter
+import android.text.InputFilter.LengthFilter
+import android.text.TextWatcher
+import android.util.TypedValue
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnTouchListener
+import android.view.ViewGroup.MarginLayoutParams
+import android.view.WindowManager
+import android.view.inputmethod.InputMethodManager
+import android.widget.*
+import android.widget.SeekBar.OnSeekBarChangeListener
+import androidx.annotation.ColorInt
+import androidx.annotation.IntDef
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AlertDialog
+import androidx.core.graphics.ColorUtils
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
+import com.jrummyapps.android.colorpicker.ColorPickerView.OnColorChangedListener
+import java.util.*
+import kotlin.math.roundToInt
+
+/**
+ *
+ * A dialog to pick a color.
+ *
+ *
+ * The [activity][Activity] that shows this dialog should implement [ColorPickerDialogListener]
+ *
+ *
+ * Example usage:
+ *
+ *
+ * ColorPickerDialog.newBuilder().show(activity);
+
*
+ */
+class ColorPickerDialog :
+ DialogFragment(),
+ OnTouchListener,
+ OnColorChangedListener,
+ TextWatcher {
+
+ companion object {
+ private const val ARG_ID = "id"
+ private const val ARG_TYPE = "dialogType"
+ private const val ARG_COLOR = "color"
+ private const val ARG_ALPHA = "alpha"
+ private const val ARG_PRESETS = "presets"
+ private const val ARG_ALLOW_PRESETS = "allowPresets"
+ private const val ARG_ALLOW_CUSTOM = "allowCustom"
+ private const val ARG_DIALOG_TITLE = "dialogTitle"
+ private const val ARG_SHOW_COLOR_SHADES = "showColorShades"
+ private const val ARG_COLOR_SHAPE = "colorShape"
+ const val TYPE_CUSTOM = 0
+ const val TYPE_PRESETS = 1
+ const val ALPHA_THRESHOLD = 165
+
+ /**
+ * Material design colors used as the default color presets
+ */
+ @JvmField
+ val MATERIAL_COLORS = intArrayOf(
+ -0xbbcca, // RED 500
+ -0x16e19d, // PINK 500
+ -0xd36d, // LIGHT PINK 500
+ -0x63d850, // PURPLE 500
+ -0x98c549, // DEEP PURPLE 500
+ -0xc0ae4b, // INDIGO 500
+ -0xde690d, // BLUE 500
+ -0xfc560c, // LIGHT BLUE 500
+ -0xff432c, // CYAN 500
+ -0xff6978, // TEAL 500
+ -0xb350b0, // GREEN 500
+ -0x743cb6, // LIGHT GREEN 500
+ -0x3223c7, // LIME 500
+ -0x14c5, // YELLOW 500
+ -0x3ef9, // AMBER 500
+ -0x6800, // ORANGE 500
+ -0x86aab8, // BROWN 500
+ -0x9f8275, // BLUE GREY 500
+ -0x616162
+ )
+
+ /**
+ * Create a new Builder for creating a [ColorPickerDialog] instance
+ *
+ * @return The [builder][Builder] to create the [ColorPickerDialog].
+ */
+ @JvmStatic
+ fun newBuilder(): Builder {
+ return Builder()
+ }
+
+ fun unshiftIfNotExists(array: IntArray, value: Int): IntArray {
+ if (array.any { it == value }) {
+ return array
+ }
+ val newArray = IntArray(array.size + 1)
+ newArray[0] = value
+ System.arraycopy(array, 0, newArray, 1, newArray.size - 1)
+ return newArray
+ }
+
+ fun pushIfNotExists(array: IntArray, value: Int): IntArray {
+ if (array.any { it == value }) {
+ return array
+ }
+ val newArray = IntArray(array.size + 1)
+ newArray[newArray.size - 1] = value
+ System.arraycopy(array, 0, newArray, 0, newArray.size - 1)
+ return newArray
+ }
+ }
+
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(TYPE_CUSTOM, TYPE_PRESETS)
+ annotation class DialogType
+
+ var colorPickerDialogListener: ColorPickerDialogListener? = null
+
+ var presets: IntArray = MATERIAL_COLORS
+
+ private var rootView: FrameLayout? = null
+
+ @ColorInt
+ var color = 0
+ private var dialogType = 0
+ var dialogId = 0
+ var showColorShades = false
+ private var colorShape = 0
+
+ // -- PRESETS --------------------------
+ internal var adapter: ColorPaletteAdapter? = null
+ private var shadesLayout: LinearLayout? = null
+ private var transparencySeekBar: SeekBar? = null
+ private var transparencyPercText: TextView? = null
+
+ // -- CUSTOM ---------------------------
+ var colorPicker: ColorPickerView? = null
+ private var newColorPanel: ColorPanelView? = null
+ private var hexEditText: EditText? = null
+ private var showAlphaSlider = false
+ private var fromEditText = false
+
+ private val selectedItemPosition: Int
+ get() = presets.indexOf(color)
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ if (colorPickerDialogListener == null && context is ColorPickerDialogListener) {
+ colorPickerDialogListener = context
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val args = arguments ?: error("onCreateDialog: args is null")
+ val activity = activity ?: error("onCreateDialog: activity is null")
+ dialogId = args.getInt(ARG_ID)
+ showAlphaSlider = args.getBoolean(ARG_ALPHA)
+ showColorShades = args.getBoolean(ARG_SHOW_COLOR_SHADES)
+ colorShape = args.getInt(ARG_COLOR_SHAPE)
+ if (savedInstanceState == null) {
+ color = args.getInt(ARG_COLOR)
+ dialogType = args.getInt(ARG_TYPE)
+ } else {
+ color = savedInstanceState.getInt(ARG_COLOR)
+ dialogType = savedInstanceState.getInt(ARG_TYPE)
+ }
+ val rootView = FrameLayout(activity).also { this.rootView = it }
+ when (dialogType) {
+ TYPE_CUSTOM -> rootView.addView(createPickerView())
+ TYPE_PRESETS -> rootView.addView(createPresetsView())
+ }
+
+ val builder = AlertDialog.Builder(activity)
+ .setView(rootView)
+ .setPositiveButton(R.string.cpv_select) { _, _ ->
+ colorPickerDialogListener?.onColorSelected(dialogId, color)
+ }
+
+ val dialogTitleStringRes = args.getInt(ARG_DIALOG_TITLE)
+ if (dialogTitleStringRes != 0) {
+ builder.setTitle(dialogTitleStringRes)
+ }
+ val neutralButtonStringRes: Int
+ neutralButtonStringRes =
+ if (dialogType == TYPE_CUSTOM && args.getBoolean(ARG_ALLOW_PRESETS)) {
+ R.string.cpv_presets
+ } else if (dialogType == TYPE_PRESETS && args.getBoolean(ARG_ALLOW_CUSTOM)) {
+ R.string.cpv_custom
+ } else {
+ 0
+ }
+ if (neutralButtonStringRes != 0) {
+ builder.setNeutralButton(neutralButtonStringRes, null)
+ }
+ return builder.create()
+ }
+
+ override fun onStart() {
+ super.onStart()
+ val dialog = dialog as AlertDialog
+
+ // http://stackoverflow.com/a/16972670/1048340
+ dialog.window?.clearFlags(
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ )
+ dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
+
+ // Do not dismiss the dialog when clicking the neutral button.
+ val neutralButton = dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
+ neutralButton?.setOnClickListener { v: View ->
+ rootView!!.removeAllViews()
+ when (dialogType) {
+ TYPE_CUSTOM -> {
+ dialogType = TYPE_PRESETS
+ (v as Button).setText(R.string.cpv_custom)
+ rootView!!.addView(createPresetsView())
+ }
+ TYPE_PRESETS -> {
+ dialogType = TYPE_CUSTOM
+ (v as Button).setText(R.string.cpv_presets)
+ rootView!!.addView(createPickerView())
+ }
+ }
+ }
+ }
+
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+ colorPickerDialogListener!!.onDialogDismissed(dialogId)
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ outState.putInt(ARG_COLOR, color)
+ outState.putInt(ARG_TYPE, dialogType)
+ super.onSaveInstanceState(outState)
+ }
+
+
+ // region Custom Picker
+ private fun createPickerView(): View {
+ val args = arguments ?: throw RuntimeException("createPickerView: args is null")
+ val activity = activity ?: throw RuntimeException("createPickerView: activity is null")
+ val contentView = View.inflate(getActivity(), R.layout.cpv_dialog_color_picker, null)
+ colorPicker = contentView.findViewById(R.id.cpv_color_picker_view)
+ val oldColorPanel: ColorPanelView = contentView.findViewById(R.id.cpv_color_panel_old)
+ newColorPanel = contentView.findViewById(R.id.cpv_color_panel_new)
+ val arrowRight = contentView.findViewById(R.id.cpv_arrow_right)
+ hexEditText = contentView.findViewById(R.id.cpv_hex)
+ try {
+ val value = TypedValue()
+ val typedArray = activity.obtainStyledAttributes(
+ value.data, intArrayOf(android.R.attr.textColorPrimary)
+ )
+ val arrowColor = typedArray.getColor(0, Color.BLACK)
+ typedArray.recycle()
+ arrowRight.setColorFilter(arrowColor)
+ } catch (ignored: Exception) {
+ }
+ oldColorPanel.color = args.getInt(ARG_COLOR)
+
+ val c = color
+
+ colorPicker?.apply {
+ setAlphaSliderVisible(showAlphaSlider)
+ setColor(c, true)
+ onColorChangedListener = this@ColorPickerDialog
+ }
+
+ newColorPanel?.apply {
+ color = c
+ setOnClickListener {
+ if (color == c) {
+ colorPickerDialogListener?.onColorSelected(dialogId, color)
+ dismiss()
+ }
+ }
+ }
+
+ hexEditText?.apply {
+ setHex(color)
+ addTextChangedListener(this@ColorPickerDialog)
+ setOnFocusChangeListener { _, hasFocus ->
+ if (hasFocus) showSoftInput(true)
+ }
+ if (!showAlphaSlider) {
+ filters = arrayOf(LengthFilter(6))
+ }
+ }
+
+ contentView.setOnTouchListener(this)
+
+ return contentView
+ }
+
+ private fun View?.showSoftInput(show: Boolean) {
+ this ?: return
+ val imm = (activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)
+ ?: return
+ if (show) {
+ imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
+ } else {
+ imm.hideSoftInputFromWindow(this.windowToken, 0)
+ }
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(v: View, event: MotionEvent): Boolean {
+ val hexEditText = this.hexEditText
+ if (hexEditText?.hasFocus() == true && v !== hexEditText) {
+ hexEditText.clearFocus()
+ hexEditText.showSoftInput(false)
+ hexEditText.clearFocus()
+ return true
+ }
+ return false
+ }
+
+ override fun onColorChanged(newColor: Int) {
+ color = newColor
+ newColorPanel!!.color = newColor
+ if (!fromEditText) {
+ setHex(newColor)
+ val hexEditText = this.hexEditText
+ if (hexEditText?.hasFocus() == true) {
+ hexEditText.showSoftInput(false)
+ hexEditText.clearFocus()
+ }
+ }
+ fromEditText = false
+ }
+
+ override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+ override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
+ override fun afterTextChanged(s: Editable) {
+ if (hexEditText!!.isFocused) {
+ try {
+ val color = parseColorString(s.toString())
+ if (color != colorPicker!!.color) {
+ fromEditText = true
+ colorPicker!!.setColor(color, true)
+ }
+ } catch (ex: NumberFormatException) {
+ // nothing to do
+ }
+ }
+ }
+
+ private fun setHex(color: Int) {
+ if (showAlphaSlider) {
+ hexEditText!!.setText(String.format("%08X", color))
+ } else {
+ hexEditText!!.setText(String.format("%06X", 0xFFFFFF and color))
+ }
+ }
+
+
+ // -- endregion --
+ // region Presets Picker
+ private fun createPresetsView(): View {
+ val contentView = View.inflate(activity, R.layout.cpv_dialog_presets, null)
+ shadesLayout = contentView.findViewById(R.id.shades_layout)
+ transparencySeekBar = contentView.findViewById(R.id.transparency_seekbar)
+ transparencyPercText = contentView.findViewById(R.id.transparency_text)
+ val gridView = contentView.findViewById(R.id.gridView)
+ loadPresets()
+ if (showColorShades) {
+ createColorShades(color)
+ } else {
+ shadesLayout?.visibility = View.GONE
+ contentView.findViewById(R.id.shades_divider).visibility = View.GONE
+ }
+ adapter = ColorPaletteAdapter(presets, selectedItemPosition, colorShape){
+ when(it){
+ color -> {
+ colorPickerDialogListener?.onColorSelected(dialogId, color)
+ dismiss()
+ }
+ else ->{
+ color = it
+ if (showColorShades) {
+ createColorShades(color)
+ }
+ }
+ }
+ }
+
+ gridView.adapter = adapter
+ if (showAlphaSlider) {
+ setupTransparency()
+ } else {
+ contentView.findViewById(R.id.transparency_layout).visibility = View.GONE
+ contentView.findViewById(R.id.transparency_title).visibility = View.GONE
+ }
+ return contentView
+ }
+
+ private fun loadPresets() {
+ presets = arguments?.getIntArray(ARG_PRESETS) ?: MATERIAL_COLORS
+ presets = presets.copyOf()
+
+ val isMaterialColors = presets.contentEquals(MATERIAL_COLORS)
+
+ val alpha = Color.alpha(color)
+
+ // don't update the original array when modifying alpha
+ if (alpha != 255) {
+ // add alpha to the presets
+ for (i in presets.indices) {
+ val color = presets[i]
+ val red = Color.red(color)
+ val green = Color.green(color)
+ val blue = Color.blue(color)
+ presets[i] = Color.argb(alpha, red, green, blue)
+ }
+ }
+ presets = unshiftIfNotExists(presets, color)
+ if (isMaterialColors && presets.size == 19) {
+ // Add black to have a total of 20 colors if the current color is in the material color palette
+ presets = pushIfNotExists(presets, Color.argb(alpha, 0, 0, 0))
+ }
+ }
+
+ private fun createColorShades(@ColorInt color: Int) {
+ val colorShades = getColorShades(color)
+ if (shadesLayout!!.childCount != 0) {
+ for (i in 0 until shadesLayout!!.childCount) {
+ val layout = shadesLayout!!.getChildAt(i) as FrameLayout
+ val cpv: ColorPanelView = layout.findViewById(R.id.cpv_color_panel_view)
+ val iv = layout.findViewById(R.id.cpv_color_image_view)
+ cpv.color = colorShades[i]
+ cpv.tag = false
+ iv.setImageDrawable(null)
+ }
+ return
+ }
+ val horizontalPadding = resources
+ .getDimensionPixelSize(R.dimen.cpv_item_horizontal_padding)
+ for (colorShade in colorShades) {
+ var layoutResId: Int
+ layoutResId = if (colorShape == ColorShape.SQUARE) {
+ R.layout.cpv_color_item_square
+ } else {
+ R.layout.cpv_color_item_circle
+ }
+ val view = View.inflate(activity, layoutResId, null)
+ val colorPanelView: ColorPanelView = view.findViewById(R.id.cpv_color_panel_view)
+ val params = colorPanelView
+ .layoutParams as MarginLayoutParams
+ params.rightMargin = horizontalPadding
+ params.leftMargin = params.rightMargin
+ colorPanelView.layoutParams = params
+ colorPanelView.color = colorShade
+ shadesLayout!!.addView(view)
+ colorPanelView.post {
+ // The color is black when rotating the dialog. This is a dirty fix. WTF!?
+ colorPanelView.color = colorShade
+ }
+ colorPanelView.setOnClickListener { v: View ->
+ if (v.tag is Boolean && v.tag as Boolean) {
+ colorPickerDialogListener!!.onColorSelected(
+ dialogId,
+ this@ColorPickerDialog.color
+ )
+ dismiss()
+ return@setOnClickListener // already selected
+ }
+ this@ColorPickerDialog.color = colorPanelView.color
+ adapter!!.selectNone()
+ for (i in 0 until shadesLayout!!.childCount) {
+ val layout = shadesLayout!!.getChildAt(i) as FrameLayout
+ val cpv: ColorPanelView = layout.findViewById(R.id.cpv_color_panel_view)
+ val iv = layout.findViewById(R.id.cpv_color_image_view)
+ iv.setImageResource(if (cpv === v) R.drawable.cpv_preset_checked else 0)
+ if (cpv === v && ColorUtils.calculateLuminance(cpv.color) >= 0.65 ||
+ Color.alpha(cpv.color) <= ALPHA_THRESHOLD
+ ) {
+ iv.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN)
+ } else {
+ iv.colorFilter = null
+ }
+ cpv.tag = cpv === v
+ }
+ }
+ colorPanelView.setOnLongClickListener {
+ colorPanelView.showHint()
+ true
+ }
+ }
+ }
+
+ private fun shadeColor(@ColorInt color: Int, percent: Double): Int {
+ val hex = String.format("#%06X", 0xFFFFFF and color)
+ val f = hex.substring(1).toLong(16)
+ val t = (if (percent < 0) 0 else 255).toDouble()
+ val p = if (percent < 0) percent * -1 else percent
+ val R = f shr 16
+ val G = f shr 8 and 0x00FF
+ val B = f and 0x0000FF
+ return Color.argb(
+ Color.alpha(color),
+ ((t - R) * p).roundToInt() + R.toInt(),
+ ((t - G) * p).roundToInt() + G.toInt(),
+ ((t - B) * p).roundToInt() + B.toInt(),
+ )
+ }
+
+ private fun getColorShades(@ColorInt color: Int): IntArray {
+ return intArrayOf(
+ shadeColor(color, 0.9),
+ shadeColor(color, 0.7),
+ shadeColor(color, 0.5),
+ shadeColor(color, 0.333),
+ shadeColor(color, 0.166),
+ shadeColor(color, -0.125),
+ shadeColor(color, -0.25),
+ shadeColor(color, -0.375),
+ shadeColor(color, -0.5),
+ shadeColor(color, -0.675),
+ shadeColor(color, -0.7),
+ shadeColor(color, -0.775)
+ )
+ }
+
+ private fun setupTransparency() {
+
+ val transparency = 255 - Color.alpha(color)
+ val percentage = (transparency.toDouble() * 100 / 255).toInt()
+
+ transparencyPercText?.text =
+ String.format(Locale.ENGLISH, "%d%%", percentage)
+
+ transparencySeekBar?.apply {
+ max = 255
+ progress = transparency
+ this.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
+ override fun onStartTrackingTouch(seekBar: SeekBar) {}
+ override fun onStopTrackingTouch(seekBar: SeekBar) {}
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ handleTransparencyChanged(progress)
+ }
+ })
+ }
+ }
+
+ private fun handleTransparencyChanged(transparency: Int) {
+
+ val percentage = (transparency.toDouble() * 100 / 255).toInt()
+ val alpha = 255 - transparency
+
+ transparencyPercText?.text =
+ String.format(Locale.ENGLISH, "%d%%", percentage)
+
+ // update color:
+ val red = Color.red(color)
+ val green = Color.green(color)
+ val blue = Color.blue(color)
+ color = Color.argb(alpha, red, green, blue)
+
+ // update items in GridView:
+ adapter?.apply {
+ for (i in colors.indices) {
+ val color = colors[i]
+ colors[i] = Color.argb(
+ alpha,
+ Color.red(color),
+ Color.green(color),
+ Color.blue(color)
+ )
+ }
+ notifyDataSetChanged()
+ }
+
+ // update shades:
+ shadesLayout?.apply {
+ for (i in 0 until childCount) {
+ val layout = getChildAt(i) as FrameLayout
+ val cpv: ColorPanelView = layout.findViewById(R.id.cpv_color_panel_view)
+ val iv = layout.findViewById(R.id.cpv_color_image_view)
+ if (layout.tag == null) {
+ // save the original border color
+ layout.tag = cpv.borderColor
+ }
+ var color = cpv.color
+ color = Color.argb(
+ alpha,
+ Color.red(color),
+ Color.green(color),
+ Color.blue(color)
+ )
+ if (alpha <= ALPHA_THRESHOLD) {
+ cpv.borderColor = color or -0x1000000
+ } else {
+ cpv.borderColor = layout.tag as Int
+ }
+ if (cpv.tag != null && cpv.tag as Boolean) {
+ // The alpha changed on the selected shaded color. Update the checkmark color filter.
+ if (alpha <= ALPHA_THRESHOLD) {
+ iv.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN)
+ } else {
+ if (ColorUtils.calculateLuminance(color) >= 0.65) {
+ iv.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN)
+ } else {
+ iv.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
+ }
+ }
+ }
+ cpv.color = color
+ }
+ }
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate")
+ class Builder(
+ // dialog title string resource id.
+ @StringRes
+ var dialogTitle: Int = R.string.cpv_default_title,
+
+ // which dialog view to show.
+ // Either [ColorPickerDialog.TYPE_CUSTOM] or [ColorPickerDialog.TYPE_PRESETS].
+ var dialogType: Int = TYPE_PRESETS,
+
+ // the colors used for the presets.
+ var presets: IntArray = MATERIAL_COLORS,
+
+ // the original color.
+ @ColorInt
+ var color: Int = Color.BLACK,
+
+ // the dialog id used for callbacks.
+ var dialogId: Int = 0,
+
+ // the alpha slider
+ // true to show the alpha slider.
+ // Currently only supported with the ColorPickerView.
+ var showAlphaSlider: Boolean = false,
+
+ // Show/Hide a neutral button to select preset colors.
+ // false to disable showing the presets button.
+ var allowPresets: Boolean = true,
+
+ // Show/Hide the neutral button to select a custom color.
+ // false to disable showing the custom button.
+ var allowCustom: Boolean = true,
+
+ // Show/Hide the color shades in the presets picker
+ // false to hide the color shades.
+ var showColorShades: Boolean = true,
+
+ // the shape of the color panel view.
+ // Either [ColorShape.CIRCLE] or [ColorShape.SQUARE].
+ var colorShape: Int = ColorShape.CIRCLE,
+ ) {
+ fun setDialogTitle(@StringRes dialogTitle: Int): Builder {
+ this.dialogTitle = dialogTitle
+ return this
+ }
+
+ fun setDialogType(@DialogType dialogType: Int): Builder {
+ this.dialogType = dialogType
+ return this
+ }
+
+ fun setPresets(presets: IntArray): Builder {
+ this.presets = presets
+ return this
+ }
+
+ fun setColor(color: Int): Builder {
+ this.color = color
+ return this
+ }
+
+ fun setDialogId(dialogId: Int): Builder {
+ this.dialogId = dialogId
+ return this
+ }
+
+ fun setShowAlphaSlider(showAlphaSlider: Boolean): Builder {
+ this.showAlphaSlider = showAlphaSlider
+ return this
+ }
+
+ fun setAllowPresets(allowPresets: Boolean): Builder {
+ this.allowPresets = allowPresets
+ return this
+ }
+
+ fun setAllowCustom(allowCustom: Boolean): Builder {
+ this.allowCustom = allowCustom
+ return this
+ }
+
+ fun setShowColorShades(showColorShades: Boolean): Builder {
+ this.showColorShades = showColorShades
+ return this
+ }
+
+ fun setColorShape(colorShape: Int): Builder {
+ this.colorShape = colorShape
+ return this
+ }
+
+ /**
+ * Create the [ColorPickerDialog] instance.
+ */
+ fun create(): ColorPickerDialog {
+ val dialog = ColorPickerDialog()
+ val args = Bundle()
+ args.putInt(ARG_ID, dialogId)
+ args.putInt(ARG_TYPE, dialogType)
+ args.putInt(ARG_COLOR, color)
+ args.putIntArray(ARG_PRESETS, presets)
+ args.putBoolean(ARG_ALPHA, showAlphaSlider)
+ args.putBoolean(ARG_ALLOW_CUSTOM, allowCustom)
+ args.putBoolean(ARG_ALLOW_PRESETS, allowPresets)
+ args.putInt(ARG_DIALOG_TITLE, dialogTitle)
+ args.putBoolean(ARG_SHOW_COLOR_SHADES, showColorShades)
+ args.putInt(ARG_COLOR_SHAPE, colorShape)
+ dialog.arguments = args
+ return dialog
+ }
+
+ /**
+ * Create and show the [ColorPickerDialog] created with this builder.
+ */
+ fun show(activity: FragmentActivity) {
+ create().show(activity.supportFragmentManager, "color-picker-dialog")
+ }
+ }
+}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialogListener.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialogListener.kt
similarity index 52%
rename from colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialogListener.java
rename to colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialogListener.kt
index 1bf059d1..addab4f6 100644
--- a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialogListener.java
+++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialogListener.kt
@@ -13,32 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.jrummyapps.android.colorpicker
-package com.jrummyapps.android.colorpicker;
-
-import androidx.annotation.ColorInt;
+import androidx.annotation.ColorInt
/**
* Callback used for getting the selected color from a color picker dialog.
*/
-public interface ColorPickerDialogListener {
+interface ColorPickerDialogListener {
+ /**
+ * Callback that is invoked when a color is selected from the color picker dialog.
+ *
+ * @param dialogId
+ * The dialog id used to create the dialog instance.
+ * @param color
+ * The selected color
+ */
+ fun onColorSelected(dialogId: Int, @ColorInt newColor: Int)
- /**
- * Callback that is invoked when a color is selected from the color picker dialog.
- *
- * @param dialogId
- * The dialog id used to create the dialog instance.
- * @param color
- * The selected color
- */
- void onColorSelected(int dialogId, @ColorInt int color);
-
- /**
- * Callback that is invoked when the color picker dialog was dismissed.
- *
- * @param dialogId
- * The dialog id used to create the dialog instance.
- */
- void onDialogDismissed(int dialogId);
-
+ /**
+ * Callback that is invoked when the color picker dialog was dismissed.
+ *
+ * @param dialogId
+ * The dialog id used to create the dialog instance.
+ */
+ fun onDialogDismissed(dialogId: Int)
}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerView.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerView.java
deleted file mode 100644
index 92865e42..00000000
--- a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerView.java
+++ /dev/null
@@ -1,982 +0,0 @@
-/*
- * Copyright (C) 2017 JRummy Apps Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.jrummyapps.android.colorpicker;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ComposeShader;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Paint.Style;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Shader;
-import android.graphics.Shader.TileMode;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.MotionEvent;
-import android.view.View;
-
-/**
- * Displays a color picker to the user and allow them to select a color. A slider for the alpha channel is also available.
- * Enable it by setting setAlphaSliderVisible(boolean) to true.
- */
-public class ColorPickerView extends View {
-
- private final static int DEFAULT_BORDER_COLOR = 0xFF6E6E6E;
- private final static int DEFAULT_SLIDER_COLOR = 0xFFBDBDBD;
-
- private final static int HUE_PANEL_WDITH_DP = 30;
- private final static int ALPHA_PANEL_HEIGH_DP = 20;
- private final static int PANEL_SPACING_DP = 10;
- private final static int CIRCLE_TRACKER_RADIUS_DP = 5;
- private final static int SLIDER_TRACKER_SIZE_DP = 4;
- private final static int SLIDER_TRACKER_OFFSET_DP = 2;
-
- /**
- * The width in pixels of the border
- * surrounding all color panels.
- */
- private final static int BORDER_WIDTH_PX = 1;
-
- /**
- * The width in px of the hue panel.
- */
- private int huePanelWidthPx;
- /**
- * The height in px of the alpha panel
- */
- private int alphaPanelHeightPx;
- /**
- * The distance in px between the different
- * color panels.
- */
- private int panelSpacingPx;
- /**
- * The radius in px of the color palette tracker circle.
- */
- private int circleTrackerRadiusPx;
- /**
- * The px which the tracker of the hue or alpha panel
- * will extend outside of its bounds.
- */
- private int sliderTrackerOffsetPx;
- /**
- * Height of slider tracker on hue panel,
- * width of slider on alpha panel.
- */
- private int sliderTrackerSizePx;
-
- private Paint satValPaint;
- private Paint satValTrackerPaint;
-
- private Paint alphaPaint;
- private Paint alphaTextPaint;
- private Paint hueAlphaTrackerPaint;
-
- private Paint borderPaint;
-
- private Shader valShader;
- private Shader satShader;
- private Shader alphaShader;
-
- /*
- * We cache a bitmap of the sat/val panel which is expensive to draw each time.
- * We can reuse it when the user is sliding the circle picker as long as the hue isn't changed.
- */
- private BitmapCache satValBackgroundCache;
- /* We cache the hue background to since its also very expensive now. */
- private BitmapCache hueBackgroundCache;
-
- /* Current values */
- private int alpha = 0xff;
- private float hue = 360f;
- private float sat = 0f;
- private float val = 0f;
-
- private boolean showAlphaPanel = false;
- private String alphaSliderText = null;
- private int sliderTrackerColor = DEFAULT_SLIDER_COLOR;
- private int borderColor = DEFAULT_BORDER_COLOR;
-
- /**
- * Minimum required padding. The offset from the
- * edge we must have or else the finger tracker will
- * get clipped when it's drawn outside of the view.
- */
- private int mRequiredPadding;
-
- /**
- * The Rect in which we are allowed to draw.
- * Trackers can extend outside slightly,
- * due to the required padding we have set.
- */
- private Rect drawingRect;
-
- private Rect satValRect;
- private Rect hueRect;
- private Rect alphaRect;
-
- private Point startTouchPoint = null;
-
- private AlphaPatternDrawable alphaPatternDrawable;
- private OnColorChangedListener onColorChangedListener;
-
- public ColorPickerView(Context context) {
- this(context, null);
- }
-
- public ColorPickerView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context, attrs);
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- Bundle state = new Bundle();
- state.putParcelable("instanceState", super.onSaveInstanceState());
- state.putInt("alpha", alpha);
- state.putFloat("hue", hue);
- state.putFloat("sat", sat);
- state.putFloat("val", val);
- state.putBoolean("show_alpha", showAlphaPanel);
- state.putString("alpha_text", alphaSliderText);
-
- return state;
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
-
- if (state instanceof Bundle) {
- Bundle bundle = (Bundle) state;
-
- alpha = bundle.getInt("alpha");
- hue = bundle.getFloat("hue");
- sat = bundle.getFloat("sat");
- val = bundle.getFloat("val");
- showAlphaPanel = bundle.getBoolean("show_alpha");
- alphaSliderText = bundle.getString("alpha_text");
-
- state = bundle.getParcelable("instanceState");
- }
- super.onRestoreInstanceState(state);
- }
-
- private void init(Context context, AttributeSet attrs) {
- //Load those if set in xml resource file.
- TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPickerView);
- showAlphaPanel = a.getBoolean(R.styleable.ColorPickerView_cpv_alphaChannelVisible, false);
- alphaSliderText = a.getString(R.styleable.ColorPickerView_cpv_alphaChannelText);
- sliderTrackerColor = a.getColor(R.styleable.ColorPickerView_cpv_sliderColor, 0xFFBDBDBD);
- borderColor = a.getColor(R.styleable.ColorPickerView_cpv_borderColor, 0xFF6E6E6E);
- a.recycle();
-
- applyThemeColors(context);
-
- huePanelWidthPx = DrawingUtils.dpToPx(getContext(), HUE_PANEL_WDITH_DP);
- alphaPanelHeightPx = DrawingUtils.dpToPx(getContext(), ALPHA_PANEL_HEIGH_DP);
- panelSpacingPx = DrawingUtils.dpToPx(getContext(), PANEL_SPACING_DP);
- circleTrackerRadiusPx = DrawingUtils.dpToPx(getContext(), CIRCLE_TRACKER_RADIUS_DP);
- sliderTrackerSizePx = DrawingUtils.dpToPx(getContext(), SLIDER_TRACKER_SIZE_DP);
- sliderTrackerOffsetPx = DrawingUtils.dpToPx(getContext(), SLIDER_TRACKER_OFFSET_DP);
-
- mRequiredPadding = getResources().getDimensionPixelSize(R.dimen.cpv_required_padding);
-
- initPaintTools();
-
- //Needed for receiving trackball motion events.
- setFocusable(true);
- setFocusableInTouchMode(true);
- }
-
- private void applyThemeColors(Context c) {
- // If no specific border/slider color has been
- // set we take the default secondary text color
- // as border/slider color. Thus it will adopt
- // to theme changes automatically.
-
- final TypedValue value = new TypedValue();
- TypedArray a = c.obtainStyledAttributes(value.data, new int[]{android.R.attr.textColorSecondary});
-
- if (borderColor == DEFAULT_BORDER_COLOR) {
- borderColor = a.getColor(0, DEFAULT_BORDER_COLOR);
- }
-
- if (sliderTrackerColor == DEFAULT_SLIDER_COLOR) {
- sliderTrackerColor = a.getColor(0, DEFAULT_SLIDER_COLOR);
- }
-
- a.recycle();
- }
-
- private void initPaintTools() {
-
- satValPaint = new Paint();
- satValTrackerPaint = new Paint();
- hueAlphaTrackerPaint = new Paint();
- alphaPaint = new Paint();
- alphaTextPaint = new Paint();
- borderPaint = new Paint();
-
- satValTrackerPaint.setStyle(Style.STROKE);
- satValTrackerPaint.setStrokeWidth(DrawingUtils.dpToPx(getContext(), 2));
- satValTrackerPaint.setAntiAlias(true);
-
- hueAlphaTrackerPaint.setColor(sliderTrackerColor);
- hueAlphaTrackerPaint.setStyle(Style.STROKE);
- hueAlphaTrackerPaint.setStrokeWidth(DrawingUtils.dpToPx(getContext(), 2));
- hueAlphaTrackerPaint.setAntiAlias(true);
-
- alphaTextPaint.setColor(0xff1c1c1c);
- alphaTextPaint.setTextSize(DrawingUtils.dpToPx(getContext(), 14));
- alphaTextPaint.setAntiAlias(true);
- alphaTextPaint.setTextAlign(Align.CENTER);
- alphaTextPaint.setFakeBoldText(true);
-
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if (drawingRect.width() <= 0 || drawingRect.height() <= 0) {
- return;
- }
-
- drawSatValPanel(canvas);
- drawHuePanel(canvas);
- drawAlphaPanel(canvas);
- }
-
- private void drawSatValPanel(Canvas canvas) {
- final Rect rect = satValRect;
-
- if (BORDER_WIDTH_PX > 0) {
- borderPaint.setColor(borderColor);
- canvas.drawRect(drawingRect.left, drawingRect.top,
- rect.right + BORDER_WIDTH_PX,
- rect.bottom + BORDER_WIDTH_PX, borderPaint);
- }
-
- if (valShader == null) {
- //Black gradient has either not been created or the view has been resized.
- valShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, 0xffffffff, 0xff000000, TileMode.CLAMP);
- }
-
- //If the hue has changed we need to recreate the cache.
- if (satValBackgroundCache == null || satValBackgroundCache.value != hue) {
-
- if (satValBackgroundCache == null) {
- satValBackgroundCache = new BitmapCache();
- }
-
- //We create our bitmap in the cache if it doesn't exist.
- if (satValBackgroundCache.bitmap == null) {
- satValBackgroundCache.bitmap = Bitmap
- .createBitmap(rect.width(), rect.height(), Config.ARGB_8888);
- }
-
- //We create the canvas once so we can draw on our bitmap and the hold on to it.
- if (satValBackgroundCache.canvas == null) {
- satValBackgroundCache.canvas = new Canvas(satValBackgroundCache.bitmap);
- }
-
- int rgb = Color.HSVToColor(new float[]{hue, 1f, 1f});
-
- satShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, 0xffffffff, rgb, TileMode.CLAMP);
-
- ComposeShader mShader = new ComposeShader(
- valShader, satShader, PorterDuff.Mode.MULTIPLY);
- satValPaint.setShader(mShader);
-
- // Finally we draw on our canvas, the result will be
- // stored in our bitmap which is already in the cache.
- // Since this is drawn on a canvas not rendered on
- // screen it will automatically not be using the
- // hardware acceleration. And this was the code that
- // wasn't supported by hardware acceleration which mean
- // there is no need to turn it of anymore. The rest of
- // the view will still be hw accelerated.
- satValBackgroundCache.canvas.drawRect(0, 0,
- satValBackgroundCache.bitmap.getWidth(),
- satValBackgroundCache.bitmap.getHeight(),
- satValPaint);
-
- //We set the hue value in our cache to which hue it was drawn with,
- //then we know that if it hasn't changed we can reuse our cached bitmap.
- satValBackgroundCache.value = hue;
-
- }
-
- // We draw our bitmap from the cached, if the hue has changed
- // then it was just recreated otherwise the old one will be used.
- canvas.drawBitmap(satValBackgroundCache.bitmap, null, rect, null);
-
- Point p = satValToPoint(sat, val);
-
- satValTrackerPaint.setColor(0xff000000);
- canvas.drawCircle(p.x, p.y, circleTrackerRadiusPx - DrawingUtils.dpToPx(getContext(), 1), satValTrackerPaint);
-
- satValTrackerPaint.setColor(0xffdddddd);
- canvas.drawCircle(p.x, p.y, circleTrackerRadiusPx, satValTrackerPaint);
-
- }
-
- private void drawHuePanel(Canvas canvas) {
- final Rect rect = hueRect;
-
- if (BORDER_WIDTH_PX > 0) {
- borderPaint.setColor(borderColor);
-
- canvas.drawRect(rect.left - BORDER_WIDTH_PX,
- rect.top - BORDER_WIDTH_PX,
- rect.right + BORDER_WIDTH_PX,
- rect.bottom + BORDER_WIDTH_PX,
- borderPaint);
- }
-
- if (hueBackgroundCache == null) {
- hueBackgroundCache = new BitmapCache();
- hueBackgroundCache.bitmap = Bitmap.createBitmap(rect.width(), rect.height(), Config.ARGB_8888);
- hueBackgroundCache.canvas = new Canvas(hueBackgroundCache.bitmap);
-
- int[] hueColors = new int[(int) (rect.height() + 0.5f)];
-
- // Generate array of all colors, will be drawn as individual lines.
- float h = 360f;
- for (int i = 0; i < hueColors.length; i++) {
- hueColors[i] = Color.HSVToColor(new float[]{h, 1f, 1f});
- h -= 360f / hueColors.length;
- }
-
- // Time to draw the hue color gradient,
- // its drawn as individual lines which
- // will be quite many when the resolution is high
- // and/or the panel is large.
- Paint linePaint = new Paint();
- linePaint.setStrokeWidth(0);
- for (int i = 0; i < hueColors.length; i++) {
- linePaint.setColor(hueColors[i]);
- hueBackgroundCache.canvas.drawLine(0, i, hueBackgroundCache.bitmap.getWidth(), i, linePaint);
- }
- }
-
- canvas.drawBitmap(hueBackgroundCache.bitmap, null, rect, null);
-
- Point p = hueToPoint(hue);
-
- RectF r = new RectF();
- r.left = rect.left - sliderTrackerOffsetPx;
- r.right = rect.right + sliderTrackerOffsetPx;
- r.top = p.y - (sliderTrackerSizePx / 2);
- r.bottom = p.y + (sliderTrackerSizePx / 2);
-
- canvas.drawRoundRect(r, 2, 2, hueAlphaTrackerPaint);
- }
-
- private void drawAlphaPanel(Canvas canvas) {
- /*
- * Will be drawn with hw acceleration, very fast.
- * Also the AlphaPatternDrawable is backed by a bitmap
- * generated only once if the size does not change.
- */
-
- if (!showAlphaPanel || alphaRect == null || alphaPatternDrawable == null) return;
-
- final Rect rect = alphaRect;
-
- if (BORDER_WIDTH_PX > 0) {
- borderPaint.setColor(borderColor);
- canvas.drawRect(rect.left - BORDER_WIDTH_PX,
- rect.top - BORDER_WIDTH_PX,
- rect.right + BORDER_WIDTH_PX,
- rect.bottom + BORDER_WIDTH_PX,
- borderPaint);
- }
-
- alphaPatternDrawable.draw(canvas);
-
- float[] hsv = new float[]{hue, sat, val};
- int color = Color.HSVToColor(hsv);
- int acolor = Color.HSVToColor(0, hsv);
-
- alphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
- color, acolor, TileMode.CLAMP);
-
- alphaPaint.setShader(alphaShader);
-
- canvas.drawRect(rect, alphaPaint);
-
- if (alphaSliderText != null && !alphaSliderText.equals("")) {
- canvas.drawText(alphaSliderText, rect.centerX(), rect.centerY() + DrawingUtils.dpToPx(getContext(), 4), alphaTextPaint);
- }
-
- Point p = alphaToPoint(alpha);
-
- RectF r = new RectF();
- r.left = p.x - (sliderTrackerSizePx / 2);
- r.right = p.x + (sliderTrackerSizePx / 2);
- r.top = rect.top - sliderTrackerOffsetPx;
- r.bottom = rect.bottom + sliderTrackerOffsetPx;
-
- canvas.drawRoundRect(r, 2, 2, hueAlphaTrackerPaint);
- }
-
- private Point hueToPoint(float hue) {
-
- final Rect rect = hueRect;
- final float height = rect.height();
-
- Point p = new Point();
-
- p.y = (int) (height - (hue * height / 360f) + rect.top);
- p.x = rect.left;
-
- return p;
- }
-
- private Point satValToPoint(float sat, float val) {
-
- final Rect rect = satValRect;
- final float height = rect.height();
- final float width = rect.width();
-
- Point p = new Point();
-
- p.x = (int) (sat * width + rect.left);
- p.y = (int) ((1f - val) * height + rect.top);
-
- return p;
- }
-
- private Point alphaToPoint(int alpha) {
-
- final Rect rect = alphaRect;
- final float width = rect.width();
-
- Point p = new Point();
-
- p.x = (int) (width - (alpha * width / 0xff) + rect.left);
- p.y = rect.top;
-
- return p;
-
- }
-
- private float[] pointToSatVal(float x, float y) {
-
- final Rect rect = satValRect;
- float[] result = new float[2];
-
- float width = rect.width();
- float height = rect.height();
-
- if (x < rect.left) {
- x = 0f;
- } else if (x > rect.right) {
- x = width;
- } else {
- x = x - rect.left;
- }
-
- if (y < rect.top) {
- y = 0f;
- } else if (y > rect.bottom) {
- y = height;
- } else {
- y = y - rect.top;
- }
-
- result[0] = 1.f / width * x;
- result[1] = 1.f - (1.f / height * y);
-
- return result;
- }
-
- private float pointToHue(float y) {
-
- final Rect rect = hueRect;
-
- float height = rect.height();
-
- if (y < rect.top) {
- y = 0f;
- } else if (y > rect.bottom) {
- y = height;
- } else {
- y = y - rect.top;
- }
-
- return 360f - (y * 360f / height);
- }
-
- private int pointToAlpha(int x) {
-
- final Rect rect = alphaRect;
- final int width = rect.width();
-
- if (x < rect.left) {
- x = 0;
- } else if (x > rect.right) {
- x = width;
- } else {
- x = x - rect.left;
- }
-
- return 0xff - (x * 0xff / width);
-
- }
-
- @SuppressLint("ClickableViewAccessibility")
- @Override
- public boolean onTouchEvent(MotionEvent event) {
-
- try {
- this.getParent().requestDisallowInterceptTouchEvent(true);
- } catch (Throwable ignored) {
- }
-
- boolean update = false;
-
- switch (event.getAction()) {
-
- case MotionEvent.ACTION_DOWN:
- startTouchPoint = new Point((int) event.getX(), (int) event.getY());
- update = moveTrackersIfNeeded(event);
- break;
- case MotionEvent.ACTION_MOVE:
- update = moveTrackersIfNeeded(event);
- break;
- case MotionEvent.ACTION_UP:
- startTouchPoint = null;
- update = moveTrackersIfNeeded(event);
- break;
- }
-
- if (update) {
- if (onColorChangedListener != null) {
- onColorChangedListener.onColorChanged(Color.HSVToColor(alpha, new float[]{hue, sat, val}));
- }
- invalidate();
- return true;
- }
-
- return super.onTouchEvent(event);
- }
-
- private boolean moveTrackersIfNeeded(MotionEvent event) {
- if (startTouchPoint == null) {
- return false;
- }
-
- boolean update = false;
-
- int startX = startTouchPoint.x;
- int startY = startTouchPoint.y;
-
- if (hueRect.contains(startX, startY)) {
- hue = pointToHue(event.getY());
-
- update = true;
- } else if (satValRect.contains(startX, startY)) {
- float[] result = pointToSatVal(event.getX(), event.getY());
-
- sat = result[0];
- val = result[1];
-
- update = true;
- } else if (alphaRect != null && alphaRect.contains(startX, startY)) {
- alpha = pointToAlpha((int) event.getX());
-
- update = true;
- }
-
- return update;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int finalWidth;
- int finalHeight;
-
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-
- int widthAllowed = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
- int heightAllowed =
- MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop();
-
- if (widthMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.EXACTLY) {
- //A exact value has been set in either direction, we need to stay within this size.
-
- if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
- //The with has been specified exactly, we need to adopt the height to fit.
- int h = (widthAllowed - panelSpacingPx - huePanelWidthPx);
-
- if (showAlphaPanel) {
- h += panelSpacingPx + alphaPanelHeightPx;
- }
-
- //We can't fit the view in this container, set the size to whatever was allowed.
- finalHeight = Math.min(h, heightAllowed);
-
- finalWidth = widthAllowed;
-
- } else if (widthMode != MeasureSpec.EXACTLY) {
- //The height has been specified exactly, we need to stay within this height and adopt the width.
-
- int w = (heightAllowed + panelSpacingPx + huePanelWidthPx);
-
- if (showAlphaPanel) {
- w -= (panelSpacingPx + alphaPanelHeightPx);
- }
-
- //we can't fit within this container, set the size to whatever was allowed.
- finalWidth = Math.min(w, widthAllowed);
-
- finalHeight = heightAllowed;
-
- } else {
- //If we get here the dev has set the width and height to exact sizes. For example match_parent or 300dp.
- //This will mean that the sat/val panel will not be square but it doesn't matter. It will work anyway.
- //In all other senarios our goal is to make that panel square.
-
- //We set the sizes to exactly what we were told.
- finalWidth = widthAllowed;
- finalHeight = heightAllowed;
- }
-
- } else {
- //If no exact size has been set we try to make our view as big as possible
- //within the allowed space.
-
- //Calculate the needed width to layout using max allowed height.
- int widthNeeded = (heightAllowed + panelSpacingPx + huePanelWidthPx);
-
- //Calculate the needed height to layout using max allowed width.
- int heightNeeded = (widthAllowed - panelSpacingPx - huePanelWidthPx);
-
- if (showAlphaPanel) {
- widthNeeded -= (panelSpacingPx + alphaPanelHeightPx);
- heightNeeded += panelSpacingPx + alphaPanelHeightPx;
- }
-
- boolean widthOk = false;
- boolean heightOk = false;
-
- if (widthNeeded <= widthAllowed) {
- widthOk = true;
- }
-
- if (heightNeeded <= heightAllowed) {
- heightOk = true;
- }
-
- if (widthOk && heightOk) {
- finalWidth = widthAllowed;
- finalHeight = heightNeeded;
- } else if (widthOk) {
- finalHeight = heightAllowed;
- finalWidth = widthNeeded;
- } else if (heightOk) {
- finalHeight = heightNeeded;
- finalWidth = widthAllowed;
- } else {
- finalHeight = heightAllowed;
- finalWidth = widthAllowed;
- }
-
- }
-
- setMeasuredDimension(finalWidth + getPaddingLeft() + getPaddingRight(),
- finalHeight + getPaddingTop() + getPaddingBottom());
- }
-
- @Override
- public int getPaddingTop() {
- return Math.max(super.getPaddingTop(), mRequiredPadding);
- }
-
- @Override
- public int getPaddingBottom() {
- return Math.max(super.getPaddingBottom(), mRequiredPadding);
- }
-
- @Override
- public int getPaddingLeft() {
- return Math.max(super.getPaddingLeft(), mRequiredPadding);
- }
-
- @Override
- public int getPaddingRight() {
- return Math.max(super.getPaddingRight(), mRequiredPadding);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- drawingRect = new Rect();
- drawingRect.left = getPaddingLeft();
- drawingRect.right = w - getPaddingRight();
- drawingRect.top = getPaddingTop();
- drawingRect.bottom = h - getPaddingBottom();
-
- //The need to be recreated because they depend on the size of the view.
- valShader = null;
- satShader = null;
- alphaShader = null;
-
- // Clear those bitmap caches since the size may have changed.
- satValBackgroundCache = null;
- hueBackgroundCache = null;
-
- setUpSatValRect();
- setUpHueRect();
- setUpAlphaRect();
- }
-
- private void setUpSatValRect() {
- //Calculate the size for the big color rectangle.
- final Rect dRect = drawingRect;
-
- int left = dRect.left + BORDER_WIDTH_PX;
- int top = dRect.top + BORDER_WIDTH_PX;
- int bottom = dRect.bottom - BORDER_WIDTH_PX;
- int right = dRect.right - BORDER_WIDTH_PX - panelSpacingPx - huePanelWidthPx;
-
- if (showAlphaPanel) {
- bottom -= (alphaPanelHeightPx + panelSpacingPx);
- }
-
- satValRect = new Rect(left, top, right, bottom);
- }
-
- private void setUpHueRect() {
- //Calculate the size for the hue slider on the left.
- final Rect dRect = drawingRect;
-
- int left = dRect.right - huePanelWidthPx + BORDER_WIDTH_PX;
- int top = dRect.top + BORDER_WIDTH_PX;
- int bottom = dRect.bottom - BORDER_WIDTH_PX -
- (showAlphaPanel ? (panelSpacingPx + alphaPanelHeightPx) : 0);
- int right = dRect.right - BORDER_WIDTH_PX;
-
- hueRect = new Rect(left, top, right, bottom);
- }
-
- private void setUpAlphaRect() {
-
- if (!showAlphaPanel) return;
-
- final Rect dRect = drawingRect;
-
- int left = dRect.left + BORDER_WIDTH_PX;
- int top = dRect.bottom - alphaPanelHeightPx + BORDER_WIDTH_PX;
- int bottom = dRect.bottom - BORDER_WIDTH_PX;
- int right = dRect.right - BORDER_WIDTH_PX;
-
- alphaRect = new Rect(left, top, right, bottom);
-
- alphaPatternDrawable = new AlphaPatternDrawable(DrawingUtils.dpToPx(getContext(), 4));
- alphaPatternDrawable.setBounds(Math.round(alphaRect.left), Math
- .round(alphaRect.top), Math.round(alphaRect.right), Math
- .round(alphaRect.bottom));
- }
-
- /**
- * Set a OnColorChangedListener to get notified when the color
- * selected by the user has changed.
- *
- * @param listener the listener
- */
- public void setOnColorChangedListener(OnColorChangedListener listener) {
- onColorChangedListener = listener;
- }
-
- /**
- * Get the current color this view is showing.
- *
- * @return the current color.
- */
- public int getColor() {
- return Color.HSVToColor(alpha, new float[]{hue, sat, val});
- }
-
- /**
- * Set the color the view should show.
- *
- * @param color The color that should be selected. #argb
- */
- public void setColor(int color) {
- setColor(color, false);
- }
-
- /**
- * Set the color this view should show.
- *
- * @param color The color that should be selected. #argb
- * @param callback If you want to get a callback to your OnColorChangedListener.
- */
- public void setColor(int color, boolean callback) {
-
- int alpha = Color.alpha(color);
- int red = Color.red(color);
- int blue = Color.blue(color);
- int green = Color.green(color);
-
- float[] hsv = new float[3];
-
- Color.RGBToHSV(red, green, blue, hsv);
-
- this.alpha = alpha;
- hue = hsv[0];
- sat = hsv[1];
- val = hsv[2];
-
- if (callback && onColorChangedListener != null) {
- onColorChangedListener
- .onColorChanged(Color.HSVToColor(this.alpha, new float[]{hue, sat, val}));
- }
-
- invalidate();
- }
-
- /**
- * Set if the user is allowed to adjust the alpha panel. Default is false.
- * If it is set to false no alpha will be set.
- *
- * @param visible {@code true} to show the alpha slider
- */
- public void setAlphaSliderVisible(boolean visible) {
- if (showAlphaPanel != visible) {
- showAlphaPanel = visible;
-
- /*
- * Force recreation.
- */
- valShader = null;
- satShader = null;
- alphaShader = null;
- hueBackgroundCache = null;
- satValBackgroundCache = null;
-
- requestLayout();
- }
-
- }
-
- /**
- * Set the color of the tracker slider on the hue and alpha panel.
- *
- * @param color a color value
- */
- @SuppressWarnings("unused")
- public void setSliderTrackerColor(int color) {
- sliderTrackerColor = color;
- hueAlphaTrackerPaint.setColor(sliderTrackerColor);
- invalidate();
- }
-
- /**
- * Get color of the tracker slider on the hue and alpha panel.
- *
- * @return the color value
- */
- @SuppressWarnings("unused")
- public int getSliderTrackerColor() {
- return sliderTrackerColor;
- }
-
- /**
- * Set the color of the border surrounding all panels.
- *
- * @param color a color value
- */
- @SuppressWarnings("unused")
- public void setBorderColor(int color) {
- borderColor = color;
- invalidate();
- }
-
- /**
- * Get the color of the border surrounding all panels.
- */
- @SuppressWarnings("unused")
- public int getBorderColor() {
- return borderColor;
- }
-
- /**
- * Set the text that should be shown in the
- * alpha slider. Set to null to disable text.
- *
- * @param res string resource id.
- */
- @SuppressWarnings("unused")
- public void setAlphaSliderText(int res) {
- String text = getContext().getString(res);
- setAlphaSliderText(text);
- }
-
- /**
- * Set the text that should be shown in the
- * alpha slider. Set to null to disable text.
- *
- * @param text Text that should be shown.
- */
- public void setAlphaSliderText(String text) {
- alphaSliderText = text;
- invalidate();
- }
-
- /**
- * Get the current value of the text
- * that will be shown in the alpha
- * slider.
- *
- * @return the slider text
- */
- @SuppressWarnings("unused")
- public String getAlphaSliderText() {
- return alphaSliderText;
- }
-
- private static class BitmapCache {
- public Canvas canvas;
- public Bitmap bitmap;
- public float value;
- }
-
- public interface OnColorChangedListener {
- void onColorChanged(int newColor);
- }
-
-}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerView.kt b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerView.kt
new file mode 100644
index 00000000..97f1e523
--- /dev/null
+++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerView.kt
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) 2017 JRummy Apps Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jrummyapps.android.colorpicker
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.*
+import android.graphics.Paint.Align
+import android.graphics.Shader.TileMode
+import android.os.Bundle
+import android.os.Parcelable
+import android.util.AttributeSet
+import android.util.TypedValue
+import android.view.MotionEvent
+import android.view.View
+import androidx.annotation.ColorInt
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Displays a color picker to the user and allow them to select a color. A slider for the alpha channel is also available.
+ * Enable it by setting setAlphaSliderVisible(boolean) to true.
+ */
+class ColorPickerView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+) : View(context, attrs, defStyle) {
+
+ companion object {
+ private const val DEFAULT_BORDER_COLOR = -0x919192
+ private const val DEFAULT_SLIDER_COLOR = -0x424243
+ private const val HUE_PANEL_WDITH_DP = 30
+ private const val ALPHA_PANEL_HEIGH_DP = 20
+ private const val PANEL_SPACING_DP = 10
+ private const val CIRCLE_TRACKER_RADIUS_DP = 5
+ private const val SLIDER_TRACKER_SIZE_DP = 4
+ private const val SLIDER_TRACKER_OFFSET_DP = 2
+
+ /**
+ * The width in pixels of the border
+ * surrounding all color panels.
+ */
+ private const val BORDER_WIDTH_PX = 1
+ }
+
+ interface OnColorChangedListener {
+ fun onColorChanged(newColor: Int)
+ }
+
+ private class BitmapCache(
+ var canvas: Canvas? = null,
+ var bitmap: Bitmap? = null,
+ var value: Float = 0f,
+ )
+
+ /**
+ * The width in px of the hue panel.
+ */
+ private val huePanelWidthPx =
+ DrawingUtils.dpToPx(context, HUE_PANEL_WDITH_DP.toFloat())
+
+ /**
+ * The height in px of the alpha panel
+ */
+ private val alphaPanelHeightPx =
+ DrawingUtils.dpToPx(context, ALPHA_PANEL_HEIGH_DP.toFloat())
+
+ /**
+ * The distance in px between the different
+ * color panels.
+ */
+ private val panelSpacingPx =
+ DrawingUtils.dpToPx(context, PANEL_SPACING_DP.toFloat())
+
+ /**
+ * The radius in px of the color palette tracker circle.
+ */
+ private val circleTrackerRadiusPx =
+ DrawingUtils.dpToPx(context, CIRCLE_TRACKER_RADIUS_DP.toFloat())
+
+ /**
+ * The px which the tracker of the hue or alpha panel
+ * will extend outside of its bounds.
+ */
+ private val sliderTrackerOffsetPx =
+ DrawingUtils.dpToPx(context, SLIDER_TRACKER_OFFSET_DP.toFloat())
+
+ /**
+ * Height of slider tracker on hue panel,
+ * width of slider on alpha panel.
+ */
+ private val sliderTrackerSizePx =
+ DrawingUtils.dpToPx(context, SLIDER_TRACKER_SIZE_DP.toFloat())
+
+ /**
+ * the current value of the text that will be shown in the alpha slider.
+ * null to disable text.
+ */
+ @Suppress("MemberVisibilityCanBePrivate")
+ var alphaSliderText: String? = null
+ set(value) {
+ if (field != value) {
+ field = value
+ invalidate()
+ }
+ }
+
+ // the color the view should show.
+ var color: Int
+ get() = Color.HSVToColor(alpha, floatArrayOf(hue, sat, bri))
+ set(color) {
+ setColor(color, false)
+ }
+
+ @ColorInt
+ var sliderTrackerColor = DEFAULT_SLIDER_COLOR
+ set(value) {
+ field = value
+ hueAlphaTrackerPaint.color = value
+ invalidate()
+ }
+
+ @ColorInt
+ var borderColor = DEFAULT_BORDER_COLOR
+ set(value) {
+ if (field != value) {
+ field = value
+ invalidate()
+ }
+ }
+
+ private val satValPaint = Paint()
+
+ private val satValTrackerPaint = Paint().apply {
+ style = Paint.Style.STROKE
+ strokeWidth = DrawingUtils.dpToPx(context, 2f).toFloat()
+ isAntiAlias = true
+ }
+
+ private val alphaPaint = Paint()
+
+ private val alphaTextPaint = Paint().apply {
+ color = -0xe3e3e4
+ textSize = DrawingUtils.dpToPx(context, 14f).toFloat()
+ isAntiAlias = true
+ textAlign = Align.CENTER
+ isFakeBoldText = true
+ }
+
+ private val hueAlphaTrackerPaint = Paint().apply {
+ color = sliderTrackerColor
+ style = Paint.Style.STROKE
+ strokeWidth = DrawingUtils.dpToPx(context, 2f).toFloat()
+ isAntiAlias = true
+ }
+
+ private val borderPaint = Paint()
+
+ private var valShader: Shader? = null
+ private var satShader: Shader? = null
+ private var alphaShader: Shader? = null
+
+ /*
+ * We cache a bitmap of the sat/val panel which is expensive to draw each time.
+ * We can reuse it when the user is sliding the circle picker as long as the hue isn't changed.
+ */
+ private var satValBackgroundCache: BitmapCache? = null
+
+ /* We cache the hue background to since its also very expensive now. */
+ private var hueBackgroundCache: BitmapCache? = null
+
+ /* Current values */
+ private var alpha = 0xff
+ private var hue = 360f
+ private var sat = 0f
+ private var bri = 0f
+ private var showAlphaPanel = false
+
+ /**
+ * Minimum required padding. The offset from the
+ * edge we must have or else the finger tracker will
+ * get clipped when it's drawn outside of the view.
+ */
+ private val mRequiredPadding =
+ context.resources.getDimensionPixelSize(R.dimen.cpv_required_padding)
+
+ /**
+ * The Rect in which we are allowed to draw.
+ * Trackers can extend outside slightly,
+ * due to the required padding we have set.
+ */
+ private var drawingRect: Rect? = null
+ private var satValRect: Rect? = null
+ private var hueRect: Rect? = null
+ private var alphaRect: Rect? = null
+ private var startTouchPoint: Point? = null
+ private var alphaPatternDrawable: AlphaPatternDrawable? = null
+
+ /**
+ * OnColorChangedListener to get notified when the color selected by the user has changed.
+ */
+ var onColorChangedListener: OnColorChangedListener? = null
+
+
+ public override fun onSaveInstanceState(): Parcelable {
+ val state = Bundle()
+ state.putParcelable("instanceState", super.onSaveInstanceState())
+ state.putInt("alpha", alpha)
+ state.putFloat("hue", hue)
+ state.putFloat("sat", sat)
+ state.putFloat("val", bri)
+ state.putBoolean("show_alpha", showAlphaPanel)
+ state.putString("alpha_text", alphaSliderText)
+ return state
+ }
+
+ public override fun onRestoreInstanceState(stateArg: Parcelable) {
+ var state: Parcelable? = stateArg
+ if (state is Bundle) {
+ val bundle = state
+ alpha = bundle.getInt("alpha")
+ hue = bundle.getFloat("hue")
+ sat = bundle.getFloat("sat")
+ bri = bundle.getFloat("val")
+ showAlphaPanel = bundle.getBoolean("show_alpha")
+ alphaSliderText = bundle.getString("alpha_text")
+ state = bundle.getParcelable("instanceState")
+ }
+ super.onRestoreInstanceState(state)
+ }
+
+ init {
+ //Load those if set in xml resource file.
+ var a = context.obtainStyledAttributes(attrs, R.styleable.ColorPickerView)
+ showAlphaPanel = a.getBoolean(R.styleable.ColorPickerView_cpv_alphaChannelVisible, false)
+ alphaSliderText = a.getString(R.styleable.ColorPickerView_cpv_alphaChannelText)
+ sliderTrackerColor = a.getColor(R.styleable.ColorPickerView_cpv_sliderColor, -0x424243)
+ borderColor = a.getColor(R.styleable.ColorPickerView_cpv_borderColor, -0x919192)
+ a.recycle()
+
+ // If no specific border/slider color has been
+ // set we take the default secondary text color
+ // as border/slider color. Thus it will adopt
+ // to theme changes automatically.
+ val value = TypedValue()
+ a = context.obtainStyledAttributes(
+ value.data,
+ intArrayOf(android.R.attr.textColorSecondary)
+ )
+ if (borderColor == DEFAULT_BORDER_COLOR) {
+ borderColor = a.getColor(0, DEFAULT_BORDER_COLOR)
+ }
+ if (sliderTrackerColor == DEFAULT_SLIDER_COLOR) {
+ sliderTrackerColor = a.getColor(0, DEFAULT_SLIDER_COLOR)
+ }
+ a.recycle()
+
+ //Needed for receiving trackball motion events.
+ isFocusable = true
+ isFocusableInTouchMode = true
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ val drawingRect = this.drawingRect
+ if (drawingRect == null || drawingRect.width() <= 0 || drawingRect.height() <= 0) {
+ return
+ }
+ drawSatValPanel(canvas)
+ drawHuePanel(canvas)
+ drawAlphaPanel(canvas)
+ }
+
+ private fun drawSatValPanel(canvas: Canvas) {
+ val rect = this.satValRect ?: return
+ val drawingRect = this.drawingRect ?: return
+
+ if (BORDER_WIDTH_PX > 0) {
+ borderPaint.color = borderColor
+ canvas.drawRect(
+ drawingRect.left.toFloat(),
+ drawingRect.top.toFloat(),
+ (rect.right + BORDER_WIDTH_PX).toFloat(),
+ (rect.bottom + BORDER_WIDTH_PX).toFloat(), borderPaint
+ )
+ }
+ if (valShader == null) {
+ //Black gradient has either not been created or the view has been resized.
+ valShader = LinearGradient(
+ rect.left.toFloat(),
+ rect.top.toFloat(),
+ rect.left.toFloat(),
+ rect.bottom.toFloat(),
+ -0x1,
+ -0x1000000,
+ TileMode.CLAMP
+ )
+ }
+
+ //If the hue has changed we need to recreate the cache.
+ if (satValBackgroundCache == null || satValBackgroundCache!!.value != hue) {
+ if (satValBackgroundCache == null) {
+ satValBackgroundCache = BitmapCache()
+ }
+
+ //We create our bitmap in the cache if it doesn't exist.
+ if (satValBackgroundCache!!.bitmap == null) {
+ satValBackgroundCache!!.bitmap = Bitmap
+ .createBitmap(rect.width(), rect.height(), Bitmap.Config.ARGB_8888)
+ }
+
+ //We create the canvas once so we can draw on our bitmap and the hold on to it.
+ if (satValBackgroundCache!!.canvas == null) {
+ satValBackgroundCache!!.canvas = Canvas(satValBackgroundCache!!.bitmap!!)
+ }
+ val rgb = Color.HSVToColor(floatArrayOf(hue, 1f, 1f))
+ satShader = LinearGradient(
+ rect.left.toFloat(),
+ rect.top.toFloat(),
+ rect.right.toFloat(),
+ rect.top.toFloat(),
+ -0x1,
+ rgb,
+ TileMode.CLAMP
+ )
+ satValPaint.shader = ComposeShader(
+ valShader!!,
+ satShader!!,
+ PorterDuff.Mode.MULTIPLY
+ )
+
+ // Finally we draw on our canvas, the result will be
+ // stored in our bitmap which is already in the cache.
+ // Since this is drawn on a canvas not rendered on
+ // screen it will automatically not be using the
+ // hardware acceleration. And this was the code that
+ // wasn't supported by hardware acceleration which mean
+ // there is no need to turn it of anymore. The rest of
+ // the view will still be hw accelerated.
+ satValBackgroundCache!!.canvas!!.drawRect(
+ 0f, 0f,
+ satValBackgroundCache!!.bitmap!!.width.toFloat(),
+ satValBackgroundCache!!.bitmap!!.height.toFloat(),
+ satValPaint
+ )
+
+ //We set the hue value in our cache to which hue it was drawn with,
+ //then we know that if it hasn't changed we can reuse our cached bitmap.
+ satValBackgroundCache!!.value = hue
+ }
+
+ // We draw our bitmap from the cached, if the hue has changed
+ // then it was just recreated otherwise the old one will be used.
+ canvas.drawBitmap(satValBackgroundCache!!.bitmap!!, null, rect, null)
+ val p = satValToPoint(sat, bri)
+ satValTrackerPaint.color = -0x1000000
+ canvas.drawCircle(
+ p.x.toFloat(), p.y.toFloat(), (circleTrackerRadiusPx - DrawingUtils.dpToPx(
+ context, 1f
+ )).toFloat(), satValTrackerPaint
+ )
+ satValTrackerPaint.color = -0x222223
+ canvas.drawCircle(
+ p.x.toFloat(),
+ p.y.toFloat(),
+ circleTrackerRadiusPx.toFloat(),
+ satValTrackerPaint
+ )
+ }
+
+ private fun drawHuePanel(canvas: Canvas) {
+ val rect = hueRect
+ if (BORDER_WIDTH_PX > 0) {
+ borderPaint.color = borderColor
+ canvas.drawRect(
+ (rect!!.left - BORDER_WIDTH_PX).toFloat(), (
+ rect.top - BORDER_WIDTH_PX).toFloat(), (
+ rect.right + BORDER_WIDTH_PX).toFloat(), (
+ rect.bottom + BORDER_WIDTH_PX).toFloat(),
+ borderPaint
+ )
+ }
+ if (hueBackgroundCache == null) {
+ val hueBackgroundCache = BitmapCache()
+ .also { this.hueBackgroundCache = it }
+
+ Bitmap.createBitmap(rect!!.width(), rect.height(), Bitmap.Config.ARGB_8888)
+ .also {
+ hueBackgroundCache.bitmap = it
+ hueBackgroundCache.canvas = Canvas(it)
+ }
+
+ val hueColors = IntArray((rect.height() + 0.5f).toInt())
+
+ // Generate array of all colors, will be drawn as individual lines.
+ var h = 360f
+ for (i in hueColors.indices) {
+ hueColors[i] = Color.HSVToColor(floatArrayOf(h, 1f, 1f))
+ h -= 360f / hueColors.size
+ }
+
+ // Time to draw the hue color gradient,
+ // its drawn as individual lines which
+ // will be quite many when the resolution is high
+ // and/or the panel is large.
+ val linePaint = Paint()
+ linePaint.strokeWidth = 0f
+ for (i in hueColors.indices) {
+ linePaint.color = hueColors[i]
+ hueBackgroundCache.canvas?.drawLine(
+ 0f,
+ i.toFloat(),
+ hueBackgroundCache.bitmap?.width?.toFloat() ?: 0f,
+ i.toFloat(),
+ linePaint
+ )
+ }
+ }
+ canvas.drawBitmap(hueBackgroundCache!!.bitmap!!, null, rect!!, null)
+ val p = hueToPoint(hue)
+ val r = RectF()
+ r.left = (rect.left - sliderTrackerOffsetPx).toFloat()
+ r.right = (rect.right + sliderTrackerOffsetPx).toFloat()
+ r.top = p.y - sliderTrackerSizePx / 2f
+ r.bottom = p.y + sliderTrackerSizePx / 2f
+ canvas.drawRoundRect(r, 2f, 2f, hueAlphaTrackerPaint)
+ }
+
+ private fun drawAlphaPanel(canvas: Canvas) {
+ if (!showAlphaPanel) return
+ val rect = this.alphaRect ?: return
+ val alphaPatternDrawable = this.alphaPatternDrawable ?: return
+
+ /*
+ * Will be drawn with hw acceleration, very fast.
+ * Also the AlphaPatternDrawable is backed by a bitmap
+ * generated only once if the size does not change.
+ */
+ if (BORDER_WIDTH_PX > 0) {
+ borderPaint.color = borderColor
+ canvas.drawRect(
+ (rect.left - BORDER_WIDTH_PX).toFloat(), (
+ rect.top - BORDER_WIDTH_PX).toFloat(), (
+ rect.right + BORDER_WIDTH_PX).toFloat(), (
+ rect.bottom + BORDER_WIDTH_PX).toFloat(),
+ borderPaint
+ )
+ }
+ alphaPatternDrawable.draw(canvas)
+ val hsv = floatArrayOf(hue, sat, bri)
+ val color = Color.HSVToColor(hsv)
+ val acolor = Color.HSVToColor(0, hsv)
+ alphaShader = LinearGradient(
+ rect.left.toFloat(),
+ rect.top.toFloat(),
+ rect.right.toFloat(),
+ rect.top.toFloat(),
+ color, acolor, TileMode.CLAMP
+ )
+ alphaPaint.shader = alphaShader
+ canvas.drawRect(rect, alphaPaint)
+
+ alphaSliderText
+ ?.takeIf { it.isNotEmpty() }
+ ?.let {
+ canvas.drawText(
+ it,
+ rect.centerX().toFloat(),
+ (rect.centerY() + DrawingUtils.dpToPx(
+ context, 4f
+ )).toFloat(),
+ alphaTextPaint
+ )
+ }
+
+ val p = alphaToPoint(alpha)
+ val r = RectF()
+ r.left = p.x - sliderTrackerSizePx / 2f
+ r.right = p.x + sliderTrackerSizePx / 2f
+ r.top = (rect.top - sliderTrackerOffsetPx).toFloat()
+ r.bottom = (rect.bottom + sliderTrackerOffsetPx).toFloat()
+ canvas.drawRoundRect(r, 2f, 2f, hueAlphaTrackerPaint)
+ }
+
+ private fun hueToPoint(hue: Float): Point {
+ val rect = hueRect
+ val height = rect!!.height().toFloat()
+ val p = Point()
+ p.y = (height - hue * height / 360f + rect.top).toInt()
+ p.x = rect.left
+ return p
+ }
+
+ private fun satValToPoint(sat: Float, `val`: Float): Point {
+ val rect = satValRect
+ val height = rect!!.height().toFloat()
+ val width = rect.width().toFloat()
+ val p = Point()
+ p.x = (sat * width + rect.left).toInt()
+ p.y = ((1f - `val`) * height + rect.top).toInt()
+ return p
+ }
+
+ private fun alphaToPoint(alpha: Int): Point {
+ val rect = alphaRect
+ val width = rect!!.width().toFloat()
+ val p = Point()
+ p.x = (width - alpha * width / 0xff + rect.left).toInt()
+ p.y = rect.top
+ return p
+ }
+
+ private fun pointToSatVal(xArg: Float, yArg: Float): FloatArray {
+ var x = xArg
+ var y = yArg
+ val rect = satValRect
+ val result = FloatArray(2)
+ val width = rect!!.width().toFloat()
+ val height = rect.height().toFloat()
+ x = when {
+ x < rect.left -> 0f
+ x > rect.right -> width
+ else -> x - rect.left
+ }
+ y = when {
+ y < rect.top -> 0f
+ y > rect.bottom -> height
+ else -> y - rect.top
+ }
+ result[0] = 1f / width * x
+ result[1] = 1f - 1f / height * y
+ return result
+ }
+
+ private fun pointToHue(yArg: Float): Float {
+ var y = yArg
+ val rect = hueRect
+ val height = rect!!.height().toFloat()
+ y = when {
+ y < rect.top -> 0f
+ y > rect.bottom -> height
+ else -> y - rect.top
+ }
+ return 360f - y * 360f / height
+ }
+
+ private fun pointToAlpha(xArg: Int): Int {
+ var x = xArg
+ val rect = alphaRect
+ val width = rect!!.width()
+ x = when {
+ x < rect.left -> 0
+ x > rect.right -> width
+ else -> x - rect.left
+ }
+ return 0xff - x * 0xff / width
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ try {
+ this.parent.requestDisallowInterceptTouchEvent(true)
+ } catch (ignored: Throwable) {
+ }
+ var update = false
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ startTouchPoint = Point(event.x.toInt(), event.y.toInt())
+ update = moveTrackersIfNeeded(event)
+ }
+ MotionEvent.ACTION_MOVE -> update = moveTrackersIfNeeded(event)
+ MotionEvent.ACTION_UP -> {
+ startTouchPoint = null
+ update = moveTrackersIfNeeded(event)
+ }
+ }
+ if (update) {
+ onColorChangedListener?.onColorChanged(
+ Color.HSVToColor(
+ alpha,
+ floatArrayOf(hue, sat, bri)
+ )
+ )
+ invalidate()
+ return true
+ }
+ return super.onTouchEvent(event)
+ }
+
+ private fun moveTrackersIfNeeded(event: MotionEvent): Boolean {
+ val startTouchPoint = this.startTouchPoint ?: return false
+ val startX = startTouchPoint.x
+ val startY = startTouchPoint.y
+ return when {
+ hueRect?.contains(startX, startY) == true -> {
+ hue = pointToHue(event.y)
+ true
+ }
+ satValRect?.contains(startX, startY) == true -> {
+ val result = pointToSatVal(event.x, event.y)
+ sat = result[0]
+ bri = result[1]
+ true
+ }
+ alphaRect?.contains(startX, startY) == true -> {
+ alpha = pointToAlpha(event.x.toInt())
+ true
+ }
+ else -> false
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val finalWidth: Int
+ val finalHeight: Int
+ val widthMode = MeasureSpec.getMode(widthMeasureSpec)
+ val heightMode = MeasureSpec.getMode(heightMeasureSpec)
+ val widthAllowed = MeasureSpec.getSize(widthMeasureSpec) - paddingLeft - paddingRight
+ val heightAllowed = MeasureSpec.getSize(heightMeasureSpec) - paddingBottom - paddingTop
+ if (widthMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.EXACTLY) {
+ //A exact value has been set in either direction, we need to stay within this size.
+ if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
+ //The with has been specified exactly, we need to adopt the height to fit.
+ var h = widthAllowed - panelSpacingPx - huePanelWidthPx
+ if (showAlphaPanel) {
+ h += panelSpacingPx + alphaPanelHeightPx
+ }
+
+ //We can't fit the view in this container, set the size to whatever was allowed.
+ finalHeight = min(h, heightAllowed)
+ finalWidth = widthAllowed
+ } else if (widthMode != MeasureSpec.EXACTLY) {
+ //The height has been specified exactly, we need to stay within this height and adopt the width.
+ var w = heightAllowed + panelSpacingPx + huePanelWidthPx
+ if (showAlphaPanel) {
+ w -= panelSpacingPx + alphaPanelHeightPx
+ }
+
+ //we can't fit within this container, set the size to whatever was allowed.
+ finalWidth = min(w, widthAllowed)
+ finalHeight = heightAllowed
+ } else {
+ //If we get here the dev has set the width and height to exact sizes. For example match_parent or 300dp.
+ //This will mean that the sat/val panel will not be square but it doesn't matter. It will work anyway.
+ //In all other senarios our goal is to make that panel square.
+
+ //We set the sizes to exactly what we were told.
+ finalWidth = widthAllowed
+ finalHeight = heightAllowed
+ }
+ } else {
+ //If no exact size has been set we try to make our view as big as possible
+ //within the allowed space.
+
+ //Calculate the needed width to layout using max allowed height.
+ var widthNeeded = heightAllowed + panelSpacingPx + huePanelWidthPx
+
+ //Calculate the needed height to layout using max allowed width.
+ var heightNeeded = widthAllowed - panelSpacingPx - huePanelWidthPx
+ if (showAlphaPanel) {
+ widthNeeded -= panelSpacingPx + alphaPanelHeightPx
+ heightNeeded += panelSpacingPx + alphaPanelHeightPx
+ }
+ val widthOk = widthNeeded <= widthAllowed
+ val heightOk = heightNeeded <= heightAllowed
+ when {
+ widthOk && heightOk -> {
+ finalWidth = widthAllowed
+ finalHeight = heightNeeded
+ }
+ widthOk -> {
+ finalHeight = heightAllowed
+ finalWidth = widthNeeded
+ }
+ heightOk -> {
+ finalHeight = heightNeeded
+ finalWidth = widthAllowed
+ }
+ else -> {
+ finalHeight = heightAllowed
+ finalWidth = widthAllowed
+ }
+ }
+ }
+ setMeasuredDimension(
+ finalWidth + paddingLeft + paddingRight,
+ finalHeight + paddingTop + paddingBottom
+ )
+ }
+
+ override fun getPaddingTop(): Int =
+ max(super.getPaddingTop(), mRequiredPadding)
+
+ override fun getPaddingBottom(): Int =
+ max(super.getPaddingBottom(), mRequiredPadding)
+
+ override fun getPaddingLeft(): Int =
+ max(super.getPaddingLeft(), mRequiredPadding)
+
+ override fun getPaddingRight(): Int =
+ max(super.getPaddingRight(), mRequiredPadding)
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ val drawingRect = Rect().also { this.drawingRect = it }
+ drawingRect.left = paddingLeft
+ drawingRect.right = w - paddingRight
+ drawingRect.top = paddingTop
+ drawingRect.bottom = h - paddingBottom
+
+ //The need to be recreated because they depend on the size of the view.
+ valShader = null
+ satShader = null
+ alphaShader = null
+
+ // Clear those bitmap caches since the size may have changed.
+ satValBackgroundCache = null
+ hueBackgroundCache = null
+ setUpSatValRect()
+ setUpHueRect()
+ setUpAlphaRect()
+ }
+
+ private fun setUpSatValRect() {
+ //Calculate the size for the big color rectangle.
+ val dRect = drawingRect!!
+ val left = dRect.left + BORDER_WIDTH_PX
+ val top = dRect.top + BORDER_WIDTH_PX
+ var bottom = dRect.bottom - BORDER_WIDTH_PX
+ val right = dRect.right - BORDER_WIDTH_PX - panelSpacingPx - huePanelWidthPx
+ if (showAlphaPanel) {
+ bottom -= alphaPanelHeightPx + panelSpacingPx
+ }
+ satValRect = Rect(left, top, right, bottom)
+ }
+
+ private fun setUpHueRect() {
+ //Calculate the size for the hue slider on the left.
+ val dRect = drawingRect!!
+ val left = dRect.right - huePanelWidthPx + BORDER_WIDTH_PX
+ val top = dRect.top + BORDER_WIDTH_PX
+ val bottom = dRect.bottom - BORDER_WIDTH_PX -
+ if (showAlphaPanel) panelSpacingPx + alphaPanelHeightPx else 0
+ val right = dRect.right - BORDER_WIDTH_PX
+ hueRect = Rect(left, top, right, bottom)
+ }
+
+ private fun setUpAlphaRect() {
+ if (!showAlphaPanel) return
+
+ val dRect = drawingRect ?: return
+
+ val left = dRect.left + BORDER_WIDTH_PX
+ val top = dRect.bottom - alphaPanelHeightPx + BORDER_WIDTH_PX
+ val bottom = dRect.bottom - BORDER_WIDTH_PX
+ val right = dRect.right - BORDER_WIDTH_PX
+
+ val alphaRect = Rect(left, top, right, bottom)
+ .also { this.alphaRect = it }
+
+ alphaPatternDrawable = AlphaPatternDrawable(DrawingUtils.dpToPx(context, 4f))
+ .apply {
+ setBounds(
+ alphaRect.left,
+ alphaRect.top,
+ alphaRect.right,
+ alphaRect.bottom
+ )
+ }
+ }
+
+ /**
+ * Set the color this view should show.
+ *
+ * @param color The color that should be selected. #argb
+ * @param callback If you want to get a callback to your OnColorChangedListener.
+ */
+ fun setColor(color: Int, callback: Boolean) {
+ val alpha = Color.alpha(color)
+ val red = Color.red(color)
+ val blue = Color.blue(color)
+ val green = Color.green(color)
+ val hsv = FloatArray(3)
+ Color.RGBToHSV(red, green, blue, hsv)
+ this.alpha = alpha
+ hue = hsv[0]
+ sat = hsv[1]
+ bri = hsv[2]
+ if (callback) {
+ onColorChangedListener
+ ?.onColorChanged(Color.HSVToColor(this.alpha, floatArrayOf(hue, sat, bri)))
+ }
+ invalidate()
+ }
+
+ /**
+ * Set if the user is allowed to adjust the alpha panel. Default is false.
+ * If it is set to false no alpha will be set.
+ *
+ * @param visible `true` to show the alpha slider
+ */
+ fun setAlphaSliderVisible(visible: Boolean) {
+ if (showAlphaPanel != visible) {
+ showAlphaPanel = visible
+
+ /*
+ * Force recreation.
+ */
+ valShader = null
+ satShader = null
+ alphaShader = null
+ hueBackgroundCache = null
+ satValBackgroundCache = null
+ requestLayout()
+ }
+ }
+
+ /**
+ * Set the text that should be shown in the
+ * alpha slider. Set to null to disable text.
+ *
+ * @param res string resource id.
+ */
+ fun setAlphaSliderText(res: Int) {
+ alphaSliderText = context.getString(res)
+ }
+}
\ No newline at end of file
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPreference.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPreference.java
deleted file mode 100644
index 96d5940b..00000000
--- a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPreference.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2017 JRummy Apps Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.jrummyapps.android.colorpicker;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.preference.Preference;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.jrummyapps.android.colorpicker.ColorPickerDialog.DialogType;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-
-/**
- * A Preference to select a color
- */
-public class ColorPreference extends Preference implements ColorPickerDialogListener {
-
- private static final int SIZE_NORMAL = 0;
- private static final int SIZE_LARGE = 1;
-
- private OnShowDialogListener onShowDialogListener;
- private int color = Color.BLACK;
- private boolean showDialog;
- @DialogType
- private int dialogType;
- private int colorShape;
- private boolean allowPresets;
- private boolean allowCustom;
- private boolean showAlphaSlider;
- private boolean showColorShades;
- private int previewSize;
- private int[] presets;
- private int dialogTitle;
-
- public ColorPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(attrs);
- }
-
- public ColorPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(attrs);
- }
-
- private void init(AttributeSet attrs) {
- setPersistent(true);
- TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPreference);
- showDialog = a.getBoolean(R.styleable.ColorPreference_cpv_showDialog, true);
- //noinspection WrongConstant
- dialogType = a.getInt(R.styleable.ColorPreference_cpv_dialogType, ColorPickerDialog.TYPE_PRESETS);
- colorShape = a.getInt(R.styleable.ColorPreference_cpv_colorShape, ColorShape.CIRCLE);
- allowPresets = a.getBoolean(R.styleable.ColorPreference_cpv_allowPresets, true);
- allowCustom = a.getBoolean(R.styleable.ColorPreference_cpv_allowCustom, true);
- showAlphaSlider = a.getBoolean(R.styleable.ColorPreference_cpv_showAlphaSlider, false);
- showColorShades = a.getBoolean(R.styleable.ColorPreference_cpv_showColorShades, true);
- previewSize = a.getInt(R.styleable.ColorPreference_cpv_previewSize, SIZE_NORMAL);
- final int presetsResId = a.getResourceId(R.styleable.ColorPreference_cpv_colorPresets, 0);
- dialogTitle = a.getResourceId(R.styleable.ColorPreference_cpv_dialogTitle, R.string.cpv_default_title);
- if (presetsResId != 0) {
- presets = getContext().getResources().getIntArray(presetsResId);
- } else {
- presets = ColorPickerDialog.MATERIAL_COLORS;
- }
- if (colorShape == ColorShape.CIRCLE) {
- setWidgetLayoutResource(
- previewSize == SIZE_LARGE ? R.layout.cpv_preference_circle_large : R.layout.cpv_preference_circle);
- } else {
- setWidgetLayoutResource(
- previewSize == SIZE_LARGE ? R.layout.cpv_preference_square_large : R.layout.cpv_preference_square
- );
- }
- a.recycle();
- }
-
- @Override
- protected void onClick() {
- super.onClick();
- if (onShowDialogListener != null) {
- onShowDialogListener.onShowColorPickerDialog((String) getTitle(), color);
- } else if (showDialog) {
- ColorPickerDialog dialog = ColorPickerDialog.newBuilder()
- .setDialogType(dialogType)
- .setDialogTitle(dialogTitle)
- .setColorShape(colorShape)
- .setPresets(presets)
- .setAllowPresets(allowPresets)
- .setAllowCustom(allowCustom)
- .setShowAlphaSlider(showAlphaSlider)
- .setShowColorShades(showColorShades)
- .setColor(color)
- .create();
- dialog.setColorPickerDialogListener(ColorPreference.this);
- FragmentManager fm = getFragmentManager();
- if (fm != null) {
- dialog.show(fm, getFragmentTag());
- }
- }
- }
-
- @Nullable
- private FragmentManager getFragmentManager() {
- Context context = getContext();
- if (context instanceof FragmentActivity) {
- return ((FragmentActivity) context).getSupportFragmentManager();
- }
- return null;
- }
-
- @Override
- protected void onAttachedToActivity() {
- super.onAttachedToActivity();
- FragmentManager fm = getFragmentManager();
- if (showDialog && fm != null) {
- ColorPickerDialog fragment = (ColorPickerDialog) fm.findFragmentByTag(getFragmentTag());
- if (fragment != null) {
- // re-bind preference to fragment
- fragment.setColorPickerDialogListener(this);
- }
- }
- }
-
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
- ColorPanelView preview = view.findViewById(R.id.cpv_preference_preview_color_panel);
- if (preview != null) {
- preview.setColor(color);
- }
- }
-
- @Override
- protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
- if (restorePersistedValue) {
- color = getPersistedInt(0xFF000000);
- } else {
- color = (Integer) defaultValue;
- persistInt(color);
- }
- }
-
- @Override
- protected Object onGetDefaultValue(TypedArray a, int index) {
- return a.getInteger(index, Color.BLACK);
- }
-
- @Override
- public void onColorSelected(int dialogId, @ColorInt int color) {
- saveValue(color);
- }
-
- @Override
- public void onDialogDismissed(int dialogId) {
- // no-op
- }
-
- /**
- * Set the new color
- *
- * @param color The newly selected color
- */
- public void saveValue(@ColorInt int color) {
- this.color = color;
- persistInt(this.color);
- notifyChanged();
- callChangeListener(color);
- }
-
- /**
- * Set the colors shown in the {@link ColorPickerDialog}.
- *
- * @param presets An array of color ints
- */
- public void setPresets(@NonNull int[] presets) {
- this.presets = presets;
- }
-
- /**
- * Get the colors that will be shown in the {@link ColorPickerDialog}.
- *
- * @return An array of color ints
- */
- public int[] getPresets() {
- return presets;
- }
-
- /**
- * The listener used for showing the {@link ColorPickerDialog}.
- * Call {@link #saveValue(int)} after the user chooses a color.
- * If this is set then it is up to you to show the dialog.
- *
- * @param listener The listener to show the dialog
- */
- public void setOnShowDialogListener(OnShowDialogListener listener) {
- onShowDialogListener = listener;
- }
-
- /**
- * The tag used for the {@link ColorPickerDialog}.
- *
- * @return The tag
- */
- public String getFragmentTag() {
- return "color_" + getKey();
- }
-
- public interface OnShowDialogListener {
-
- void onShowColorPickerDialog(String title, int currentColor);
- }
-
-}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPreference.kt b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPreference.kt
new file mode 100644
index 00000000..2612565f
--- /dev/null
+++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPreference.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2017 JRummy Apps Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:Suppress("DEPRECATION")
+package com.jrummyapps.android.colorpicker
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.graphics.Color
+import android.preference.Preference
+import android.util.AttributeSet
+import android.view.View
+import androidx.annotation.ColorInt
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import com.jrummyapps.android.colorpicker.ColorPickerDialog.Companion.newBuilder
+
+/**
+ * A Preference to select a color
+ */
+class ColorPreference : Preference, ColorPickerDialogListener {
+
+ companion object {
+ private const val SIZE_NORMAL = 0
+ private const val SIZE_LARGE = 1
+ }
+
+ interface OnShowDialogListener {
+ fun onShowColorPickerDialog(title: String?, currentColor: Int)
+ }
+
+ private var onShowDialogListener: OnShowDialogListener? = null
+ private var color = Color.BLACK
+ private var showDialog = false
+
+ @ColorPickerDialog.DialogType
+ private var dialogType = 0
+ private var colorShape = 0
+ private var allowPresets = false
+ private var allowCustom = false
+ private var showAlphaSlider = false
+ private var showColorShades = false
+ private var previewSize = 0
+
+ // An array of color ints
+ var presets: IntArray = ColorPickerDialog.MATERIAL_COLORS
+
+ private var dialogTitle = 0
+
+ /**
+ * The tag used for the [ColorPickerDialog].
+ */
+ private val fragmentTag: String
+ get() = "color_$key"
+
+ constructor(context: Context?, attrs: AttributeSet) : super(context, attrs) {
+ init(attrs)
+ }
+
+ constructor(context: Context?, attrs: AttributeSet, defStyle: Int) : super(
+ context,
+ attrs,
+ defStyle
+ ) {
+ init(attrs)
+ }
+
+ private fun init(attrs: AttributeSet) {
+ isPersistent = true
+ val a = context.obtainStyledAttributes(attrs, R.styleable.ColorPreference)
+ showDialog = a.getBoolean(R.styleable.ColorPreference_cpv_showDialog, true)
+ dialogType =
+ a.getInt(R.styleable.ColorPreference_cpv_dialogType, ColorPickerDialog.TYPE_PRESETS)
+ colorShape = a.getInt(R.styleable.ColorPreference_cpv_colorShape, ColorShape.CIRCLE)
+ allowPresets = a.getBoolean(R.styleable.ColorPreference_cpv_allowPresets, true)
+ allowCustom = a.getBoolean(R.styleable.ColorPreference_cpv_allowCustom, true)
+ showAlphaSlider = a.getBoolean(R.styleable.ColorPreference_cpv_showAlphaSlider, false)
+ showColorShades = a.getBoolean(R.styleable.ColorPreference_cpv_showColorShades, true)
+ previewSize = a.getInt(R.styleable.ColorPreference_cpv_previewSize, SIZE_NORMAL)
+ val presetsResId = a.getResourceId(R.styleable.ColorPreference_cpv_colorPresets, 0)
+ dialogTitle =
+ a.getResourceId(R.styleable.ColorPreference_cpv_dialogTitle, R.string.cpv_default_title)
+ presets = if (presetsResId != 0) {
+ context.resources.getIntArray(presetsResId)
+ } else {
+ ColorPickerDialog.MATERIAL_COLORS
+ }
+ widgetLayoutResource = if (colorShape == ColorShape.CIRCLE) {
+ if (previewSize == SIZE_LARGE) R.layout.cpv_preference_circle_large else R.layout.cpv_preference_circle
+ } else {
+ if (previewSize == SIZE_LARGE) R.layout.cpv_preference_square_large else R.layout.cpv_preference_square
+ }
+ a.recycle()
+ }
+
+ override fun onClick() {
+ super.onClick()
+ if (onShowDialogListener != null) {
+ onShowDialogListener!!.onShowColorPickerDialog(title as String, color)
+ } else if (showDialog) {
+ val dialog = newBuilder()
+ .setDialogType(dialogType)
+ .setDialogTitle(dialogTitle)
+ .setColorShape(colorShape)
+ .setPresets(presets)
+ .setAllowPresets(allowPresets)
+ .setAllowCustom(allowCustom)
+ .setShowAlphaSlider(showAlphaSlider)
+ .setShowColorShades(showColorShades)
+ .setColor(color)
+ .create()
+ dialog.colorPickerDialogListener = this@ColorPreference
+ val fm = fragmentManager
+ if (fm != null) {
+ dialog.show(fm, fragmentTag)
+ }
+ }
+ }
+
+ private val fragmentManager: FragmentManager?
+ get() {
+ val context = context
+ return if (context is FragmentActivity) {
+ context.supportFragmentManager
+ } else null
+ }
+
+ override fun onAttachedToActivity() {
+ super.onAttachedToActivity()
+ val fm = fragmentManager
+ if (showDialog && fm != null) {
+ val fragment = fm.findFragmentByTag(fragmentTag) as ColorPickerDialog?
+ if (fragment != null) {
+ // re-bind preference to fragment
+ fragment.colorPickerDialogListener = this
+ }
+ }
+ }
+
+ override fun onBindView(view: View) {
+ super.onBindView(view)
+ val preview: ColorPanelView = view.findViewById(R.id.cpv_preference_preview_color_panel)
+ preview.color = color
+ }
+
+ override fun onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any) {
+ if (restorePersistedValue) {
+ color = getPersistedInt(-0x1000000)
+ } else {
+ color = defaultValue as Int
+ persistInt(color)
+ }
+ }
+
+ override fun onGetDefaultValue(a: TypedArray, index: Int): Any {
+ return a.getInteger(index, Color.BLACK)
+ }
+
+ override fun onColorSelected(dialogId: Int, @ColorInt color: Int) {
+ saveValue(color)
+ }
+
+ override fun onDialogDismissed(dialogId: Int) {
+ // no-op
+ }
+
+ /**
+ * Set the new color
+ *
+ * @param color The newly selected color
+ */
+ private fun saveValue(@ColorInt color: Int) {
+ this.color = color
+ persistInt(this.color)
+ notifyChanged()
+ callChangeListener(color)
+ }
+
+ /**
+ * The listener used for showing the [ColorPickerDialog].
+ * Call [.saveValue] after the user chooses a color.
+ * If this is set then it is up to you to show the dialog.
+ *
+ * @param listener The listener to show the dialog
+ */
+ fun setOnShowDialogListener(listener: OnShowDialogListener?) {
+ onShowDialogListener = listener
+ }
+}
\ No newline at end of file
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorShape.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorShape.kt
similarity index 67%
rename from colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorShape.java
rename to colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorShape.kt
index e40342bd..3f68b60d 100644
--- a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorShape.java
+++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorShape.kt
@@ -13,23 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.jrummyapps.android.colorpicker
-package com.jrummyapps.android.colorpicker;
-
-import androidx.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import androidx.annotation.IntDef
/**
* The shape of the color preview
*/
-@Retention(RetentionPolicy.SOURCE)
-@IntDef({ColorShape.SQUARE, ColorShape.CIRCLE})
-public @interface ColorShape {
-
- int SQUARE = 0;
-
- int CIRCLE = 1;
-
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(ColorShape.SQUARE, ColorShape.CIRCLE)
+annotation class ColorShape {
+ companion object {
+ const val SQUARE = 0
+ const val CIRCLE = 1
+ }
}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorString.kt b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorString.kt
new file mode 100644
index 00000000..d50cbed6
--- /dev/null
+++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorString.kt
@@ -0,0 +1,27 @@
+package com.jrummyapps.android.colorpicker
+
+import android.graphics.Color
+
+@Throws(NumberFormatException::class)
+fun parseColorString(src: String): Int {
+ val start = if (src.startsWith("#")) 1 else 0
+
+ fun c1(offset: Int) =
+ src.substring(start + offset, start + offset + 1).toInt(16) * 0x11
+
+ fun c2(offset: Int) =
+ src.substring(start + offset, start + offset + 2).toInt(16)
+
+ return when (src.length - start) {
+ 0 -> Color.BLACK
+ 1 -> Color.argb(255, 0, 0, c1(0))
+ 2 -> Color.argb(255, 0, 0, c2(0))
+ 3 -> Color.argb(255, c1(0), c1(1), c1(2))
+ 4 -> Color.argb(c1(0), c1(1), c1(2), c1(3))
+ 5 -> Color.argb(255, c2(0), c2(2), c1(4))
+ 6 -> Color.argb(255, c2(0), c2(2), c2(4))
+ 7 -> Color.argb(c2(0), c2(2), c2(4), c1(6))
+ 8 -> Color.argb(c2(0), c2(2), c2(4), c2(6))
+ else -> Color.WHITE
+ }
+}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/DrawingUtils.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/DrawingUtils.kt
similarity index 54%
rename from colorpicker/src/main/java/com/jrummyapps/android/colorpicker/DrawingUtils.java
rename to colorpicker/src/main/java/com/jrummyapps/android/colorpicker/DrawingUtils.kt
index 1c9ce013..69e19e91 100644
--- a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/DrawingUtils.java
+++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/DrawingUtils.kt
@@ -13,21 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.jrummyapps.android.colorpicker
-package com.jrummyapps.android.colorpicker;
-
-import android.content.Context;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-
-final class DrawingUtils {
-
- static int dpToPx(Context c, float dipValue) {
- DisplayMetrics metrics = c.getResources().getDisplayMetrics();
- float val = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics);
- int res = (int) (val + 0.5); // Round
- // Ensure at least 1 pixel if val was > 0
- return res == 0 && val > 0 ? 1 : res;
- }
+import android.content.Context
+import android.util.TypedValue
+internal object DrawingUtils {
+ fun dpToPx(c: Context, dipValue: Float): Int {
+ val metrics = c.resources.displayMetrics
+ val v = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics)
+ val res = (v + 0.5).toInt() // Round
+ // Ensure at least 1 pixel if val was > 0
+ return if (res == 0 && v > 0) 1 else res
+ }
}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/NestedGridView.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/NestedGridView.java
deleted file mode 100644
index 9023b779..00000000
--- a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/NestedGridView.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 JRummy Apps Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.jrummyapps.android.colorpicker;
-
-import android.content.Context;
-import androidx.annotation.RestrictTo;
-import android.util.AttributeSet;
-import android.widget.GridView;
-
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class NestedGridView extends GridView {
-
- public NestedGridView(Context context) {
- super(context);
- }
-
- public NestedGridView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public NestedGridView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
- super.onMeasure(widthMeasureSpec, expandSpec);
- }
-
-}
diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/NestedGridView.kt b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/NestedGridView.kt
new file mode 100644
index 00000000..baf0d094
--- /dev/null
+++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/NestedGridView.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 JRummy Apps Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jrummyapps.android.colorpicker
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.GridView
+
+class NestedGridView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = android.R.attr.gridViewStyle,
+ defStyleRes: Int = 0,
+) : GridView(context, attrs, defStyle, defStyleRes) {
+
+ public override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val expandSpec = MeasureSpec.makeMeasureSpec(Int.MAX_VALUE shr 2, MeasureSpec.AT_MOST)
+ super.onMeasure(widthMeasureSpec, expandSpec)
+ }
+}
diff --git a/sample_apng/src/main/java/jp/juggler/apng/sample/ActList.kt b/sample_apng/src/main/java/jp/juggler/apng/sample/ActList.kt
index b07e247e..48574e5b 100644
--- a/sample_apng/src/main/java/jp/juggler/apng/sample/ActList.kt
+++ b/sample_apng/src/main/java/jp/juggler/apng/sample/ActList.kt
@@ -2,7 +2,6 @@ package jp.juggler.apng.sample
import android.Manifest
import android.content.pm.PackageManager
-import android.os.Build
import android.os.Bundle
import android.os.SystemClock
import android.util.Log
@@ -50,21 +49,20 @@ class ActList : AppCompatActivity(), CoroutineScope {
listView.onItemClickListener = listAdapter
timeAnimationStart = SystemClock.elapsedRealtime()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- // Assume thisActivity is the current activity
- if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(
- this,
- Manifest.permission.WRITE_EXTERNAL_STORAGE
- )
- ) {
- ActivityCompat.requestPermissions(
- this,
- arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
+ // Assume thisActivity is the current activity
+ if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(
+ this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ )
+ ) {
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
- PERMISSION_REQUEST_CODE_STORAGE
- )
- }
+ PERMISSION_REQUEST_CODE_STORAGE
+ )
}
+
load()
}
@@ -78,8 +76,8 @@ class ActList : AppCompatActivity(), CoroutineScope {
permissions: Array,
grantResults: IntArray
) {
- if( requestCode == PERMISSION_REQUEST_CODE_STORAGE){
- if (grantResults.all{ it == PackageManager.PERMISSION_GRANTED }) {
+ if (requestCode == PERMISSION_REQUEST_CODE_STORAGE) {
+ if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
// 特に何もしてないらしい
}
}