colorpickerのkotlin化など
This commit is contained in:
parent
c1b631621f
commit
5ed8383f43
|
@ -91,12 +91,8 @@ lint/tmp/
|
|||
*.hprof
|
||||
|
||||
#####################################
|
||||
*.apk
|
||||
*.iml
|
||||
*.log
|
||||
|
||||
.DS_Store
|
||||
.externalNativeBuild
|
||||
.gradle
|
||||
|
||||
/.idea/assetWizardSettings.xml
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.graphics.Color
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.jrummyapps.android.colorpicker.parseColorString
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class TestColorString {
|
||||
@Test
|
||||
fun testColorString() {
|
||||
fun a(s: String, expect: Int) {
|
||||
assertEquals(s, expect, parseColorString(s))
|
||||
assertEquals("#$s", expect, parseColorString("#$s"))
|
||||
}
|
||||
a("", Color.BLACK)
|
||||
a("8", Color.BLACK or 0x00_00_88)
|
||||
a("56", Color.BLACK or 0x00_00_56)
|
||||
a("123", Color.BLACK or 0x11_22_33)
|
||||
a("1234", 0x11_22_33_44)
|
||||
a("12345", Color.BLACK or 0x12_34_55)
|
||||
a("123456", Color.BLACK or 0x12_34_56)
|
||||
a("1234567", 0x12_34_56_77)
|
||||
a("12345678", 0x12_34_56_78)
|
||||
a("123456789", Color.WHITE)
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ import android.database.sqlite.SQLiteOpenHelper
|
|||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import jp.juggler.subwaytooter.App1
|
||||
import jp.juggler.subwaytooter.global.DB_VERSION
|
||||
import jp.juggler.subwaytooter.global.TABLE_LIST
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -36,9 +38,9 @@ class TestDatabase {
|
|||
val helper = MockDbHelper(
|
||||
context,
|
||||
dbName,
|
||||
App1.DB_VERSION,
|
||||
DB_VERSION,
|
||||
create = { db ->
|
||||
App1.tableList.forEach {
|
||||
TABLE_LIST.forEach {
|
||||
val ex = try {
|
||||
it.onDBCreate(db)
|
||||
null
|
||||
|
@ -49,7 +51,7 @@ class TestDatabase {
|
|||
}
|
||||
},
|
||||
upgrade = { db, oldV, newV ->
|
||||
App1.tableList.forEach {
|
||||
TABLE_LIST.forEach {
|
||||
val ex = try {
|
||||
it.onDBUpgrade(db, oldV, newV)
|
||||
null
|
||||
|
|
|
@ -366,6 +366,14 @@
|
|||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<meta-data
|
||||
android:name="jp.juggler.subwaytooter.global.GlobalInitializer"
|
||||
android:value="androidx.startup" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -430,12 +430,12 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
override fun onDialogDismissed(dialogId: Int) {
|
||||
}
|
||||
|
||||
override fun onColorSelected(dialogId: Int, @ColorInt colorSelected: Int) {
|
||||
override fun onColorSelected(dialogId: Int, @ColorInt newColor: Int) {
|
||||
val colorTarget = this.colorTarget ?: return
|
||||
val ip: IntPref = colorTarget.pref.cast() ?: error("$colorTarget has no in pref")
|
||||
val c = when (colorTarget.type) {
|
||||
SettingType.ColorAlpha -> colorSelected.notZero() ?: 0x01000000
|
||||
else -> colorSelected or Color.BLACK
|
||||
SettingType.ColorAlpha -> newColor.notZero() ?: 1
|
||||
else -> newColor or Color.BLACK
|
||||
}
|
||||
pref.edit().put(ip, c).apply()
|
||||
findItemViewHolder(colorTarget)?.showColor()
|
||||
|
@ -1216,7 +1216,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
}
|
||||
}
|
||||
|
||||
fun setWebBrowser(appSettingItem: AppSettingItem, value: String) {
|
||||
private fun setWebBrowser(appSettingItem: AppSettingItem, value: String) {
|
||||
val sp: StringPref = appSettingItem.pref.cast()
|
||||
?: error("${getString(appSettingItem.caption)}: not StringPref")
|
||||
pref.edit().put(sp, value).apply()
|
||||
|
@ -1224,7 +1224,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
showWebBrowser(findItemViewHolder(appSettingItem)?.textView1, value)
|
||||
}
|
||||
|
||||
fun showWebBrowser(tv: TextView?, prefValue: String) {
|
||||
private fun showWebBrowser(tv: TextView?, prefValue: String) {
|
||||
tv ?: return
|
||||
val cn = prefValue.cn()
|
||||
val (label, icon) = CustomShare.getInfo(this, cn)
|
||||
|
|
|
@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
|
|||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
|
@ -205,25 +206,22 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
|||
}
|
||||
}
|
||||
|
||||
// 0xFF000000 と書きたいがkotlinではこれはlong型定数になってしまう
|
||||
private val colorFF000000: Int = (0xff shl 24)
|
||||
|
||||
override fun onColorSelected(dialogId: Int, @ColorInt colorSelected: Int) {
|
||||
override fun onColorSelected(dialogId: Int, @ColorInt newColor: Int) {
|
||||
when (dialogId) {
|
||||
COLOR_DIALOG_ID_HEADER_BACKGROUND -> column.headerBgColor = colorFF000000 or
|
||||
colorSelected
|
||||
COLOR_DIALOG_ID_HEADER_FOREGROUND -> column.headerFgColor = colorFF000000 or
|
||||
colorSelected
|
||||
COLOR_DIALOG_ID_COLUMN_BACKGROUND -> column.columnBgColor = colorFF000000 or
|
||||
colorSelected
|
||||
COLOR_DIALOG_ID_HEADER_BACKGROUND ->
|
||||
column.headerBgColor = Color.BLACK or newColor
|
||||
|
||||
COLOR_DIALOG_ID_ACCT_TEXT -> {
|
||||
column.acctColor = colorSelected.notZero() ?: 1
|
||||
}
|
||||
COLOR_DIALOG_ID_HEADER_FOREGROUND ->
|
||||
column.headerFgColor = Color.BLACK or newColor
|
||||
|
||||
COLOR_DIALOG_ID_CONTENT_TEXT -> {
|
||||
column.contentColor = colorSelected.notZero() ?: 1
|
||||
}
|
||||
COLOR_DIALOG_ID_COLUMN_BACKGROUND ->
|
||||
column.columnBgColor = Color.BLACK or newColor
|
||||
|
||||
COLOR_DIALOG_ID_ACCT_TEXT ->
|
||||
column.acctColor = newColor.notZero() ?: 1
|
||||
|
||||
COLOR_DIALOG_ID_CONTENT_TEXT ->
|
||||
column.contentColor = newColor.notZero() ?: 1
|
||||
}
|
||||
show()
|
||||
}
|
||||
|
@ -236,7 +234,8 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
|||
runApiTask { client ->
|
||||
try {
|
||||
val backgroundDir = getBackgroundImageDir(this@ActColumnCustomize)
|
||||
val file = File(backgroundDir, "${column.columnId}:${System.currentTimeMillis()}")
|
||||
val file =
|
||||
File(backgroundDir, "${column.columnId}:${System.currentTimeMillis()}")
|
||||
val fileUri = Uri.fromFile(file)
|
||||
|
||||
client.publishApiProgress("loading image from $uriArg")
|
||||
|
@ -319,7 +318,8 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
|||
sbColumnBackgroundAlpha = findViewById(R.id.sbColumnBackgroundAlpha)
|
||||
sbColumnBackgroundAlpha.max = PROGRESS_MAX
|
||||
|
||||
sbColumnBackgroundAlpha.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
sbColumnBackgroundAlpha.setOnSeekBarChangeListener(object :
|
||||
SeekBar.OnSeekBarChangeListener {
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
|
|
|
@ -213,10 +213,10 @@ class ActHighlightWordEdit
|
|||
|
||||
override fun onDialogDismissed(dialogId: Int) {}
|
||||
|
||||
override fun onColorSelected(dialogId: Int, color: Int) {
|
||||
override fun onColorSelected(dialogId: Int, newColor: Int) {
|
||||
when (dialogId) {
|
||||
COLOR_DIALOG_ID_TEXT -> item.color_fg = color or Color.BLACK
|
||||
COLOR_DIALOG_ID_BACKGROUND -> item.color_bg = color.notZero() ?: 0x01000000
|
||||
COLOR_DIALOG_ID_TEXT -> item.color_fg = newColor or Color.BLACK
|
||||
COLOR_DIALOG_ID_BACKGROUND -> item.color_bg = newColor.notZero() ?: 0x01000000
|
||||
}
|
||||
showColor()
|
||||
}
|
||||
|
|
|
@ -8,7 +8,10 @@ import android.content.res.Configuration
|
|||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.view.*
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import android.widget.HorizontalScrollView
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
|
@ -140,7 +143,7 @@ class ActMain : AppCompatActivity(),
|
|||
lateinit var handler: Handler
|
||||
lateinit var appState: AppState
|
||||
|
||||
lateinit var sideMenuAdapter: SideMenuAdapter
|
||||
private lateinit var sideMenuAdapter: SideMenuAdapter
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// 読み取り専用のプロパティ
|
||||
|
|
|
@ -240,10 +240,10 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
|
|||
}
|
||||
}
|
||||
|
||||
override fun onColorSelected(dialogId: Int, @ColorInt color: Int) {
|
||||
override fun onColorSelected(dialogId: Int, @ColorInt newColor: Int) {
|
||||
when (dialogId) {
|
||||
1 -> colorFg = -0x1000000 or color
|
||||
2 -> colorBg = -0x1000000 or color
|
||||
1 -> colorFg = -0x1000000 or newColor
|
||||
2 -> colorBg = -0x1000000 or newColor
|
||||
}
|
||||
show()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package jp.juggler.subwaytooter.actmain
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.view.View
|
||||
|
@ -375,6 +376,7 @@ fun ActMain.scrollToLastColumn() {
|
|||
)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun ActMain.resizeColumnWidth(views: ActMainTabletViews) {
|
||||
|
||||
var columnWMinDp = ActMain.COLUMN_WIDTH_MIN_DP
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.text.Spannable
|
|||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
|
@ -15,17 +14,17 @@ import com.google.android.flexbox.FlexWrap
|
|||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.google.android.flexbox.JustifyContent
|
||||
import jp.juggler.subwaytooter.ActMain
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
||||
import jp.juggler.subwaytooter.api.entity.TootAnnouncement
|
||||
import jp.juggler.subwaytooter.api.entity.TootReaction
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.column.Column
|
||||
import jp.juggler.subwaytooter.column.getContentColor
|
||||
import jp.juggler.subwaytooter.dialog.EmojiPicker
|
||||
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
||||
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.util.*
|
||||
|
|
|
@ -3,12 +3,11 @@ package jp.juggler.subwaytooter.columnviewholder
|
|||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection
|
||||
import jp.juggler.subwaytooter.App1
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.PrefI
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.actmain.closePopup
|
||||
import jp.juggler.subwaytooter.column.*
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.PrefI
|
||||
import jp.juggler.subwaytooter.util.endPadding
|
||||
import jp.juggler.subwaytooter.util.startPadding
|
||||
import jp.juggler.subwaytooter.view.ListDivider
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package jp.juggler.subwaytooter.columnviewholder
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Handler
|
||||
import android.os.SystemClock
|
||||
import android.view.ViewGroup
|
||||
|
@ -165,6 +166,7 @@ class ItemListAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun notifyChange(
|
||||
reason: String,
|
||||
changeList: List<AdapterChange>? = null,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package jp.juggler.subwaytooter.pref
|
||||
|
||||
import android.os.Build
|
||||
import jp.juggler.subwaytooter.pref.impl.BooleanPref
|
||||
|
||||
object PrefB {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
// 絵文字リソースの種類によって異なるスパンを作る
|
||||
|
|
|
@ -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("----------")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
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'
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>A dialog to pick a color.</p>
|
||||
*
|
||||
* <p>The {@link Activity activity} that shows this dialog should implement {@link ColorPickerDialogListener}</p>
|
||||
*
|
||||
* <p>Example usage:</p>
|
||||
*
|
||||
* <pre>
|
||||
* ColorPickerDialog.newBuilder().show(activity);
|
||||
* </pre>
|
||||
*/
|
||||
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
|
||||
|
||||
}
|
|
@ -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:
|
||||
*
|
||||
* <pre>
|
||||
* ColorPickerDialog.newBuilder().show(activity);
|
||||
</pre> *
|
||||
*/
|
||||
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<ImageView>(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<InputFilter>(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<GridView>(R.id.gridView)
|
||||
loadPresets()
|
||||
if (showColorShades) {
|
||||
createColorShades(color)
|
||||
} else {
|
||||
shadesLayout?.visibility = View.GONE
|
||||
contentView.findViewById<View>(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<View>(R.id.transparency_layout).visibility = View.GONE
|
||||
contentView.findViewById<View>(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<ImageView>(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<ImageView>(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<ImageView>(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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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<String>,
|
||||
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 }) {
|
||||
// 特に何もしてないらしい
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue