Mi solución, usando a SwitchCompaty Kotlin. En mi situación, necesitaba reaccionar a un cambio solo si el usuario lo activaba a través de la interfaz de usuario. De hecho, mi interruptor reacciona a una LiveData, y esto hizo que tanto setOnClickListenere setOnCheckedChangeListenerinutilizable. setOnClickListenerde hecho, reacciona correctamente a la interacción del usuario, pero no se activa si el usuario arrastra el pulgar por el interruptor.setOnCheckedChangeListeneren el otro extremo también se activa si el interruptor se alterna mediante programación (por ejemplo, por un observador). Ahora, en mi caso, el conmutador estaba presente en dos fragmentos, y asíRestoreInstanceState activaría en algunos casos el conmutador con un valor anterior sobrescribiendo el valor correcto.
Entonces, miré el código de SwitchCompat y pude imitar su comportamiento con éxito al distinguir hacer clic y arrastrar, y lo usé para crear un escucha táctil personalizado que funciona como debería. Aquí vamos:
/**
* This function calls the lambda function passed with the right value of isChecked
* when the switch is tapped with single click isChecked is relative to the current position so we pass !isChecked
* when the switch is dragged instead, the position of the thumb centre where the user leaves the
* thumb is compared to the middle of the switch, and we assume that left means false, right means true
* (there is no rtl or vertical switch management)
* The behaviour is extrapolated from the SwitchCompat source code
*/
class SwitchCompatTouchListener(private val v: SwitchCompat, private val lambda: (Boolean)->Unit) : View.OnTouchListener {
companion object {
private const val TOUCH_MODE_IDLE = 0
private const val TOUCH_MODE_DOWN = 1
private const val TOUCH_MODE_DRAGGING = 2
}
private val vc = ViewConfiguration.get(v.context)
private val mScaledTouchSlop = vc.scaledTouchSlop
private var mTouchMode = 0
private var mTouchX = 0f
private var mTouchY = 0f
/**
* @return true if (x, y) is within the target area of the switch thumb
* x,y and rect are in view coordinates, 0,0 is top left of the view
*/
private fun hitThumb(x: Float, y: Float): Boolean {
val rect = v.thumbDrawable.bounds
return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom
}
override fun onTouch(view: View, event: MotionEvent): Boolean {
if (view == v) {
when (MotionEventCompat.getActionMasked(event)) {
MotionEvent.ACTION_DOWN -> {
val x = event.x
val y = event.y
if (v.isEnabled && hitThumb(x, y)) {
mTouchMode = TOUCH_MODE_DOWN;
mTouchX = x;
mTouchY = y;
}
}
MotionEvent.ACTION_MOVE -> {
val x = event.x
val y = event.y
if (mTouchMode == TOUCH_MODE_DOWN &&
(abs(x - mTouchX) > mScaledTouchSlop || abs(y - mTouchY) > mScaledTouchSlop)
)
mTouchMode = TOUCH_MODE_DRAGGING;
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
if (mTouchMode == TOUCH_MODE_DRAGGING) {
val r = v.thumbDrawable.bounds
if (r.left + r.right < v.width) lambda(false)
else lambda(true)
} else lambda(!v.isChecked)
mTouchMode = TOUCH_MODE_IDLE;
}
}
}
return v.onTouchEvent(event)
}
}
Cómo usarlo:
El oyente táctil real que acepta una lambda con el código para ejecutar:
myswitch.setOnTouchListener(
SwitchCompatTouchListener(myswitch) {
// here goes all the code for your callback, in my case
// i called a service which, when successful, in turn would
// update my liveData
viewModel.sendCommandToMyService(it)
}
)
En aras de la exhaustividad, así es como switchstatese veía el observador del estado (si lo tiene):
switchstate.observe(this, Observer {
myswitch.isChecked = it
})