Skip to content

Instantly share code, notes, and snippets.

@jossiwolf
Last active December 9, 2019 11:31
Show Gist options
  • Save jossiwolf/f38532cae54bb979cb032e94be35231d to your computer and use it in GitHub Desktop.
Save jossiwolf/f38532cae54bb979cb032e94be35231d to your computer and use it in GitHub Desktop.
Enhanced MotionLayout capabilities such as being able to add multiple listeners and disable touch and await animations using coroutines. Adapted from https://github.com/chrisbanes/tivi/blob/master/common-ui/src/main/java/app/tivi/ui/widget/TiviMotionLayout.kt
class EnhancedMotionLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : MotionLayout(context, attrs, defStyleAttr) {
private val listeners = CopyOnWriteArrayList<TransitionListener>()
fun addTransitionListener(listener: TransitionListener) {
listeners.addIfAbsent(listener)
}
fun removeTransitionListener(listener: TransitionListener) {
listeners.remove(listener)
}
init {
super.setTransitionListener(object : TransitionListener {
override fun onTransitionTrigger(motionLayout: MotionLayout, triggerId: Int, positive: Boolean, progress: Float) {
listeners.forEach {
it.onTransitionTrigger(motionLayout, triggerId, positive, progress)
}
}
override fun onTransitionStarted(motionLayout: MotionLayout, startId: Int, endId: Int) {
listeners.forEach {
it.onTransitionStarted(motionLayout, startId, endId)
}
}
override fun onTransitionChange(motionLayout: MotionLayout, startId: Int, endId: Int, progress: Float) {
listeners.forEach {
it.onTransitionChange(motionLayout, startId, endId, progress)
}
}
override fun onTransitionCompleted(motionLayout: MotionLayout, currentId: Int) {
listeners.forEach {
it.onTransitionCompleted(motionLayout, currentId)
}
}
})
}
@Deprecated("Use addTransitionListener instead")
override fun setTransitionListener(listener: TransitionListener) {
throw IllegalArgumentException("Use addTransitionListener instead")
}
/**
* Whether this MotionLayout should react to nested scrolls and touch events
*/
var motionEnabled = true
override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
return motionEnabled && super.onStartNestedScroll(child, target, axes, type)
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN && !motionEnabled) {
return false
}
return super.onInterceptTouchEvent(event)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN && !motionEnabled) {
return false
}
return super.onTouchEvent(event)
}
}
/**
* Wait for the transition to complete so that the given [transitionId] is fully displayed.
*/
suspend fun EnhancedMotionLayout.transitionTo(@IdRes transitionId: Int) {
// If we're already at the specified state, return now
if (currentState == transitionId) return
suspendCancellableCoroutine<Unit> { cont ->
val listener = object : TransitionAdapter() {
override fun onTransitionCompleted(motionLayout: MotionLayout, currentId: Int) {
if (currentId == transitionId) {
removeTransitionListener(this)
cont.resume(Unit)
}
}
}
// If the coroutine is cancelled, remove the listener
cont.invokeOnCancellation {
removeTransitionListener(listener)
// TODO maybe reverse the current transition?
}
// And finally add the listener
addTransitionListener(listener)
transitionToState(transitionId)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment