Support change_bounds transitions for wrap_content views

commit_hash:0acb1b5c7c980ab6f63704c500ef0f4e5a8f9362
This commit is contained in:
grechka62
2026-04-27 15:03:22 +03:00
parent 160545a8e6
commit 31824c3152
9 changed files with 212 additions and 220 deletions
+2
View File
@@ -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 {
@@ -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 {
@@ -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()
}
@@ -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,
)
@@ -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()