Meine Lösung mit a SwitchCompat
und Kotlin. In meiner Situation musste ich nur dann auf eine Änderung reagieren, wenn der Benutzer sie über die Benutzeroberfläche auslöste. In der Tat, mein Schalter reagiert auf ein LiveData
, und dies sowohl gemacht setOnClickListener
und setOnCheckedChangeListener
unbrauchbar. setOnClickListener
Tatsächlich reagiert es korrekt auf Benutzerinteraktionen, wird jedoch nicht ausgelöst, wenn der Benutzer den Daumen über den Switch zieht.setOnCheckedChangeListener
am anderen Ende wird auch ausgelöst, wenn der Schalter programmgesteuert umgeschaltet wird (z. B. von einem Beobachter). In meinem Fall war der Schalter auf zwei Fragmenten vorhanden, und so löste onRestoreInstanceState in einigen Fällen den Schalter mit einem alten Wert aus, der den korrekten Wert überschrieb.
Also habe ich mir den Code von SwitchCompat angesehen und konnte sein Verhalten bei der Unterscheidung von Klicken und Ziehen erfolgreich nachahmen und daraus einen benutzerdefinierten Touchlistener erstellen, der ordnungsgemäß funktioniert. Auf geht's:
/**
* 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)
}
}
Wie man es benutzt:
Der eigentliche Touch-Listener, der ein Lambda mit dem auszuführenden Code akzeptiert:
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)
}
)
Der Vollständigkeit halber sah der Beobachter des Staates switchstate
(falls vorhanden) folgendermaßen aus:
switchstate.observe(this, Observer {
myswitch.isChecked = it
})