Skip to content

Instantly share code, notes, and snippets.

@NaingAungLuu
Last active March 30, 2022 05:18
Show Gist options
  • Save NaingAungLuu/5a8a911f18c30c2411a0c9cc70e49bb3 to your computer and use it in GitHub Desktop.
Save NaingAungLuu/5a8a911f18c30c2411a0c9cc70e49bb3 to your computer and use it in GitHub Desktop.
A Number Control Widget for android with custom validations, min and max value setters, custom xml attributes, and listeners

Add This is attrs.xml for xml properties to work

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="NumberControl">
        <attr name="maxNumber" format="integer" />
        <attr name="minNumber" format="integer" />
    </declare-styleable>

</resources>
package com.rwsentosa.mobileapp.widgets
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.databinding.BindingAdapter
import com.rwsentosa.mobileapp.R
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlin.math.max
import kotlin.math.min
class NumberControl : FrameLayout {
constructor(context: Context) : this(context, null)
constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
constructor(
context: Context,
attributeSet: AttributeSet?,
defStyleAttr: Int
) : super(
context, attributeSet, defStyleAttr
) {
init(context, attributeSet)
}
constructor(
context: Context,
attributeSet: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attributeSet, defStyleAttr, defStyleRes) {
init(context, attributeSet)
}
private var view: View? = null
private lateinit var btnPlus: ImageView
private lateinit var btnMinus: ImageView
private lateinit var tvValue: TextView
private var listener: ((value: Int) -> Unit)? = null
private var validator: ((value: Int) -> Boolean) = { value ->
true
}
private var onMaxReachedListener: (() -> Unit)? = null
private var onMinReachedListener: (() -> Unit)? = null
var value: Int = 0
set(value) {
if (validator.invoke(value)) {
// Validator Success
field = value
valueFlow.tryEmit(value)
showValue(value)
listener?.invoke(value)
}
if (field >= maxValue) onMaxReachedListener?.invoke()
if (field <= minValue) onMinReachedListener?.invoke()
}
var maxValue: Int = Integer.MAX_VALUE
var minValue: Int = 0
var valueFlow: MutableSharedFlow<Int> = MutableStateFlow(0)
fun setValueListener(listener: (value: Int) -> Unit) {
this.listener = listener
}
fun setValidator(validator: (value: Int) -> Boolean) {
this.validator = validator
}
private fun init(context: Context, attrs: AttributeSet?) {
view = inflate(context, R.layout.view_number_control, this)
btnPlus = view!!.findViewById(R.id.btnPlus)
btnMinus = view!!.findViewById(R.id.btnMinus)
tvValue = view!!.findViewById(R.id.tvValue)
value = 0
attrs?.let {
val typedArray = context.obtainStyledAttributes(it, R.styleable.NumberControl)
maxValue = typedArray.getInt(R.styleable.NumberControl_maxNumber, Integer.MAX_VALUE)
minValue = typedArray.getInt(R.styleable.NumberControl_minNumber, minValue)
typedArray.recycle()
}
setupListeners()
}
private fun setupListeners() {
btnPlus.setOnClickListener { plus() }
btnMinus.setOnClickListener { minus() }
}
private fun plus() {
value = min(maxValue, value.plus(1))
}
private fun minus() {
value = max(minValue, value.minus(1))
}
private fun showValue(value: Int) {
tvValue.text = value.toString()
}
}
@BindingAdapter("numberControlValue")
fun setNumberControlValue(view: NumberControl, value: Int) {
view.value = value
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment