colorpickerのkotlin化など
This commit is contained in:
parent
c1b631621f
commit
5ed8383f43
|
@ -91,12 +91,8 @@ lint/tmp/
|
||||||
*.hprof
|
*.hprof
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
*.apk
|
|
||||||
*.iml
|
|
||||||
*.log
|
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.externalNativeBuild
|
|
||||||
.gradle
|
.gradle
|
||||||
|
|
||||||
/.idea/assetWizardSettings.xml
|
/.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.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.runner.AndroidJUnit4
|
import androidx.test.runner.AndroidJUnit4
|
||||||
import jp.juggler.subwaytooter.App1
|
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.Assert.assertNull
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
@ -36,9 +38,9 @@ class TestDatabase {
|
||||||
val helper = MockDbHelper(
|
val helper = MockDbHelper(
|
||||||
context,
|
context,
|
||||||
dbName,
|
dbName,
|
||||||
App1.DB_VERSION,
|
DB_VERSION,
|
||||||
create = { db ->
|
create = { db ->
|
||||||
App1.tableList.forEach {
|
TABLE_LIST.forEach {
|
||||||
val ex = try {
|
val ex = try {
|
||||||
it.onDBCreate(db)
|
it.onDBCreate(db)
|
||||||
null
|
null
|
||||||
|
@ -49,7 +51,7 @@ class TestDatabase {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
upgrade = { db, oldV, newV ->
|
upgrade = { db, oldV, newV ->
|
||||||
App1.tableList.forEach {
|
TABLE_LIST.forEach {
|
||||||
val ex = try {
|
val ex = try {
|
||||||
it.onDBUpgrade(db, oldV, newV)
|
it.onDBUpgrade(db, oldV, newV)
|
||||||
null
|
null
|
||||||
|
|
|
@ -366,6 +366,14 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</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>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -430,12 +430,12 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
override fun onDialogDismissed(dialogId: Int) {
|
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 colorTarget = this.colorTarget ?: return
|
||||||
val ip: IntPref = colorTarget.pref.cast() ?: error("$colorTarget has no in pref")
|
val ip: IntPref = colorTarget.pref.cast() ?: error("$colorTarget has no in pref")
|
||||||
val c = when (colorTarget.type) {
|
val c = when (colorTarget.type) {
|
||||||
SettingType.ColorAlpha -> colorSelected.notZero() ?: 0x01000000
|
SettingType.ColorAlpha -> newColor.notZero() ?: 1
|
||||||
else -> colorSelected or Color.BLACK
|
else -> newColor or Color.BLACK
|
||||||
}
|
}
|
||||||
pref.edit().put(ip, c).apply()
|
pref.edit().put(ip, c).apply()
|
||||||
findItemViewHolder(colorTarget)?.showColor()
|
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()
|
val sp: StringPref = appSettingItem.pref.cast()
|
||||||
?: error("${getString(appSettingItem.caption)}: not StringPref")
|
?: error("${getString(appSettingItem.caption)}: not StringPref")
|
||||||
pref.edit().put(sp, value).apply()
|
pref.edit().put(sp, value).apply()
|
||||||
|
@ -1224,7 +1224,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
showWebBrowser(findItemViewHolder(appSettingItem)?.textView1, value)
|
showWebBrowser(findItemViewHolder(appSettingItem)?.textView1, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showWebBrowser(tv: TextView?, prefValue: String) {
|
private fun showWebBrowser(tv: TextView?, prefValue: String) {
|
||||||
tv ?: return
|
tv ?: return
|
||||||
val cn = prefValue.cn()
|
val cn = prefValue.cn()
|
||||||
val (label, icon) = CustomShare.getInfo(this, cn)
|
val (label, icon) = CustomShare.getInfo(this, cn)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
|
@ -205,25 +206,22 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0xFF000000 と書きたいがkotlinではこれはlong型定数になってしまう
|
override fun onColorSelected(dialogId: Int, @ColorInt newColor: Int) {
|
||||||
private val colorFF000000: Int = (0xff shl 24)
|
|
||||||
|
|
||||||
override fun onColorSelected(dialogId: Int, @ColorInt colorSelected: Int) {
|
|
||||||
when (dialogId) {
|
when (dialogId) {
|
||||||
COLOR_DIALOG_ID_HEADER_BACKGROUND -> column.headerBgColor = colorFF000000 or
|
COLOR_DIALOG_ID_HEADER_BACKGROUND ->
|
||||||
colorSelected
|
column.headerBgColor = Color.BLACK or newColor
|
||||||
COLOR_DIALOG_ID_HEADER_FOREGROUND -> column.headerFgColor = colorFF000000 or
|
|
||||||
colorSelected
|
|
||||||
COLOR_DIALOG_ID_COLUMN_BACKGROUND -> column.columnBgColor = colorFF000000 or
|
|
||||||
colorSelected
|
|
||||||
|
|
||||||
COLOR_DIALOG_ID_ACCT_TEXT -> {
|
COLOR_DIALOG_ID_HEADER_FOREGROUND ->
|
||||||
column.acctColor = colorSelected.notZero() ?: 1
|
column.headerFgColor = Color.BLACK or newColor
|
||||||
}
|
|
||||||
|
|
||||||
COLOR_DIALOG_ID_CONTENT_TEXT -> {
|
COLOR_DIALOG_ID_COLUMN_BACKGROUND ->
|
||||||
column.contentColor = colorSelected.notZero() ?: 1
|
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()
|
show()
|
||||||
}
|
}
|
||||||
|
@ -236,7 +234,8 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||||
runApiTask { client ->
|
runApiTask { client ->
|
||||||
try {
|
try {
|
||||||
val backgroundDir = getBackgroundImageDir(this@ActColumnCustomize)
|
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)
|
val fileUri = Uri.fromFile(file)
|
||||||
|
|
||||||
client.publishApiProgress("loading image from $uriArg")
|
client.publishApiProgress("loading image from $uriArg")
|
||||||
|
@ -319,7 +318,8 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||||
sbColumnBackgroundAlpha = findViewById(R.id.sbColumnBackgroundAlpha)
|
sbColumnBackgroundAlpha = findViewById(R.id.sbColumnBackgroundAlpha)
|
||||||
sbColumnBackgroundAlpha.max = PROGRESS_MAX
|
sbColumnBackgroundAlpha.max = PROGRESS_MAX
|
||||||
|
|
||||||
sbColumnBackgroundAlpha.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
sbColumnBackgroundAlpha.setOnSeekBarChangeListener(object :
|
||||||
|
SeekBar.OnSeekBarChangeListener {
|
||||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||||
|
|
||||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||||
|
|
|
@ -213,10 +213,10 @@ class ActHighlightWordEdit
|
||||||
|
|
||||||
override fun onDialogDismissed(dialogId: Int) {}
|
override fun onDialogDismissed(dialogId: Int) {}
|
||||||
|
|
||||||
override fun onColorSelected(dialogId: Int, color: Int) {
|
override fun onColorSelected(dialogId: Int, newColor: Int) {
|
||||||
when (dialogId) {
|
when (dialogId) {
|
||||||
COLOR_DIALOG_ID_TEXT -> item.color_fg = color or Color.BLACK
|
COLOR_DIALOG_ID_TEXT -> item.color_fg = newColor or Color.BLACK
|
||||||
COLOR_DIALOG_ID_BACKGROUND -> item.color_bg = color.notZero() ?: 0x01000000
|
COLOR_DIALOG_ID_BACKGROUND -> item.color_bg = newColor.notZero() ?: 0x01000000
|
||||||
}
|
}
|
||||||
showColor()
|
showColor()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,10 @@ import android.content.res.Configuration
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
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.HorizontalScrollView
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
@ -140,7 +143,7 @@ class ActMain : AppCompatActivity(),
|
||||||
lateinit var handler: Handler
|
lateinit var handler: Handler
|
||||||
lateinit var appState: AppState
|
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) {
|
when (dialogId) {
|
||||||
1 -> colorFg = -0x1000000 or color
|
1 -> colorFg = -0x1000000 or newColor
|
||||||
2 -> colorBg = -0x1000000 or color
|
2 -> colorBg = -0x1000000 or newColor
|
||||||
}
|
}
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package jp.juggler.subwaytooter.actmain
|
package jp.juggler.subwaytooter.actmain
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -375,6 +376,7 @@ fun ActMain.scrollToLastColumn() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun ActMain.resizeColumnWidth(views: ActMainTabletViews) {
|
fun ActMain.resizeColumnWidth(views: ActMainTabletViews) {
|
||||||
|
|
||||||
var columnWMinDp = ActMain.COLUMN_WIDTH_MIN_DP
|
var columnWMinDp = ActMain.COLUMN_WIDTH_MIN_DP
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.text.Spannable
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
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.FlexboxLayout
|
||||||
import com.google.android.flexbox.JustifyContent
|
import com.google.android.flexbox.JustifyContent
|
||||||
import jp.juggler.subwaytooter.ActMain
|
import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
|
||||||
import jp.juggler.subwaytooter.R
|
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.TootAnnouncement
|
||||||
import jp.juggler.subwaytooter.api.entity.TootReaction
|
import jp.juggler.subwaytooter.api.entity.TootReaction
|
||||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||||
|
import jp.juggler.subwaytooter.api.runApiTask
|
||||||
import jp.juggler.subwaytooter.column.Column
|
import jp.juggler.subwaytooter.column.Column
|
||||||
import jp.juggler.subwaytooter.column.getContentColor
|
import jp.juggler.subwaytooter.column.getContentColor
|
||||||
import jp.juggler.subwaytooter.dialog.EmojiPicker
|
import jp.juggler.subwaytooter.dialog.EmojiPicker
|
||||||
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
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.span.NetworkEmojiSpan
|
||||||
import jp.juggler.subwaytooter.util.*
|
import jp.juggler.subwaytooter.util.*
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
|
|
|
@ -3,12 +3,11 @@ package jp.juggler.subwaytooter.columnviewholder
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection
|
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.R
|
||||||
import jp.juggler.subwaytooter.actmain.closePopup
|
import jp.juggler.subwaytooter.actmain.closePopup
|
||||||
import jp.juggler.subwaytooter.column.*
|
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.endPadding
|
||||||
import jp.juggler.subwaytooter.util.startPadding
|
import jp.juggler.subwaytooter.util.startPadding
|
||||||
import jp.juggler.subwaytooter.view.ListDivider
|
import jp.juggler.subwaytooter.view.ListDivider
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package jp.juggler.subwaytooter.columnviewholder
|
package jp.juggler.subwaytooter.columnviewholder
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -165,6 +166,7 @@ class ItemListAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun notifyChange(
|
fun notifyChange(
|
||||||
reason: String,
|
reason: String,
|
||||||
changeList: List<AdapterChange>? = null,
|
changeList: List<AdapterChange>? = null,
|
||||||
|
|
|
@ -59,11 +59,11 @@ import jp.juggler.util.LogCategory
|
||||||
// 2021/5/11 59=>60 SavedAccountテーブルに項目追加
|
// 2021/5/11 59=>60 SavedAccountテーブルに項目追加
|
||||||
// 2021/5/23 60=>61 SavedAccountテーブルに項目追加
|
// 2021/5/23 60=>61 SavedAccountテーブルに項目追加
|
||||||
|
|
||||||
private const val DB_VERSION = 61
|
const val DB_VERSION = 61
|
||||||
private const val DB_NAME = "app_db"
|
const val DB_NAME = "app_db"
|
||||||
private val log = LogCategory("AppDatabase")
|
private val log = LogCategory("AppDatabase")
|
||||||
|
|
||||||
val tables = arrayOf(
|
val TABLE_LIST = arrayOf(
|
||||||
LogData,
|
LogData,
|
||||||
SavedAccount,
|
SavedAccount,
|
||||||
ClientInfo,
|
ClientInfo,
|
||||||
|
@ -87,13 +87,13 @@ private class DBOpenHelper(context: Context) :
|
||||||
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
||||||
|
|
||||||
override fun onCreate(db: SQLiteDatabase) {
|
override fun onCreate(db: SQLiteDatabase) {
|
||||||
for (ti in tables) {
|
for (ti in TABLE_LIST) {
|
||||||
ti.onDBCreate(db)
|
ti.onDBCreate(db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
for (ti in tables) {
|
for (ti in TABLE_LIST) {
|
||||||
ti.onDBUpgrade(db, oldVersion, newVersion)
|
ti.onDBUpgrade(db, oldVersion, newVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import androidx.appcompat.widget.AppCompatButton
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.google.android.flexbox.JustifyContent
|
import com.google.android.flexbox.JustifyContent
|
||||||
import jp.juggler.subwaytooter.ActMain
|
import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.App1
|
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.entity.TimelineItem
|
import jp.juggler.subwaytooter.api.entity.TimelineItem
|
||||||
import jp.juggler.subwaytooter.api.entity.TootAccountRef
|
import jp.juggler.subwaytooter.api.entity.TootAccountRef
|
||||||
|
|
|
@ -3,7 +3,6 @@ package jp.juggler.subwaytooter.itemviewholder
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import jp.juggler.subwaytooter.ActMain
|
import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.ActMediaViewer
|
import jp.juggler.subwaytooter.ActMediaViewer
|
||||||
import jp.juggler.subwaytooter.App1
|
|
||||||
import jp.juggler.subwaytooter.action.*
|
import jp.juggler.subwaytooter.action.*
|
||||||
import jp.juggler.subwaytooter.actmain.nextPosition
|
import jp.juggler.subwaytooter.actmain.nextPosition
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
|
|
|
@ -4,11 +4,10 @@ import android.content.Context
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.style.RelativeSizeSpan
|
import android.text.style.RelativeSizeSpan
|
||||||
import jp.juggler.subwaytooter.ActMain
|
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.Acct
|
||||||
import jp.juggler.subwaytooter.api.entity.EntityId
|
import jp.juggler.subwaytooter.api.entity.EntityId
|
||||||
import jp.juggler.subwaytooter.api.entity.TootMention
|
import jp.juggler.subwaytooter.api.entity.TootMention
|
||||||
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.span.HighlightSpan
|
import jp.juggler.subwaytooter.span.HighlightSpan
|
||||||
import jp.juggler.subwaytooter.span.LinkInfo
|
import jp.juggler.subwaytooter.span.LinkInfo
|
||||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package jp.juggler.subwaytooter.pref
|
package jp.juggler.subwaytooter.pref
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import jp.juggler.subwaytooter.pref.impl.BooleanPref
|
import jp.juggler.subwaytooter.pref.impl.BooleanPref
|
||||||
|
|
||||||
object PrefB {
|
object PrefB {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package jp.juggler.subwaytooter.pref
|
package jp.juggler.subwaytooter.pref
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import jp.juggler.subwaytooter.drawable.MediaBackgroundDrawable
|
|
||||||
import jp.juggler.subwaytooter.itemviewholder.AdditionalButtonsPosition
|
import jp.juggler.subwaytooter.itemviewholder.AdditionalButtonsPosition
|
||||||
import jp.juggler.subwaytooter.pref.impl.IntPref
|
import jp.juggler.subwaytooter.pref.impl.IntPref
|
||||||
|
|
||||||
|
@ -115,5 +114,5 @@ object PrefI {
|
||||||
// const val TTCS_WEEKLY = 0
|
// const val TTCS_WEEKLY = 0
|
||||||
// const val TTCS_DAILY = 1
|
// 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 androidx.annotation.IntRange
|
||||||
import com.caverock.androidsvg.SVG
|
import com.caverock.androidsvg.SVG
|
||||||
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
|
||||||
import jp.juggler.util.LogCategory
|
import jp.juggler.util.LogCategory
|
||||||
|
|
||||||
// 絵文字リソースの種類によって異なるスパンを作る
|
// 絵文字リソースの種類によって異なるスパンを作る
|
||||||
|
|
|
@ -6,11 +6,10 @@ import android.text.SpannableString
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.style.*
|
import android.text.style.*
|
||||||
import jp.juggler.subwaytooter.App1
|
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.mfm.MisskeyMarkdownDecoder
|
import jp.juggler.subwaytooter.mfm.MisskeyMarkdownDecoder
|
||||||
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.span.*
|
import jp.juggler.subwaytooter.span.*
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
import jp.juggler.subwaytooter.table.AcctColor
|
||||||
import jp.juggler.subwaytooter.table.HighlightWord
|
import jp.juggler.subwaytooter.table.HighlightWord
|
||||||
|
@ -469,7 +468,13 @@ object HTMLDecoder {
|
||||||
else -> false
|
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
|
!isBlockParent -> false
|
||||||
curr.tag != TAG_TEXT -> false
|
curr.tag != TAG_TEXT -> false
|
||||||
curr.text.isNotBlank() -> false
|
curr.text.isNotBlank() -> false
|
||||||
|
@ -504,7 +509,10 @@ object HTMLDecoder {
|
||||||
) {
|
) {
|
||||||
sb.append(options.decodeEmoji(title))
|
sb.append(options.decodeEmoji(title))
|
||||||
return
|
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
|
// notestock custom emoji
|
||||||
sb.run {
|
sb.run {
|
||||||
val start = length
|
val start = length
|
||||||
|
@ -529,7 +537,15 @@ object HTMLDecoder {
|
||||||
sb.append(caption.notEmpty() ?: url)
|
sb.append(caption.notEmpty() ?: url)
|
||||||
if (reUrlStart.find(url) != null) {
|
if (reUrlStart.find(url) != null) {
|
||||||
val span =
|
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.setSpan(span, start, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
sb.append(" ")
|
sb.append(" ")
|
||||||
|
@ -549,7 +565,12 @@ object HTMLDecoder {
|
||||||
val originalFlusher: EncodeSpanEnv.() -> Unit = {
|
val originalFlusher: EncodeSpanEnv.() -> Unit = {
|
||||||
when (tag) {
|
when (tag) {
|
||||||
"s", "strike", "del" -> {
|
"s", "strike", "del" -> {
|
||||||
sb.setSpan(StrikethroughSpan(), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
sb.setSpan(
|
||||||
|
StrikethroughSpan(),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"em" -> {
|
"em" -> {
|
||||||
sb.setSpan(
|
sb.setSpan(
|
||||||
|
@ -560,7 +581,12 @@ object HTMLDecoder {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
"strong" -> {
|
"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" -> {
|
"tr" -> {
|
||||||
sb.append("|")
|
sb.append("|")
|
||||||
|
@ -570,37 +596,122 @@ object HTMLDecoder {
|
||||||
// sb_tmpにレンダリングした分は読み捨てる
|
// sb_tmpにレンダリングした分は読み捨てる
|
||||||
}
|
}
|
||||||
"h1" -> {
|
"h1" -> {
|
||||||
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
sb.setSpan(
|
||||||
sb.setSpan(RelativeSizeSpan(1.8f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
StyleSpan(Typeface.BOLD),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
sb.setSpan(
|
||||||
|
RelativeSizeSpan(1.8f),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"h2" -> {
|
"h2" -> {
|
||||||
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
sb.setSpan(
|
||||||
sb.setSpan(RelativeSizeSpan(1.6f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
StyleSpan(Typeface.BOLD),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
sb.setSpan(
|
||||||
|
RelativeSizeSpan(1.6f),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"h3" -> {
|
"h3" -> {
|
||||||
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
sb.setSpan(
|
||||||
sb.setSpan(RelativeSizeSpan(1.4f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
StyleSpan(Typeface.BOLD),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
sb.setSpan(
|
||||||
|
RelativeSizeSpan(1.4f),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"h4" -> {
|
"h4" -> {
|
||||||
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
sb.setSpan(
|
||||||
sb.setSpan(RelativeSizeSpan(1.2f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
StyleSpan(Typeface.BOLD),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
sb.setSpan(
|
||||||
|
RelativeSizeSpan(1.2f),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"h5" -> {
|
"h5" -> {
|
||||||
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
sb.setSpan(
|
||||||
sb.setSpan(RelativeSizeSpan(1.0f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
StyleSpan(Typeface.BOLD),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
sb.setSpan(
|
||||||
|
RelativeSizeSpan(1.0f),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"h6" -> {
|
"h6" -> {
|
||||||
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
sb.setSpan(
|
||||||
sb.setSpan(RelativeSizeSpan(0.8f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
StyleSpan(Typeface.BOLD),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
sb.setSpan(
|
||||||
|
RelativeSizeSpan(0.8f),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"pre" -> {
|
"pre" -> {
|
||||||
sb.setSpan(BackgroundColorSpan(0x40808080), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
sb.setSpan(
|
||||||
sb.setSpan(RelativeSizeSpan(0.7f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
BackgroundColorSpan(0x40808080),
|
||||||
sb.setSpan(fontSpan(Typeface.MONOSPACE), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
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" -> {
|
"code" -> {
|
||||||
sb.setSpan(BackgroundColorSpan(0x40808080), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
sb.setSpan(
|
||||||
sb.setSpan(fontSpan(Typeface.MONOSPACE), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
BackgroundColorSpan(0x40808080),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
sb.setSpan(
|
||||||
|
fontSpan(Typeface.MONOSPACE),
|
||||||
|
spanStart,
|
||||||
|
sb.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"hr" -> sb.append("----------")
|
"hr" -> sb.append("----------")
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,19 +29,15 @@ class ProgressResponseBody private constructor(
|
||||||
|
|
||||||
internal val log = LogCategory("ProgressResponseBody")
|
internal val log = LogCategory("ProgressResponseBody")
|
||||||
|
|
||||||
// please append this for OkHttpClient.Builder#addInterceptor().
|
fun makeInterceptor(): Interceptor = Interceptor { chain ->
|
||||||
// ex) builder.addInterceptor( ProgressResponseBody.makeInterceptor() );
|
val originalResponse = chain.proceed(chain.request())
|
||||||
fun makeInterceptor(): Interceptor = object : Interceptor {
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
|
||||||
val originalResponse = chain.proceed(chain.request())
|
|
||||||
|
|
||||||
val originalBody = originalResponse.body
|
val originalBody = originalResponse.body
|
||||||
?: error("makeInterceptor: originalResponse.body() returns null.")
|
?: error("makeInterceptor: originalResponse.body() returns null.")
|
||||||
|
|
||||||
return originalResponse.newBuilder()
|
originalResponse.newBuilder()
|
||||||
.body(ProgressResponseBody(originalBody))
|
.body(ProgressResponseBody(originalBody))
|
||||||
.build()
|
.build()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
|
|
|
@ -12,7 +12,7 @@ class JsonException : RuntimeException {
|
||||||
|
|
||||||
private const val CHAR0 = '\u0000'
|
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.
|
// Tests if the value should be tried as a decimal.
|
||||||
// It makes no test if there are actual digits.
|
// It makes no test if there are actual digits.
|
||||||
|
|
|
@ -92,6 +92,7 @@ class ToastCompat private constructor(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@SuppressLint("DiscouragedPrivateApi")
|
||||||
private fun setContextCompat(view: View?, contextCreator: () -> Context) {
|
private fun setContextCompat(view: View?, contextCreator: () -> Context) {
|
||||||
if (view != null && Build.VERSION.SDK_INT == 25) {
|
if (view != null && Build.VERSION.SDK_INT == 25) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -13,9 +13,9 @@ android {
|
||||||
targetSdkVersion target_sdk_version
|
targetSdkVersion target_sdk_version
|
||||||
minSdkVersion min_sdk_version
|
minSdkVersion min_sdk_version
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
abortOnError false
|
abortOnError false
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,12 @@ dependencies {
|
||||||
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
||||||
|
|
||||||
implementation 'com.google.android.flexbox:flexbox:3.0.0'
|
implementation 'com.google.android.flexbox:flexbox:3.0.0'
|
||||||
}
|
|
||||||
|
|
||||||
//apply plugin: 'com.getkeepsafe.dexcount'
|
testImplementation "junit:junit:$junit_version"
|
||||||
//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'
|
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.
|
* 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.
|
* Callback that is invoked when the color picker dialog was dismissed.
|
||||||
*
|
*
|
||||||
* @param dialogId
|
* @param dialogId
|
||||||
* The dialog id used to create the dialog instance.
|
* The dialog id used to create the dialog instance.
|
||||||
* @param color
|
*/
|
||||||
* The selected color
|
fun onDialogDismissed(dialogId: Int)
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package com.jrummyapps.android.colorpicker
|
||||||
|
|
||||||
package com.jrummyapps.android.colorpicker;
|
import androidx.annotation.IntDef
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The shape of the color preview
|
* The shape of the color preview
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
@IntDef({ColorShape.SQUARE, ColorShape.CIRCLE})
|
@IntDef(ColorShape.SQUARE, ColorShape.CIRCLE)
|
||||||
public @interface ColorShape {
|
annotation class ColorShape {
|
||||||
|
companion object {
|
||||||
int SQUARE = 0;
|
const val SQUARE = 0
|
||||||
|
const val CIRCLE = 1
|
||||||
int 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package com.jrummyapps.android.colorpicker
|
||||||
|
|
||||||
package com.jrummyapps.android.colorpicker;
|
import android.content.Context
|
||||||
|
import android.util.TypedValue
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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.Manifest
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -50,21 +49,20 @@ class ActList : AppCompatActivity(), CoroutineScope {
|
||||||
listView.onItemClickListener = listAdapter
|
listView.onItemClickListener = listAdapter
|
||||||
timeAnimationStart = SystemClock.elapsedRealtime()
|
timeAnimationStart = SystemClock.elapsedRealtime()
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
// Assume thisActivity is the current activity
|
||||||
// Assume thisActivity is the current activity
|
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(
|
||||||
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(
|
this,
|
||||||
this,
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
)
|
||||||
)
|
) {
|
||||||
) {
|
ActivityCompat.requestPermissions(
|
||||||
ActivityCompat.requestPermissions(
|
this,
|
||||||
this,
|
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||||
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
|
||||||
|
|
||||||
PERMISSION_REQUEST_CODE_STORAGE
|
PERMISSION_REQUEST_CODE_STORAGE
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
load()
|
load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,8 +76,8 @@ class ActList : AppCompatActivity(), CoroutineScope {
|
||||||
permissions: Array<String>,
|
permissions: Array<String>,
|
||||||
grantResults: IntArray
|
grantResults: IntArray
|
||||||
) {
|
) {
|
||||||
if( requestCode == PERMISSION_REQUEST_CODE_STORAGE){
|
if (requestCode == PERMISSION_REQUEST_CODE_STORAGE) {
|
||||||
if (grantResults.all{ it == PackageManager.PERMISSION_GRANTED }) {
|
if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
|
||||||
// 特に何もしてないらしい
|
// 特に何もしてないらしい
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue