Ma solution, en utilisant un SwitchCompat
et Kotlin. Dans ma situation, je devais réagir à un changement uniquement si l'utilisateur l'avait déclenché via l'interface utilisateur. En fait, mon commutateur réagit à un LiveData
, et ce fait à la fois setOnClickListener
et setOnCheckedChangeListener
inutilisable. setOnClickListener
en fait, il réagit correctement à l'interaction de l'utilisateur, mais il n'est pas déclenché si l'utilisateur fait glisser le pouce sur le commutateur.setOnCheckedChangeListener
à l'autre extrémité est également déclenchée si le commutateur est basculé par programme (par exemple par un observateur). Maintenant, dans mon cas, le commutateur était présent sur deux fragments, et ainsi onRestoreInstanceState déclencherait dans certains cas le commutateur avec une ancienne valeur écrasant la valeur correcte.
Donc, j'ai regardé le code de SwitchCompat, et j'ai réussi à imiter son comportement en distinguant le clic et le glisser et je l'ai utilisé pour créer un écouteur tactile personnalisé qui fonctionne comme il se doit. Et c'est parti:
/**
* 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)
}
}
Comment l'utiliser:
l'écouteur tactile réel qui accepte un lambda avec le code à exécuter:
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)
}
)
Par souci d'exhaustivité, voici à quoi switchstate
ressemblait l'observateur de l'état (si vous l'avez):
switchstate.observe(this, Observer {
myswitch.isChecked = it
})