From c0550e9892ae454e1f244dd2980c633d85803a25 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sat, 10 Jun 2017 22:04:03 -0700 Subject: Support transparent backgrounds in the ripple canvas --- .../kau/dialogs/color/ColorPickerDialog.kt | 32 ++++++------ .../kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt | 13 +++-- .../ca/allanwang/kau/kpref/items/KPrefCheckbox.kt | 15 +++++- .../allanwang/kau/kpref/items/KPrefColorPicker.kt | 9 +++- .../ca/allanwang/kau/kpref/items/KPrefHeader.kt | 3 +- .../ca/allanwang/kau/kpref/items/KPrefItemBase.kt | 6 ++- .../ca/allanwang/kau/kpref/items/KPrefItemCore.kt | 20 +++++++- .../kotlin/ca/allanwang/kau/utils/ColorUtils.kt | 20 ++++++-- .../kotlin/ca/allanwang/kau/views/RippleCanvas.kt | 59 +++++++++++++++++----- 9 files changed, 135 insertions(+), 42 deletions(-) (limited to 'library/src') diff --git a/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt index 4961211..4931913 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt @@ -29,6 +29,9 @@ internal class ColorPickerView @JvmOverloads constructor( var isInSub: Boolean = false var isInCustom: Boolean = false var circleSize: Int = context.dimen(R.dimen.kau_color_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 = if (backgroundColor.isColorDark()) backgroundColor.lighten(0.2f) else backgroundColor.darken(0.2f) lateinit var dialog: MaterialDialog lateinit var builder: Builder lateinit var colorsTop: IntArray @@ -89,7 +92,7 @@ internal class ColorPickerView @JvmOverloads constructor( hexInput.filters = arrayOf(InputFilter.LengthFilter(8)) } } - if (findColor(builder.defaultColor)) isInCustom = true //when toggled this will be false + if (findColor(builder.defaultColor) || !builder.allowCustom) isInCustom = true //when toggled this will be false toggleCustom() } @@ -109,7 +112,7 @@ internal class ColorPickerView @JvmOverloads constructor( isInCustom = !isInCustom if (isInCustom) { isInSub = false - dialog.setActionButton(DialogAction.NEUTRAL, builder.presetText) + 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) {} @@ -174,7 +177,7 @@ internal class ColorPickerView @JvmOverloads constructor( customFrame.fadeIn() } else { findColor(selectedColor) - dialog.setActionButton(DialogAction.NEUTRAL, builder.customText) + 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() }) @@ -190,19 +193,14 @@ internal class ColorPickerView @JvmOverloads constructor( 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(selectedColor) - dialog.getActionButton(DialogAction.NEGATIVE).setTextColor(selectedColor) - dialog.getActionButton(DialogAction.NEUTRAL).setTextColor(selectedColor) + dialog.getActionButton(DialogAction.POSITIVE).setTextColor(visibleColor) + dialog.getActionButton(DialogAction.NEGATIVE).setTextColor(visibleColor) + dialog.getActionButton(DialogAction.NEUTRAL).setTextColor(visibleColor) } if (!builder.allowCustom || !isInCustom) return - // Once we get close to white or transparent, - // the action buttons and seekbars will be a very light gray. - // TODO change by dialog theme - val visibleColor = if (Color.alpha(selectedColor) < 64 || - Color.red(selectedColor) > 247 && Color.green(selectedColor) > 247 && Color.blue(selectedColor) > 247) - "#DEDEDE".toColor() else selectedColor - if (builder.allowCustomAlpha) alphaSeekbar.visible().tint(visibleColor) redSeekbar.tint(visibleColor) @@ -253,7 +251,10 @@ internal class ColorPickerView @JvmOverloads constructor( 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()) return + 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() @@ -328,6 +329,7 @@ class Builder { var theme: Theme? = null + fun applyNestedBuilder(action: Builder.() -> Unit) = this.action() } @@ -341,7 +343,7 @@ fun Context.colorPickerDialog(action: Builder.() -> Unit): MaterialDialog { autoDismiss(false) positiveText(b.doneText) negativeText(b.cancelText) - neutralText(b.presetText) + if (b.allowCustom) neutralText(b.presetText) onPositive { dialog, _ -> b.colorCallbacks.forEach { it.invoke(view.selectedColor) }; dialog.dismiss() } onNegative { dialog, _ -> view.backOrCancel() } if (b.allowCustom) onNeutral { dialog, _ -> view.toggleCustom() } diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt index e346e77..acbb9a9 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt @@ -1,8 +1,10 @@ package ca.allanwang.kau.kpref +import android.support.annotation.ColorInt import android.support.annotation.StringRes import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView +import ca.allanwang.kau.dialogs.color.Builder import ca.allanwang.kau.kpref.items.KPrefCheckbox import ca.allanwang.kau.kpref.items.KPrefColorPicker import ca.allanwang.kau.kpref.items.KPrefHeader @@ -26,21 +28,26 @@ fun RecyclerView.setKPrefAdapter(builder: KPrefAdapterBuilder.() -> Unit): FastI class KPrefAdapterBuilder { - fun header(@StringRes title: Int) = list.add(KPrefHeader(title)) + var textColor: Int? = null + var accentColor: Int? = null + + fun header(@StringRes title: Int) = list.add(KPrefHeader(this, title)) fun checkbox(@StringRes title: Int, @StringRes description: Int = -1, iicon: IIcon? = null, enabled: Boolean = true, getter: () -> Boolean, - setter: (value: Boolean) -> Unit) = list.add(KPrefCheckbox(title, description, iicon, enabled, getter, setter)) + setter: (value: Boolean) -> Unit) = list.add(KPrefCheckbox(this, title, description, iicon, enabled, getter, setter)) fun colorPicker(@StringRes title: Int, @StringRes description: Int = -1, iicon: IIcon? = null, enabled: Boolean = true, getter: () -> Int, - setter: (value: Int) -> Unit)= list.add(KPrefColorPicker(title, description, iicon, enabled, getter, setter)) + setter: (value: Int) -> Unit, + configs: Builder.() -> Unit = {}) = list.add(KPrefColorPicker(this, title, description, iicon, enabled, getter, setter, configs)) internal val list: MutableList = mutableListOf() + } \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt index 3c87571..9961df9 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt @@ -4,17 +4,20 @@ import android.support.annotation.StringRes import android.view.View import android.widget.CheckBox import ca.allanwang.kau.R +import ca.allanwang.kau.kpref.KPrefAdapterBuilder +import ca.allanwang.kau.utils.tint import com.mikepenz.iconics.typeface.IIcon /** * Created by Allan Wang on 2017-06-07. */ -class KPrefCheckbox(@StringRes title: Int, +class KPrefCheckbox(builder: KPrefAdapterBuilder, + @StringRes title: Int, @StringRes description: Int = -1, iicon: IIcon? = null, enabled: Boolean = true, getter: () -> Boolean, - setter: (value: Boolean) -> Unit) : KPrefItemBase(title, description, iicon, enabled, getter, setter) { + setter: (value: Boolean) -> Unit) : KPrefItemBase(builder, title, description, iicon, enabled, getter, setter) { override fun onPostBindView(viewHolder: KPrefItemCore.ViewHolder) { super.onPostBindView(viewHolder) @@ -29,6 +32,14 @@ class KPrefCheckbox(@StringRes title: Int, return true } + override fun setColors(viewHolder: ViewHolder, builder: KPrefAdapterBuilder) { + super.setColors(viewHolder, builder) + if (builder.accentColor != null) { + val checkbox = viewHolder.itemView.findViewById(R.id.kau_pref_checkbox) as CheckBox + checkbox.tint(builder.accentColor!!) + } + } + override fun getType(): Int = R.id.kau_item_pref_checkbox } \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt index cca35b0..38a12e2 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt @@ -3,18 +3,22 @@ package ca.allanwang.kau.kpref.items import android.support.annotation.StringRes import android.view.View import ca.allanwang.kau.R +import ca.allanwang.kau.dialogs.color.Builder import ca.allanwang.kau.dialogs.color.colorPickerDialog +import ca.allanwang.kau.kpref.KPrefAdapterBuilder import com.mikepenz.iconics.typeface.IIcon /** * Created by Allan Wang on 2017-06-07. */ -class KPrefColorPicker(@StringRes title: Int, +class KPrefColorPicker(builder: KPrefAdapterBuilder, + @StringRes title: Int, @StringRes description: Int = -1, iicon: IIcon? = null, enabled: Boolean = true, getter: () -> Int, - setter: (value: Int) -> Unit) : KPrefItemBase(title, description, iicon, enabled, getter, setter) { + setter: (value: Int) -> Unit, + val configs: Builder.() -> Unit = {}) : KPrefItemBase(builder, title, description, iicon, enabled, getter, setter) { override fun onPostBindView(viewHolder: KPrefItemCore.ViewHolder) { super.onPostBindView(viewHolder) @@ -26,6 +30,7 @@ class KPrefColorPicker(@StringRes title: Int, titleRes = this@KPrefColorPicker.title defaultColor = pref colorCallbacks.add { pref = it } + applyNestedBuilder(configs) }.show() return true } diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt index 9c22469..4d3952e 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt @@ -3,11 +3,12 @@ package ca.allanwang.kau.kpref.items import android.support.annotation.StringRes import android.view.View import ca.allanwang.kau.R +import ca.allanwang.kau.kpref.KPrefAdapterBuilder /** * Created by Allan Wang on 2017-06-07. */ -class KPrefHeader(@StringRes title: Int) : KPrefItemCore(title = title) { +class KPrefHeader(builder:KPrefAdapterBuilder, @StringRes title: Int) : KPrefItemCore(builder, title = title) { override fun getLayoutRes(): Int = R.layout.kau_preference_header diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt index c86f3b6..b5e0254 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt @@ -4,6 +4,7 @@ import android.support.annotation.CallSuper import android.support.annotation.StringRes import android.util.Log import ca.allanwang.kau.R +import ca.allanwang.kau.kpref.KPrefAdapterBuilder import com.mikepenz.iconics.typeface.IIcon /** @@ -12,12 +13,13 @@ import com.mikepenz.iconics.typeface.IIcon * Base class for pref setters that include the Shared Preference hooks */ -abstract class KPrefItemBase(@StringRes title: Int, +abstract class KPrefItemBase(builder: KPrefAdapterBuilder, + @StringRes title: Int, @StringRes description: Int = -1, iicon: IIcon? = null, val enabled: Boolean = true, private val getter: () -> T, - private val setter: (value: T) -> Unit) : KPrefItemCore(title, description, iicon) { + private val setter: (value: T) -> Unit) : KPrefItemCore(builder, title, description, iicon) { var pref: T get() = getter.invoke() diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt index 8766234..112365a 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt @@ -12,7 +12,7 @@ import android.widget.LinearLayout import android.widget.TextView import butterknife.ButterKnife import ca.allanwang.kau.R -import ca.allanwang.kau.logging.SL +import ca.allanwang.kau.kpref.KPrefAdapterBuilder import ca.allanwang.kau.utils.* import com.mikepenz.fastadapter.items.AbstractItem import com.mikepenz.iconics.typeface.IIcon @@ -23,7 +23,8 @@ import com.mikepenz.iconics.typeface.IIcon * Core class containing nothing but the view items */ -abstract class KPrefItemCore(@StringRes val title: Int, +abstract class KPrefItemCore(val builder: KPrefAdapterBuilder, + @StringRes val title: Int, @StringRes val description: Int = -1, val iicon: IIcon? = null) : AbstractItem() { @@ -43,7 +44,22 @@ abstract class KPrefItemCore(@StringRes val title: Int, iconFrame?.visible() icon?.setIcon(iicon, 48) } else iconFrame?.gone() + innerFrame?.removeAllViews() onPostBindView(this) + setColors(this, builder) + } + } + + @CallSuper + open fun setColors(viewHolder: ViewHolder, builder: KPrefAdapterBuilder) { + with(viewHolder) { + if (builder.textColor != null) { + title.setTextColor(builder.textColor!!) + desc?.setTextColor(builder.textColor!!) + } + if (builder.accentColor != null) { + icon?.drawable?.setTint(builder.accentColor!!) + } } } diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt index c8fc90d..325a8af 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt @@ -33,12 +33,12 @@ fun Int.toHSV(): FloatArray { return hsv } -fun FloatArray.toColor():Int=Color.HSVToColor(this) +fun FloatArray.toColor(): Int = Color.HSVToColor(this) -fun Int.isColorVisibleOn(@ColorInt color: Int, @IntRange(from = 0L, to = 255L) delta: Int = 8, +fun Int.isColorVisibleOn(@ColorInt color: Int, @IntRange(from = 0L, to = 255L) delta: Int = 25, @IntRange(from = 0L, to = 255L) minAlpha: Int = 50): Boolean = if (Color.alpha(this) < minAlpha) false - else (Math.abs(Color.red(this) - Color.red(color)) < delta + else !(Math.abs(Color.red(this) - Color.red(color)) < delta && Math.abs(Color.green(this) - Color.green(color)) < delta && Math.abs(Color.blue(this) - Color.blue(color)) < delta) @@ -56,6 +56,20 @@ fun Int.adjustAlpha(factor: Float): Int { return Color.argb(alpha, Color.red(this), Color.green(this), Color.blue(this)) } +@ColorInt +fun Int.lighten(@FloatRange(from = 0.0, to = 1.0) factor: Float = 0.2f): Int { + val (red, green, blue) = intArrayOf(Color.red(this), Color.green(this), Color.blue(this)) + .map { Math.min(it * (1 + factor), 255.0f).toInt() } + return Color.argb(Color.alpha(this), red, green, blue) +} + +@ColorInt +fun Int.darken(@FloatRange(from = 0.0, to = 1.0) factor: Float = 0.2f): Int { + val (red, green, blue) = intArrayOf(Color.red(this), Color.green(this), Color.blue(this)) + .map { Math.max(it * (1 - factor), 0f).toInt() } + return Color.argb(Color.alpha(this), red, green, blue) +} + @Throws(IllegalArgumentException::class) fun String.toColor(): Int { val toParse: String diff --git a/library/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt b/library/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt index 2c44197..9444ef9 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt @@ -7,6 +7,7 @@ import android.graphics.Color import android.graphics.Paint import android.util.AttributeSet import android.view.View +import ca.allanwang.kau.utils.adjustAlpha /** * Created by Allan Wang on 2016-11-17. @@ -28,21 +29,49 @@ class RippleCanvas @JvmOverloads constructor( paint.style = Paint.Style.FILL } + /** + * Drawing the ripples involves having access to the next layer if it exists, + * and using its values to decide on the current color. + * If the next layer requests a fade, we will adjust the alpha of our current layer before drawing. + * Otherwise we will just draw the color as intended + */ override fun onDraw(canvas: Canvas) { - canvas.drawColor(baseColor) - val itr = ripples.iterator() - while (itr.hasNext()) { - val r = itr.next() - paint.color = r.color - canvas.drawCircle(r.x, r.y, r.radius, paint) - if (r.radius == r.maxRadius) { - itr.remove() - baseColor = r.color + val itr = ripples.listIterator() + if (!itr.hasNext()) return canvas.drawColor(baseColor) + var next = itr.next() + canvas.drawColor(colorToDraw(baseColor, next.fade, next.radius, next.maxRadius)) + var last = false + while (!last) { + val current = next + if (itr.hasNext()) next = itr.next() + else last = true + //We may fade any layer except for the last one + paint.color = colorToDraw(current.color, next.fade && !last, next.radius, next.maxRadius) + canvas.drawCircle(current.x, current.y, current.radius, paint) + if (current.radius == current.maxRadius) { + if (!last) { + itr.previous() + itr.remove() + itr.next() + } else { + itr.remove() + } + baseColor = current.color } } } - fun ripple(color: Int, startX: Float = 0f, startY: Float = 0f, duration: Int = 1000) { + /** + * Given our current color and next layer's radius & max, + * we will decide on the alpha of our current layer + */ + internal fun colorToDraw(color: Int, fade: Boolean, current: Float, goal: Float): Int { + if (!fade || (current / goal <= FADE_PIVOT)) return color + val factor = (goal - current) / (goal - FADE_PIVOT * goal) + return color.adjustAlpha(factor) + } + + fun ripple(color: Int, startX: Float = 0f, startY: Float = 0f, duration: Int = 1000, fade: Boolean = Color.alpha(color) != 255) { var x = startX var y = startY val w = width.toFloat() @@ -54,7 +83,7 @@ class RippleCanvas @JvmOverloads constructor( y = h / 2 else if (y > h) y = 0f val maxRadius = Math.hypot(Math.max(x, w - x).toDouble(), Math.max(y, h - y).toDouble()).toFloat() - val ripple = Ripple(color, x, y, 0f, maxRadius) + val ripple = Ripple(color, x, y, 0f, maxRadius, fade) ripples.add(ripple) val animator = ValueAnimator.ofFloat(0f, maxRadius) animator.duration = duration.toLong() @@ -71,9 +100,15 @@ class RippleCanvas @JvmOverloads constructor( invalidate() } - internal class Ripple(val color: Int, val x: Float, val y: Float, var radius: Float, val maxRadius: Float) + internal class Ripple(val color: Int, + val x: Float, + val y: Float, + var radius: Float, + val maxRadius: Float, + val fade: Boolean) companion object { const val MIDDLE = -1.0f + const val FADE_PIVOT = 0.5f } } -- cgit v1.2.3