aboutsummaryrefslogtreecommitdiff
path: root/colorpicker/src/main/kotlin/ca/allanwang/kau/colorpicker/ColorPickerView.kt
diff options
context:
space:
mode:
Diffstat (limited to 'colorpicker/src/main/kotlin/ca/allanwang/kau/colorpicker/ColorPickerView.kt')
-rw-r--r--colorpicker/src/main/kotlin/ca/allanwang/kau/colorpicker/ColorPickerView.kt310
1 files changed, 310 insertions, 0 deletions
diff --git a/colorpicker/src/main/kotlin/ca/allanwang/kau/colorpicker/ColorPickerView.kt b/colorpicker/src/main/kotlin/ca/allanwang/kau/colorpicker/ColorPickerView.kt
new file mode 100644
index 0000000..d86518c
--- /dev/null
+++ b/colorpicker/src/main/kotlin/ca/allanwang/kau/colorpicker/ColorPickerView.kt
@@ -0,0 +1,310 @@
+package ca.allanwang.kau.colorpicker
+
+import android.content.Context
+import android.graphics.Color
+import android.support.annotation.ColorInt
+import android.support.v4.content.res.ResourcesCompat
+import android.text.Editable
+import android.text.InputFilter
+import android.text.TextWatcher
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import android.widget.*
+import ca.allanwang.kau.R
+import ca.allanwang.kau.colorpicker.CircleView
+import ca.allanwang.kau.utils.*
+import com.afollestad.materialdialogs.DialogAction
+import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.color.FillGridView
+import java.util.*
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ *
+ * ColorPicker component of the ColorPickerDialog
+ */
+internal class ColorPickerView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ScrollView(context, attrs, defStyleAttr) {
+ var selectedColor: Int = -1
+ var isInSub: Boolean = false
+ var isInCustom: Boolean = false
+ var circleSize: Int = context.dimen(R.dimen.kau_cp_circle_size).toInt()
+ val backgroundColor = context.resolveColor(R.attr.md_background_color,
+ if (context.resolveColor(android.R.attr.textColorPrimary).isColorDark) Color.WHITE else 0xff424242.toInt())
+ val backgroundColorTint = backgroundColor.colorToForeground()
+ lateinit var dialog: MaterialDialog
+ lateinit var builder: ColorContract
+ lateinit var colorsTop: IntArray
+ var colorsSub: Array<IntArray>? = null
+ var topIndex: Int = -1
+ var subIndex: Int = -1
+ var colorIndex: Int
+ get() = if (isInSub) subIndex else topIndex
+ set(value) {
+ if (isInSub) subIndex = value
+ else {
+ topIndex = value
+ if (colorsSub != null && colorsSub!!.size > value) {
+ dialog.setActionButton(DialogAction.NEGATIVE, builder.backText)
+ isInSub = true
+ invalidateGrid()
+ }
+ }
+ }
+
+
+ val gridView: FillGridView by bindView(R.id.md_grid)
+ val customFrame: LinearLayout by bindView(R.id.md_colorChooserCustomFrame)
+ val customColorIndicator: View by bindView(R.id.md_colorIndicator)
+ val hexInput: EditText by bindView(R.id.md_hexInput)
+ val alphaLabel: TextView by bindView(R.id.md_colorALabel)
+ val alphaSeekbar: SeekBar by bindView(R.id.md_colorA)
+ val alphaValue: TextView by bindView(R.id.md_colorAValue)
+ val redSeekbar: SeekBar by bindView(R.id.md_colorR)
+ val redValue: TextView by bindView(R.id.md_colorRValue)
+ val greenSeekbar: SeekBar by bindView(R.id.md_colorG)
+ val greenValue: TextView by bindView(R.id.md_colorGValue)
+ val blueSeekbar: SeekBar by bindView(R.id.md_colorB)
+ val blueValue: TextView by bindView(R.id.md_colorBValue)
+
+ var customHexTextWatcher: TextWatcher? = null
+ var customRgbListener: SeekBar.OnSeekBarChangeListener? = null
+
+ init {
+ View.inflate(context, R.layout.md_dialog_colorchooser, this)
+ }
+
+ fun bind(builder: ColorContract, dialog: MaterialDialog) {
+ this.builder = builder
+ this.dialog = dialog
+ this.colorsTop = with(builder) {
+ if (colorsTop != null) colorsTop!!
+ else if (isAccent) ColorPalette.ACCENT_COLORS
+ else ColorPalette.PRIMARY_COLORS
+ }
+ this.colorsSub = with(builder) {
+ if (colorsTop != null) colorsSub
+ else if (isAccent) ColorPalette.ACCENT_COLORS_SUB
+ else ColorPalette.PRIMARY_COLORS_SUB
+ }
+ this.selectedColor = builder.defaultColor
+ if (builder.allowCustom) {
+ if (!builder.allowCustomAlpha) {
+ alphaLabel.gone()
+ alphaSeekbar.gone()
+ alphaValue.gone()
+ hexInput.hint = String.format("%06X", selectedColor)
+ hexInput.filters = arrayOf(InputFilter.LengthFilter(6))
+ } else {
+ hexInput.hint = String.format("%08X", selectedColor)
+ hexInput.filters = arrayOf(InputFilter.LengthFilter(8))
+ }
+ }
+ if (findColor(builder.defaultColor) || !builder.allowCustom) isInCustom = true //when toggled this will be false
+ toggleCustom()
+ }
+
+ fun backOrCancel() {
+ if (isInSub) {
+ dialog.setActionButton(DialogAction.NEGATIVE, builder.cancelText)
+ //to top
+ isInSub = false
+ subIndex = -1
+ invalidateGrid()
+ } else {
+ dialog.cancel()
+ }
+ }
+
+ fun toggleCustom() {
+ isInCustom = !isInCustom
+ if (isInCustom) {
+ isInSub = false
+ if (builder.allowCustom) dialog.setActionButton(DialogAction.NEUTRAL, builder.presetText)
+ dialog.setActionButton(DialogAction.NEGATIVE, builder.cancelText)
+ customHexTextWatcher = object : TextWatcher {
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+
+ override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+ try {
+ selectedColor = Color.parseColor("#" + s.toString())
+ } catch (e: IllegalArgumentException) {
+ selectedColor = Color.BLACK
+ }
+
+ customColorIndicator.setBackgroundColor(selectedColor)
+ if (alphaSeekbar.isVisible()) {
+ val alpha = Color.alpha(selectedColor)
+ alphaSeekbar.progress = alpha
+ alphaValue.text = String.format(Locale.CANADA, "%d", alpha)
+ }
+ redSeekbar.progress = Color.red(selectedColor)
+ greenSeekbar.progress = Color.green(selectedColor)
+ blueSeekbar.progress = Color.blue(selectedColor)
+ isInSub = false
+ topIndex = -1
+ subIndex = -1
+ refreshColors()
+ }
+
+ override fun afterTextChanged(s: Editable?) {}
+ }
+ hexInput.setText(selectedColor.toHexString(builder.allowCustomAlpha, false))
+ hexInput.addTextChangedListener(customHexTextWatcher)
+ customRgbListener = object : SeekBar.OnSeekBarChangeListener {
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ if (fromUser) {
+ val color = if (builder.allowCustomAlpha)
+ Color.argb(alphaSeekbar.progress,
+ redSeekbar.progress,
+ greenSeekbar.progress,
+ blueSeekbar.progress)
+ else Color.rgb(redSeekbar.progress,
+ greenSeekbar.progress,
+ blueSeekbar.progress)
+
+ hexInput.setText(color.toHexString(builder.allowCustomAlpha, false))
+ }
+ if (builder.allowCustomAlpha) alphaValue.text = alphaSeekbar.progress.toString()
+ redValue.text = redSeekbar.progress.toString()
+ greenValue.text = greenSeekbar.progress.toString()
+ blueValue.text = blueSeekbar.progress.toString()
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar) {}
+
+ override fun onStopTrackingTouch(seekBar: SeekBar) {}
+ }
+ redSeekbar.setOnSeekBarChangeListener(customRgbListener)
+ greenSeekbar.setOnSeekBarChangeListener(customRgbListener)
+ blueSeekbar.setOnSeekBarChangeListener(customRgbListener)
+ if (alphaSeekbar.isVisible())
+ alphaSeekbar.setOnSeekBarChangeListener(customRgbListener)
+ hexInput.setText(selectedColor.toHexString(alphaSeekbar.isVisible(), false))
+ gridView.fadeOut(onFinish = { gridView.gone() })
+ customFrame.fadeIn()
+ } else {
+ findColor(selectedColor)
+ if (builder.allowCustom) dialog.setActionButton(DialogAction.NEUTRAL, builder.customText)
+ dialog.setActionButton(DialogAction.NEGATIVE, if (isInSub) builder.backText else builder.cancelText)
+ gridView.fadeIn(onStart = { invalidateGrid() })
+ customFrame.fadeOut(onFinish = { customFrame.gone() })
+ hexInput.removeTextChangedListener(customHexTextWatcher)
+ customHexTextWatcher = null
+ alphaSeekbar.setOnSeekBarChangeListener(null)
+ redSeekbar.setOnSeekBarChangeListener(null)
+ greenSeekbar.setOnSeekBarChangeListener(null)
+ blueSeekbar.setOnSeekBarChangeListener(null)
+ customRgbListener = null
+ }
+ }
+
+ fun refreshColors() {
+ if (!isInCustom) findColor(selectedColor)
+ //Ensure that our tinted color is still visible against the background
+ val visibleColor = if (selectedColor.isColorVisibleOn(backgroundColor)) selectedColor else backgroundColorTint
+ if (builder.dynamicButtonColors) {
+ dialog.getActionButton(DialogAction.POSITIVE).setTextColor(visibleColor)
+ dialog.getActionButton(DialogAction.NEGATIVE).setTextColor(visibleColor)
+ dialog.getActionButton(DialogAction.NEUTRAL).setTextColor(visibleColor)
+ }
+ if (!builder.allowCustom || !isInCustom) return
+ if (builder.allowCustomAlpha)
+ alphaSeekbar.visible().tint(visibleColor)
+ redSeekbar.tint(visibleColor)
+ greenSeekbar.tint(visibleColor)
+ blueSeekbar.tint(visibleColor)
+ hexInput.tint(visibleColor)
+ }
+
+ fun findColor(@ColorInt color: Int): Boolean {
+ topIndex = -1
+ subIndex = -1
+ colorsTop.forEachIndexed {
+ index, topColor ->
+ if (findSubColor(color, index)) {
+ topIndex = index
+ return true
+ }
+ if (topColor == color) { // If no sub colors exists and top color matches
+ topIndex = index
+ return true
+ }
+ }
+ return false
+ }
+
+ fun findSubColor(@ColorInt color: Int, topIndex: Int): Boolean {
+ if (colorsSub == null || colorsSub!!.size <= topIndex) return false
+ colorsSub!![topIndex].forEachIndexed {
+ index, subColor ->
+ if (subColor == color) {
+ subIndex = index
+ return true
+ }
+ }
+ return false
+ }
+
+ fun invalidateGrid() {
+ if (gridView.adapter == null) {
+ gridView.adapter = ColorGridAdapter()
+ gridView.selector = ResourcesCompat.getDrawable(resources, R.drawable.kau_transparent, null)
+ } else {
+ (gridView.adapter as BaseAdapter).notifyDataSetChanged()
+ }
+ }
+
+ inner class ColorGridAdapter : BaseAdapter(), OnClickListener, OnLongClickListener {
+ override fun onClick(v: View) {
+ if (v.tag != null && v.tag is String) {
+ val tags = (v.tag as String).split(":")
+ if (colorIndex == tags[0].toInt()) {
+ colorIndex = tags[0].toInt() //Go to sub list if exists
+ return
+ }
+ if (colorIndex != -1) (gridView.getChildAt(colorIndex) as CircleView).animateSelected(false)
+ selectedColor = tags[1].toInt()
+ refreshColors()
+ val currentSub = isInSub
+ colorIndex = tags[0].toInt()
+ if (currentSub == isInSub) (gridView.getChildAt(colorIndex) as CircleView).animateSelected(true)
+ //Otherwise we are invalidating our grid, so there is no point in animating
+ }
+ }
+
+ override fun onLongClick(v: View): Boolean {
+ if (v.tag != null && v.tag is String) {
+ val tag = (v.tag as String).split(":")
+ val color = tag[1].toInt()
+ (v as CircleView).showHint(color)
+ return true
+ }
+ return false
+ }
+
+ override fun getItem(position: Int): Any = if (isInSub) colorsSub!![topIndex][position] else colorsTop[position]
+
+ override fun getCount(): Int = if (isInSub) colorsSub!![topIndex].size else colorsTop.size
+
+ override fun getItemId(position: Int): Long = position.toLong()
+
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val view: CircleView = if (convertView == null)
+ CircleView(context).apply { layoutParams = AbsListView.LayoutParams(circleSize, circleSize) }
+ else
+ convertView as CircleView
+ val color: Int = if (isInSub) colorsSub!![topIndex][position] else colorsTop[position]
+ return view.apply {
+ setBackgroundColor(color)
+ isSelected = colorIndex == position
+ tag = "$position:$color"
+ setOnClickListener(this@ColorGridAdapter)
+ setOnLongClickListener(this@ColorGridAdapter)
+ }
+ }
+
+ }
+} \ No newline at end of file