mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
Support change_bounds transitions for wrap_content views
commit_hash:0acb1b5c7c980ab6f63704c500ef0f4e5a8f9362
This commit is contained in:
@@ -1393,6 +1393,7 @@
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivAnimatorController.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivAnimatorController.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivComparator.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivComparator.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivComparatorReporter.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivComparatorReporter.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivTransition.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivTransition.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivTransitionHandler.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivTransitionHandler.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivTransitions.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/DivTransitions.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/Fade.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/Fade.kt",
|
||||
@@ -1400,6 +1401,7 @@
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/Scale.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/Scale.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/SceneRootWatcher.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/SceneRootWatcher.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/Slide.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/Slide.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/TransitionData.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/TransitionData.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/Transitions.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/Transitions.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/Utils.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/Utils.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/core/view2/animations/VerticalTranslation.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/view2/animations/VerticalTranslation.kt",
|
||||
|
||||
@@ -8,24 +8,31 @@ import com.yandex.div.internal.core.toItemBuilderResult
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
import com.yandex.div2.Div
|
||||
|
||||
private typealias Converter<T> = (DivItemBuilderResult) -> T
|
||||
|
||||
/**
|
||||
* Gets a sequence for visiting this [Div] and all its children.
|
||||
*/
|
||||
internal fun Div.walk(resolver: ExpressionResolver): DivTreeWalk {
|
||||
return DivTreeWalk(this, resolver)
|
||||
}
|
||||
internal fun <T> Div.walk(
|
||||
resolver: ExpressionResolver,
|
||||
converter: Converter<T?>,
|
||||
): DivTreeWalk<T> = DivTreeWalk(this, resolver, converter)
|
||||
|
||||
internal class DivTreeWalk private constructor(
|
||||
internal fun Div.walk(resolver: ExpressionResolver) = walk(resolver) { it }
|
||||
|
||||
internal class DivTreeWalk<T> private constructor(
|
||||
private val root: Div,
|
||||
private val resolver: ExpressionResolver,
|
||||
private val onEnter: ((Div) -> Boolean)?,
|
||||
private val onLeave: ((Div) -> Unit)?,
|
||||
private val maxDepth: Int = Int.MAX_VALUE
|
||||
) : Sequence<DivItemBuilderResult> {
|
||||
private val converter: Converter<T?>,
|
||||
private val maxDepth: Int = Int.MAX_VALUE,
|
||||
) : Sequence<T> {
|
||||
|
||||
internal constructor(root: Div, resolver: ExpressionResolver) : this(root, resolver, null, null)
|
||||
internal constructor(root: Div, resolver: ExpressionResolver, converter: Converter<T?>) :
|
||||
this(root, resolver, null, null, converter)
|
||||
|
||||
override fun iterator(): Iterator<DivItemBuilderResult> = DivTreeWalkIterator(root, resolver)
|
||||
override fun iterator(): Iterator<T> = DivTreeWalkIterator(root, resolver, converter)
|
||||
|
||||
/**
|
||||
* Sets a [predicate], that is called on any entered container div (div-container, div-gallery, etc.)
|
||||
@@ -33,16 +40,16 @@ internal class DivTreeWalk private constructor(
|
||||
*
|
||||
* If the [predicate] returns `false` the div is not entered and neither it nor its children are visited.
|
||||
*/
|
||||
fun onEnter(predicate: (Div) -> Boolean): DivTreeWalk {
|
||||
return DivTreeWalk(root, resolver, onEnter = predicate, onLeave = onLeave, maxDepth = maxDepth)
|
||||
fun onEnter(predicate: (Div) -> Boolean): DivTreeWalk<T> {
|
||||
return DivTreeWalk(root, resolver, onEnter = predicate, onLeave = onLeave, converter, maxDepth = maxDepth)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a callback [function], that is called on any left container div after its children are visited,
|
||||
* and after it is visited itself.
|
||||
*/
|
||||
fun onLeave(function: (Div) -> Unit): DivTreeWalk {
|
||||
return DivTreeWalk(root, resolver, onEnter = onEnter, onLeave = function, maxDepth = maxDepth)
|
||||
fun onLeave(function: (Div) -> Unit): DivTreeWalk<T> {
|
||||
return DivTreeWalk(root, resolver, onEnter = onEnter, onLeave = function, converter, maxDepth = maxDepth)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,18 +60,19 @@ internal class DivTreeWalk private constructor(
|
||||
* With a value of 1, walker visits only the origin div and all its immediate children,
|
||||
* with a value of 2 also grandchildren, etc.
|
||||
*/
|
||||
fun maxDepth(depth: Int): DivTreeWalk {
|
||||
fun maxDepth(depth: Int): DivTreeWalk<T> {
|
||||
if (depth <= 0) {
|
||||
throw IllegalArgumentException("depth must be positive, but was $depth.")
|
||||
}
|
||||
|
||||
return DivTreeWalk(root, resolver, onEnter, onLeave, depth)
|
||||
return DivTreeWalk(root, resolver, onEnter, onLeave, converter, depth)
|
||||
}
|
||||
|
||||
private inner class DivTreeWalkIterator(
|
||||
private val root: Div,
|
||||
private val resolver: ExpressionResolver
|
||||
) : AbstractIterator<DivItemBuilderResult>() {
|
||||
private val resolver: ExpressionResolver,
|
||||
private val converter: Converter<T?>,
|
||||
) : AbstractIterator<T>() {
|
||||
|
||||
private val stack = ArrayDeque<Node>().apply {
|
||||
addLast(node(root.toItemBuilderResult(resolver)))
|
||||
@@ -72,11 +80,13 @@ internal class DivTreeWalk private constructor(
|
||||
|
||||
override fun computeNext() {
|
||||
val nextItem = nextItem()
|
||||
if (nextItem != null) {
|
||||
setNext(nextItem)
|
||||
} else {
|
||||
if (nextItem == null) {
|
||||
done()
|
||||
return
|
||||
}
|
||||
|
||||
converter(nextItem)?.let { setNext(it) }
|
||||
?: computeNext()
|
||||
}
|
||||
|
||||
private fun nextItem(): DivItemBuilderResult? {
|
||||
|
||||
@@ -51,8 +51,10 @@ import com.yandex.div.core.util.walk
|
||||
import com.yandex.div.core.view2.animations.DivComparator
|
||||
import com.yandex.div.core.view2.animations.DivTransitionHandler
|
||||
import com.yandex.div.core.view2.animations.SceneRootWatcher
|
||||
import com.yandex.div.core.view2.animations.TransitionData
|
||||
import com.yandex.div.core.view2.animations.allowsTransitionsOnDataChange
|
||||
import com.yandex.div.core.view2.animations.doOnEnd
|
||||
import com.yandex.div.core.view2.animations.toTransitionData
|
||||
import com.yandex.div.core.view2.divs.bindingContext
|
||||
import com.yandex.div.core.view2.divs.clearFocusOnClick
|
||||
import com.yandex.div.core.view2.divs.drawShadow
|
||||
@@ -81,7 +83,6 @@ import com.yandex.div.histogram.util.HistogramClock
|
||||
import com.yandex.div.internal.Assert
|
||||
import com.yandex.div.internal.KAssert
|
||||
import com.yandex.div.internal.KLog
|
||||
import com.yandex.div.internal.core.DivItemBuilderResult
|
||||
import com.yandex.div.internal.core.VariableMutationHandler
|
||||
import com.yandex.div.internal.util.UiThreadHandler.Companion.executeOnMainThreadBlocking
|
||||
import com.yandex.div.internal.util.hasScrollableChildUnder
|
||||
@@ -1082,10 +1083,8 @@ class Div2View private constructor(
|
||||
}
|
||||
|
||||
val transition = viewComponent.transitionBuilder.buildTransitions(
|
||||
from = oldDiv?.let { itemSequenceForTransition(oldData, it, oldExpressionResolver) },
|
||||
to = newDiv?.let { itemSequenceForTransition(newData, it, expressionResolver) },
|
||||
fromResolver = oldExpressionResolver,
|
||||
toResolver = expressionResolver
|
||||
from = oldDiv?.let { transitionSequence(oldData, it, oldExpressionResolver, isIncoming = false) },
|
||||
to = newDiv?.let { transitionSequence(newData, it, expressionResolver, isIncoming = true) },
|
||||
)
|
||||
|
||||
if (transition.transitionCount == 0) {
|
||||
@@ -1101,28 +1100,28 @@ class Div2View private constructor(
|
||||
return transition
|
||||
}
|
||||
|
||||
private fun itemSequenceForTransition(
|
||||
private fun transitionSequence(
|
||||
divData: DivData?,
|
||||
div: Div,
|
||||
resolver: ExpressionResolver
|
||||
): Sequence<DivItemBuilderResult> {
|
||||
resolver: ExpressionResolver,
|
||||
isIncoming: Boolean,
|
||||
): Sequence<TransitionData> {
|
||||
val selectors = ArrayDeque<DivTransitionSelector>().apply {
|
||||
addLast(divData?.transitionAnimationSelector?.evaluate(resolver) ?: DivTransitionSelector.NONE)
|
||||
}
|
||||
|
||||
return div.walk(resolver)
|
||||
.onEnter { div ->
|
||||
if (div is Div.State) selectors.addLast(div.value.transitionAnimationSelector.evaluate(resolver))
|
||||
true
|
||||
}
|
||||
.onLeave { div ->
|
||||
if (div is Div.State) selectors.removeLast()
|
||||
}
|
||||
.filter { item ->
|
||||
item.div.value().transitionTriggers?.allowsTransitionsOnDataChange()
|
||||
return div.walk(resolver) { item ->
|
||||
item.toTransitionData(isIncoming) { div ->
|
||||
div.transitionTriggers?.allowsTransitionsOnDataChange()
|
||||
?: selectors.lastOrNull()?.allowsTransitionsOnDataChange()
|
||||
?: false
|
||||
}
|
||||
}.onEnter { div ->
|
||||
if (div is Div.State) selectors.addLast(div.value.transitionAnimationSelector.evaluate(resolver))
|
||||
true
|
||||
}.onLeave { div ->
|
||||
if (div is Div.State) selectors.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
fun startDivAnimation(): Unit = bindingDispatcher.runWithinBindingContext {
|
||||
|
||||
+26
-103
@@ -11,26 +11,23 @@ import com.yandex.div.core.annotations.Mockable
|
||||
import com.yandex.div.core.dagger.DivViewScope
|
||||
import com.yandex.div.core.dagger.Names
|
||||
import com.yandex.div.core.util.androidInterpolator
|
||||
import com.yandex.div.core.util.walk
|
||||
import com.yandex.div.core.view2.animations.DivTransition
|
||||
import com.yandex.div.core.view2.animations.Fade
|
||||
import com.yandex.div.core.view2.animations.Scale
|
||||
import com.yandex.div.core.view2.animations.Slide
|
||||
import com.yandex.div.core.view2.animations.plusAssign
|
||||
import com.yandex.div.core.view2.animations.TransitionData
|
||||
import com.yandex.div.core.view2.divs.toPx
|
||||
import com.yandex.div.internal.core.DivItemBuilderResult
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
import com.yandex.div2.Div
|
||||
import com.yandex.div2.DivAppearanceTransition
|
||||
import com.yandex.div2.DivChangeTransition
|
||||
import com.yandex.div2.DivSlideTransition
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
import kotlin.math.max
|
||||
|
||||
@DivViewScope
|
||||
@Mockable
|
||||
internal class DivTransitionBuilder @Inject constructor(
|
||||
@Named(Names.CONTEXT) private val context: Context,
|
||||
@param:Named(Names.CONTEXT) private val context: Context,
|
||||
private val viewIdProvider: DivViewIdProvider
|
||||
) {
|
||||
|
||||
@@ -38,116 +35,42 @@ internal class DivTransitionBuilder @Inject constructor(
|
||||
get() = context.resources.displayMetrics
|
||||
|
||||
fun buildTransitions(
|
||||
fromDiv: Div?,
|
||||
toDiv: Div?,
|
||||
fromResolver: ExpressionResolver,
|
||||
toResolver: ExpressionResolver,
|
||||
from: Sequence<TransitionData>?,
|
||||
to: Sequence<TransitionData>?,
|
||||
): TransitionSet {
|
||||
return buildTransitions(
|
||||
from = fromDiv?.walk(fromResolver),
|
||||
to = toDiv?.walk(fromResolver),
|
||||
fromResolver,
|
||||
toResolver
|
||||
)
|
||||
}
|
||||
|
||||
fun buildTransitions(
|
||||
from: Sequence<DivItemBuilderResult>?,
|
||||
to: Sequence<DivItemBuilderResult>?,
|
||||
fromResolver: ExpressionResolver,
|
||||
toResolver: ExpressionResolver,
|
||||
): TransitionSet {
|
||||
val transitionSet = TransitionSet().apply {
|
||||
ordering = TransitionSet.ORDERING_TOGETHER
|
||||
}
|
||||
|
||||
if (from != null) {
|
||||
transitionSet += buildOutgoingTransitions(from, fromResolver)
|
||||
}
|
||||
|
||||
if (from != null && to != null) {
|
||||
transitionSet += buildChangeTransitions(from, fromResolver)
|
||||
}
|
||||
|
||||
if (to != null) {
|
||||
transitionSet += buildIncomingTransitions(to, toResolver)
|
||||
}
|
||||
|
||||
val transitionSet = TransitionSet()
|
||||
transitionSet.ordering = TransitionSet.ORDERING_TOGETHER
|
||||
from?.forEach { transitionSet.addData(it) }
|
||||
to?.forEach { transitionSet.addData(it) }
|
||||
return transitionSet
|
||||
}
|
||||
|
||||
private fun buildOutgoingTransitions(
|
||||
itemSequence: Sequence<DivItemBuilderResult>,
|
||||
resolver: ExpressionResolver
|
||||
): List<Transition> {
|
||||
val transitions = mutableListOf<Transition>()
|
||||
|
||||
itemSequence.forEach { item ->
|
||||
val id = item.div.value().id
|
||||
val outgoingTransition = item.div.value().transitionOut
|
||||
if (id != null && outgoingTransition != null) {
|
||||
val transition = outgoingTransition.toAndroidTransition(Visibility.MODE_OUT, resolver).apply {
|
||||
addTarget(viewIdProvider.getViewId(id))
|
||||
}
|
||||
transitions += transition
|
||||
}
|
||||
private fun TransitionSet.addData(data: TransitionData) {
|
||||
val id = viewIdProvider.getViewId(data.viewId)
|
||||
data.transitions.forEach {
|
||||
val transition = it.toAndroidTransition(data.resolver)
|
||||
transition.addTarget(id)
|
||||
addTransition(transition)
|
||||
}
|
||||
|
||||
return transitions
|
||||
}
|
||||
|
||||
private fun buildChangeTransitions(
|
||||
itemSequence: Sequence<DivItemBuilderResult>,
|
||||
resolver: ExpressionResolver
|
||||
): List<Transition> {
|
||||
val transitions = mutableListOf<Transition>()
|
||||
|
||||
itemSequence.forEach { item ->
|
||||
val id = item.div.value().id
|
||||
val changeTransition = item.div.value().transitionChange
|
||||
if (id != null && changeTransition != null) {
|
||||
val transition = changeTransition.toAndroidTransition(resolver).apply {
|
||||
addTarget(viewIdProvider.getViewId(id))
|
||||
}
|
||||
transitions += transition
|
||||
}
|
||||
}
|
||||
|
||||
return transitions
|
||||
}
|
||||
|
||||
private fun buildIncomingTransitions(
|
||||
itemSequence: Sequence<DivItemBuilderResult>,
|
||||
resolver: ExpressionResolver
|
||||
): List<Transition> {
|
||||
val transitions = mutableListOf<Transition>()
|
||||
|
||||
itemSequence.forEach { item ->
|
||||
val id = item.div.value().id
|
||||
val incomingTransition = item.div.value().transitionIn
|
||||
if (id != null && incomingTransition != null) {
|
||||
val transition = incomingTransition.toAndroidTransition(Visibility.MODE_IN, resolver).apply {
|
||||
addTarget(viewIdProvider.getViewId(id))
|
||||
}
|
||||
transitions += transition
|
||||
}
|
||||
}
|
||||
|
||||
return transitions
|
||||
}
|
||||
|
||||
fun createAndroidTransition(
|
||||
divAppearanceTransition: DivAppearanceTransition?,
|
||||
@Visibility.Mode transitionMode: Int,
|
||||
resolver: ExpressionResolver
|
||||
): Transition? {
|
||||
if (divAppearanceTransition == null) return null
|
||||
): Transition? = divAppearanceTransition?.toAndroidTransition(transitionMode, resolver)
|
||||
|
||||
return divAppearanceTransition.toAndroidTransition(transitionMode, resolver)
|
||||
}
|
||||
private fun DivTransition.toAndroidTransition(resolver: ExpressionResolver): Transition {
|
||||
return when (this) {
|
||||
is DivTransition.Appearance -> value.toAndroidTransition(mode, resolver)
|
||||
is DivTransition.Change -> value.toAndroidTransition(resolver)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DivAppearanceTransition.toAndroidTransition(@Visibility.Mode transitionMode: Int,
|
||||
resolver: ExpressionResolver): Transition {
|
||||
private fun DivAppearanceTransition.toAndroidTransition(
|
||||
@Visibility.Mode transitionMode: Int,
|
||||
resolver: ExpressionResolver,
|
||||
): Transition {
|
||||
return when (this) {
|
||||
is DivAppearanceTransition.Set -> {
|
||||
TransitionSet().apply {
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.yandex.div.core.view2.animations
|
||||
|
||||
import androidx.transition.Visibility
|
||||
import com.yandex.div2.DivAppearanceTransition
|
||||
import com.yandex.div2.DivChangeTransition
|
||||
|
||||
internal sealed class DivTransition {
|
||||
class Appearance(val value: DivAppearanceTransition, @field:Visibility.Mode val mode: Int) : DivTransition()
|
||||
class Change(val value: DivChangeTransition) : DivTransition()
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.yandex.div.core.view2.animations
|
||||
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
|
||||
internal class TransitionData(
|
||||
val viewId: String,
|
||||
val transitions: List<DivTransition>,
|
||||
val resolver: ExpressionResolver,
|
||||
)
|
||||
+27
-20
@@ -3,26 +3,9 @@ package com.yandex.div.core.view2.animations
|
||||
import androidx.transition.Transition
|
||||
import androidx.transition.TransitionListenerAdapter
|
||||
import androidx.transition.TransitionSet
|
||||
|
||||
internal operator fun TransitionSet.plusAssign(transition: Transition) {
|
||||
addTransition(transition)
|
||||
}
|
||||
|
||||
internal operator fun TransitionSet.plusAssign(transitions: Iterable<Transition>) {
|
||||
transitions.forEach { transition ->
|
||||
addTransition(transition)
|
||||
}
|
||||
}
|
||||
|
||||
internal operator fun TransitionSet.minusAssign(transition: Transition) {
|
||||
removeTransition(transition)
|
||||
}
|
||||
|
||||
internal operator fun TransitionSet.minusAssign(transitions: Iterable<Transition>) {
|
||||
transitions.forEach { transition ->
|
||||
removeTransition(transition)
|
||||
}
|
||||
}
|
||||
import androidx.transition.Visibility
|
||||
import com.yandex.div.internal.core.DivItemBuilderResult
|
||||
import com.yandex.div2.DivBase
|
||||
|
||||
internal inline fun TransitionSet.forEach(crossinline block: (Transition) -> Unit) {
|
||||
val count = transitionCount
|
||||
@@ -58,3 +41,27 @@ internal fun Transition.enumerateTargetIds(): List<Int> {
|
||||
}
|
||||
return targetIds.toList()
|
||||
}
|
||||
|
||||
internal inline fun DivItemBuilderResult.toTransitionData(
|
||||
isIncoming: Boolean,
|
||||
allowsTransitions: (DivBase) -> Boolean,
|
||||
): TransitionData? {
|
||||
val div = div.value()
|
||||
|
||||
if (!allowsTransitions(div)) return null
|
||||
|
||||
val id = div.id ?: return null
|
||||
|
||||
if (isIncoming) {
|
||||
return div.transitionIn?.let {
|
||||
val transition = DivTransition.Appearance(it, Visibility.MODE_IN)
|
||||
TransitionData(id, listOf(transition), expressionResolver)
|
||||
}
|
||||
}
|
||||
|
||||
val transitionChange = div.transitionChange?.let { DivTransition.Change(it) }
|
||||
val transitionOut = div.transitionOut?.let { DivTransition.Appearance(it, Visibility.MODE_OUT) }
|
||||
|
||||
if (transitionChange == null && transitionOut == null) return null
|
||||
return TransitionData(id, listOfNotNull(transitionChange, transitionOut), expressionResolver)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.yandex.div.core.view2.divs
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.view.animation.AnimationSet
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.doOnNextLayout
|
||||
@@ -35,11 +36,14 @@ import com.yandex.div.core.view2.DivViewBinder
|
||||
import com.yandex.div.core.view2.DivViewCreator
|
||||
import com.yandex.div.core.view2.DivVisibilityActionTracker
|
||||
import com.yandex.div.core.view2.animations.DivComparator
|
||||
import com.yandex.div.core.view2.animations.DivTransition
|
||||
import com.yandex.div.core.view2.animations.Fade
|
||||
import com.yandex.div.core.view2.animations.Scale
|
||||
import com.yandex.div.core.view2.animations.SceneRootWatcher
|
||||
import com.yandex.div.core.view2.animations.TransitionData
|
||||
import com.yandex.div.core.view2.animations.VerticalTranslation
|
||||
import com.yandex.div.core.view2.animations.allowsTransitionsOnStateChange
|
||||
import com.yandex.div.core.view2.animations.toTransitionData
|
||||
import com.yandex.div.core.view2.divs.widgets.DivHolderView
|
||||
import com.yandex.div.core.view2.divs.widgets.DivStateLayout
|
||||
import com.yandex.div.core.view2.divs.widgets.ReleaseUtils.releaseAndRemoveChildren
|
||||
@@ -47,6 +51,7 @@ import com.yandex.div.core.view2.errors.ErrorCollectors
|
||||
import com.yandex.div.core.view2.state.DivStateTransitionHolder
|
||||
import com.yandex.div.internal.KLog
|
||||
import com.yandex.div.internal.widget.DivLayoutParams
|
||||
import com.yandex.div.internal.widget.DivLayoutParams.Companion.WRAP_CONTENT_CONSTRAINED
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
import com.yandex.div.json.missingValue
|
||||
import com.yandex.div2.Div
|
||||
@@ -106,7 +111,7 @@ internal class DivStateBinder @Inject constructor(
|
||||
view.bind(context, divValue, oldDivState?.value, newState, path)
|
||||
}
|
||||
|
||||
view.bindState(context, divValue, newState, oldDivState?.value, oldState, oldDiv, path, oldResolver, id)
|
||||
view.bindState(context, divValue, oldDivState?.value, newState, oldState, oldDiv, path, oldResolver, id)
|
||||
}
|
||||
|
||||
private fun DivStateLayout.bind(
|
||||
@@ -141,10 +146,8 @@ internal class DivStateBinder @Inject constructor(
|
||||
|
||||
private fun DivStateLayout.bindState(
|
||||
bindingContext: BindingContext,
|
||||
div: DivState,
|
||||
newState: DivState.State,
|
||||
oldDivState: DivState?,
|
||||
oldState: DivState.State?,
|
||||
divState: DivState, oldDivState: DivState?,
|
||||
newState: DivState.State, oldState: DivState.State,
|
||||
oldDiv: Div?,
|
||||
path: DivStatePath,
|
||||
oldResolver: ExpressionResolver?,
|
||||
@@ -165,10 +168,23 @@ internal class DivStateBinder @Inject constructor(
|
||||
if (stateId != newState.stateId) {
|
||||
incoming = newStateDiv?.let { getIncomingView(reusableIncomingView, it, resolver) }
|
||||
|
||||
replaceViewsAnimated(bindingContext, div, newState, oldState, incoming, outgoing)?.let { transition ->
|
||||
TransitionManager.endTransitions(this)
|
||||
SceneRootWatcher.watchFor(this, transition)
|
||||
TransitionManager.beginDelayedTransition(this, transition)
|
||||
val (sceneRoot, parentTransitions) = outgoing?.let {
|
||||
oldState.div?.value()?.transitionChange?.let { transitionChange ->
|
||||
findTopWrapContentParent(listOf(DivTransition.Change(transitionChange)))
|
||||
}
|
||||
} ?: (this to null)
|
||||
|
||||
replaceViewsAnimated(
|
||||
divState,
|
||||
newState, oldState,
|
||||
incoming, outgoing,
|
||||
resolver, oldResolver,
|
||||
parentTransitions,
|
||||
divView,
|
||||
)?.let { transition ->
|
||||
TransitionManager.endTransitions(sceneRoot)
|
||||
SceneRootWatcher.watchFor(sceneRoot, transition)
|
||||
TransitionManager.beginDelayedTransition(sceneRoot, transition)
|
||||
}
|
||||
releaseAndRemoveChildren(divView)
|
||||
incoming?.let {
|
||||
@@ -176,7 +192,7 @@ internal class DivStateBinder @Inject constructor(
|
||||
newStateDiv?.let { viewBinder.get().bind(bindingContext, incoming, it, currentPath) }
|
||||
}
|
||||
if (outgoing != null) {
|
||||
divView.divTransitionHandler.runTransitions(root = this, endTransitions = false)
|
||||
divView.divTransitionHandler.runTransitions(root = sceneRoot, endTransitions = false)
|
||||
}
|
||||
} else if (newStateDivValue != null) {
|
||||
val areDivsReplaceable = outgoing != null && oldResolver != null &&
|
||||
@@ -197,7 +213,7 @@ internal class DivStateBinder @Inject constructor(
|
||||
// I can't explain this. It's black magic.
|
||||
outgoing.startAnimation(AnimationSet(false))
|
||||
// Sometimes we receive same state and do not want to untrack visibility actions
|
||||
if (oldDivState != div || newState != oldState) {
|
||||
if (oldDivState != divState || newState != oldState) {
|
||||
divView.unbindViewFromDiv(outgoing)
|
||||
if (oldDiv != null && oldResolver != null) {
|
||||
// We pass null instead of outgoing view to mark previous state as invisible
|
||||
@@ -240,10 +256,33 @@ internal class DivStateBinder @Inject constructor(
|
||||
this.path = currentPath
|
||||
|
||||
if (outgoing != null) {
|
||||
runtimeVisitor.createAndAttachRuntimesToState(divView, div, path, resolver)
|
||||
runtimeVisitor.createAndAttachRuntimesToState(divView, divState, path, resolver)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ViewGroup.findTopWrapContentParent(
|
||||
changeTransition: List<DivTransition.Change>,
|
||||
childItems: Sequence<TransitionData>? = null,
|
||||
childHasFixedWidth: Boolean = false,
|
||||
childHasFixedHeight: Boolean = false,
|
||||
): Pair<ViewGroup, Sequence<TransitionData>?> {
|
||||
val lp = layoutParams ?: return this to childItems
|
||||
val hasFixedWidth = childHasFixedWidth || (lp.width != WRAP_CONTENT && lp.width != WRAP_CONTENT_CONSTRAINED)
|
||||
val hasFixedHeight = childHasFixedHeight || (lp.height != WRAP_CONTENT && lp.height != WRAP_CONTENT_CONSTRAINED)
|
||||
if (hasFixedWidth && hasFixedHeight) return this to childItems
|
||||
|
||||
val divHolder = asDivHolderView ?: return this to childItems
|
||||
val div = divHolder.div ?: return this to childItems
|
||||
val id = div.value().id ?: return this to childItems
|
||||
val resolver = divHolder.bindingContext?.expressionResolver ?: return this to childItems
|
||||
|
||||
val item = sequenceOf(TransitionData(id, changeTransition, resolver))
|
||||
val items = childItems?.let { item + it } ?: item
|
||||
|
||||
val parent = parent as? ViewGroup ?: return this to items
|
||||
return parent.findTopWrapContentParent(changeTransition, items, hasFixedWidth, hasFixedHeight)
|
||||
}
|
||||
|
||||
private fun getIncomingView(reusableIncomingView: View?, div: Div, resolver: ExpressionResolver) =
|
||||
reusableIncomingView ?: viewCreator.create(div, resolver).apply { createLayoutParams() }
|
||||
|
||||
@@ -320,81 +359,78 @@ internal class DivStateBinder @Inject constructor(
|
||||
}
|
||||
|
||||
private fun replaceViewsAnimated(
|
||||
context: BindingContext,
|
||||
divState: DivState,
|
||||
incomingState: DivState.State,
|
||||
outgoingState: DivState.State?,
|
||||
incoming: View?,
|
||||
outgoing: View?
|
||||
incomingState: DivState.State, outgoingState: DivState.State,
|
||||
incoming: View?, outgoing: View?,
|
||||
resolver: ExpressionResolver, oldResolver: ExpressionResolver?,
|
||||
parentItems: Sequence<TransitionData>?,
|
||||
divView: Div2View,
|
||||
): Transition? {
|
||||
val oldResolver = outgoing?.bindingContext?.expressionResolver
|
||||
?: return setupAnimation(context, incomingState, outgoingState, incoming, outgoing)
|
||||
oldResolver ?: return setupAnimation(incomingState, outgoingState, incoming, outgoing, resolver, null)
|
||||
|
||||
val resolver = context.expressionResolver
|
||||
return if (divState.allowsTransitionsOnStateChange(resolver)
|
||||
&& (outgoingState?.div?.containsStateInnerTransitions(oldResolver) == true
|
||||
&& (outgoingState.div?.containsStateInnerTransitions(oldResolver) == true
|
||||
|| incomingState.div?.containsStateInnerTransitions(resolver) == true)) {
|
||||
setupTransitions(
|
||||
context.divView.viewComponent.transitionBuilder,
|
||||
context.divView.viewComponent.stateTransitionHolder,
|
||||
incomingState,
|
||||
outgoingState,
|
||||
resolver,
|
||||
oldResolver
|
||||
divView.viewComponent.transitionBuilder,
|
||||
divView.viewComponent.stateTransitionHolder,
|
||||
incomingState, outgoingState,
|
||||
resolver, oldResolver,
|
||||
parentItems
|
||||
)
|
||||
} else {
|
||||
setupAnimation(context, incomingState, outgoingState, incoming, outgoing)
|
||||
setupAnimation(incomingState, outgoingState, incoming, outgoing, resolver, oldResolver)
|
||||
}
|
||||
}
|
||||
|
||||
private fun View.createLayoutParams() {
|
||||
layoutParams = DivLayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
layoutParams = DivLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, WRAP_CONTENT)
|
||||
}
|
||||
|
||||
private fun setupTransitions(
|
||||
transitionBuilder: DivTransitionBuilder,
|
||||
transitionHolder: DivStateTransitionHolder,
|
||||
incomingState: DivState.State,
|
||||
outgoingState: DivState.State?,
|
||||
incomingResolver: ExpressionResolver,
|
||||
outgoingResolver: ExpressionResolver,
|
||||
incomingState: DivState.State, outgoingState: DivState.State,
|
||||
incomingResolver: ExpressionResolver, outgoingResolver: ExpressionResolver,
|
||||
parentTransitions: Sequence<TransitionData>?,
|
||||
) : Transition? {
|
||||
if (incomingState == outgoingState) {
|
||||
return null
|
||||
}
|
||||
|
||||
val outgoingTransitions = outgoingState.toTransitionSequence(outgoingResolver, isIncoming = false)
|
||||
val from = when {
|
||||
parentTransitions == null -> outgoingTransitions
|
||||
outgoingTransitions == null -> parentTransitions
|
||||
else -> parentTransitions + outgoingTransitions
|
||||
}
|
||||
val transition = transitionBuilder.buildTransitions(
|
||||
from = outgoingState?.div?.walk(outgoingResolver)
|
||||
?.onEnter { div -> div !is Div.State }
|
||||
?.filter { item ->
|
||||
item.div.value().transitionTriggers?.allowsTransitionsOnStateChange() ?: true
|
||||
},
|
||||
to = incomingState.div?.walk(incomingResolver)
|
||||
?.onEnter { div -> div !is Div.State }
|
||||
?.filter { item ->
|
||||
item.div.value().transitionTriggers?.allowsTransitionsOnStateChange() ?: true
|
||||
},
|
||||
fromResolver = outgoingResolver,
|
||||
toResolver = incomingResolver
|
||||
from = from,
|
||||
to = incomingState.toTransitionSequence(incomingResolver, isIncoming = true)
|
||||
)
|
||||
|
||||
transitionHolder.append(transition)
|
||||
return transition
|
||||
}
|
||||
|
||||
private fun DivState.State.toTransitionSequence(
|
||||
resolver: ExpressionResolver,
|
||||
isIncoming: Boolean
|
||||
): Sequence<TransitionData>? {
|
||||
return div?.walk(resolver) { item ->
|
||||
item.toTransitionData(isIncoming) { div ->
|
||||
div.transitionTriggers?.allowsTransitionsOnStateChange() ?: true
|
||||
}
|
||||
}?.onEnter { div -> div !is Div.State }
|
||||
}
|
||||
|
||||
private fun setupAnimation(
|
||||
context: BindingContext,
|
||||
incomingState: DivState.State,
|
||||
outgoingState: DivState.State?,
|
||||
incoming: View?,
|
||||
outgoing: View?
|
||||
incomingState: DivState.State, outgoingState: DivState.State,
|
||||
incoming: View?, outgoing: View?,
|
||||
resolver: ExpressionResolver, outResolver: ExpressionResolver?,
|
||||
): Transition? {
|
||||
val resolver = context.expressionResolver
|
||||
val animationIn = incomingState.animationIn
|
||||
val animationOut = outgoingState?.animationOut
|
||||
val animationOut = outgoingState.animationOut
|
||||
if (animationIn != null || animationOut != null ) {
|
||||
val transition = TransitionSet()
|
||||
if (animationIn != null && incoming != null) {
|
||||
@@ -416,7 +452,6 @@ internal class DivStateBinder @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
val outResolver = outgoing?.bindingContext?.expressionResolver
|
||||
if (animationOut != null && outResolver != null) {
|
||||
val animationsOut = if (animationOut.name.evaluate(outResolver) != DivAnimation.Name.SET) {
|
||||
listOf(animationOut)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.yandex.div.core.view2.divs
|
||||
|
||||
import androidx.transition.TransitionSet
|
||||
import com.yandex.div.DivDataTag
|
||||
import com.yandex.div.core.DivActionPerformer
|
||||
import com.yandex.div.core.DivCustomContainerViewAdapter
|
||||
@@ -82,9 +81,7 @@ open class DivBinderTest {
|
||||
whenever(divView.releaseViewVisitor) doReturn this
|
||||
}
|
||||
|
||||
private val transitionBuilder = mock<DivTransitionBuilder> {
|
||||
on { buildTransitions(fromDiv = anyOrNull(), toDiv = anyOrNull(), any(), any()) } doReturn TransitionSet()
|
||||
}
|
||||
private val transitionBuilder = mock<DivTransitionBuilder>()
|
||||
|
||||
private val viewIdProvider = DivViewIdProvider()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user