La mia soluzione, usando a SwitchCompate Kotlin. Nella mia situazione, dovevo reagire a un cambiamento solo se l'utente lo aveva attivato tramite l'interfaccia utente. In effetti, il mio interruttore reagisce a LiveData, e questo ha reso entrambi setOnClickListenere setOnCheckedChangeListenerinutilizzabili. setOnClickListenerin realtà reagisce correttamente all'interazione dell'utente, ma non viene attivato se l'utente trascina il pollice sull'interruttore. setOnCheckedChangeListenerdall'altro lato viene attivato anche se l'interruttore viene attivato a livello di codice (ad esempio da un osservatore). Ora nel mio caso lo switch era presente su due frammenti, e così onRestoreInstanceState avrebbe innescato in alcuni casi lo switch con un vecchio valore sovrascrivendo il valore corretto.
Quindi, ho guardato il codice di SwitchCompat e sono stato in grado di imitare con successo il suo comportamento nel distinguere il clic e il trascinamento e l'ho usato per creare un touchlistener personalizzato che funzioni come dovrebbe. Eccoci qui:
/**
* 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)
}
}
Come usarlo:
l'ascoltatore touch effettivo che accetta un lambda con il codice da eseguire:
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)
}
)
Per completezza, ecco come switchstateappariva l'osservatore per lo stato (se ce l'hai):
switchstate.observe(this, Observer {
myswitch.isChecked = it
})