Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b072e53b authored by Luca Zuccarini's avatar Luca Zuccarini
Browse files

Allow ViewHierarchyAnimator to exclude child views from animations.

This has been a long requested feature and b/283456173 provided the
right opportunity to implement it. I have tested it within the privacy
dialog and the behavior is as expected.

Bug: 283456173
Test: unit tests included
Change-Id: I9b97e2e7aaff603174cabd4328f17ec1884816ee
parent 0b73a074
Loading
Loading
Loading
Loading
+34 −8
Original line number Diff line number Diff line
@@ -70,6 +70,9 @@ class ViewHierarchyAnimator {
         * If a new layout change happens while an animation is already in progress, the animation
         * is updated to continue from the current values to the new end state.
         *
         * A set of [excludedViews] can be passed. If any dependent view from [rootView] matches an
         * entry in this set, changes to that view will not be animated.
         *
         * The animator continues to respond to layout changes until [stopAnimating] is called.
         *
         * Successive calls to this method override the previous settings ([interpolator] and
@@ -82,9 +85,16 @@ class ViewHierarchyAnimator {
        fun animate(
            rootView: View,
            interpolator: Interpolator = DEFAULT_INTERPOLATOR,
            duration: Long = DEFAULT_DURATION
            duration: Long = DEFAULT_DURATION,
            excludedViews: Set<View> = emptySet()
        ): Boolean {
            return animate(rootView, interpolator, duration, ephemeral = false)
            return animate(
                rootView,
                interpolator,
                duration,
                ephemeral = false,
                excludedViews = excludedViews
            )
        }

        /**
@@ -95,16 +105,24 @@ class ViewHierarchyAnimator {
        fun animateNextUpdate(
            rootView: View,
            interpolator: Interpolator = DEFAULT_INTERPOLATOR,
            duration: Long = DEFAULT_DURATION
            duration: Long = DEFAULT_DURATION,
            excludedViews: Set<View> = emptySet()
        ): Boolean {
            return animate(rootView, interpolator, duration, ephemeral = true)
            return animate(
                rootView,
                interpolator,
                duration,
                ephemeral = true,
                excludedViews = excludedViews
            )
        }

        private fun animate(
            rootView: View,
            interpolator: Interpolator,
            duration: Long,
            ephemeral: Boolean
            ephemeral: Boolean,
            excludedViews: Set<View> = emptySet()
        ): Boolean {
            if (
                !occupiesSpace(
@@ -119,7 +137,7 @@ class ViewHierarchyAnimator {
            }

            val listener = createUpdateListener(interpolator, duration, ephemeral)
            addListener(rootView, listener, recursive = true)
            addListener(rootView, listener, recursive = true, excludedViews = excludedViews)
            return true
        }

@@ -921,8 +939,11 @@ class ViewHierarchyAnimator {
        private fun addListener(
            view: View,
            listener: View.OnLayoutChangeListener,
            recursive: Boolean = false
            recursive: Boolean = false,
            excludedViews: Set<View> = emptySet()
        ) {
            if (excludedViews.contains(view)) return

            // Make sure that only one listener is active at a time.
            val previousListener = view.getTag(R.id.tag_layout_listener)
            if (previousListener != null && previousListener is View.OnLayoutChangeListener) {
@@ -933,7 +954,12 @@ class ViewHierarchyAnimator {
            view.setTag(R.id.tag_layout_listener, listener)
            if (view is ViewGroup && recursive) {
                for (i in 0 until view.childCount) {
                    addListener(view.getChildAt(i), listener, recursive = true)
                    addListener(
                        view.getChildAt(i),
                        listener,
                        recursive = true,
                        excludedViews = excludedViews
                    )
                }
            }
        }
+36 −2
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.test.filters.SmallTest
import com.android.app.animation.Interpolators
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.children
import junit.framework.Assert.assertEquals
@@ -19,7 +20,6 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import com.android.app.animation.Interpolators

@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -178,7 +178,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
    }

    @Test
    fun animatesRootAndChildren() {
    fun animatesRootAndChildren_withoutExcludedViews() {
        setUpRootWithChildren()

        val success = ViewHierarchyAnimator.animate(rootView)
@@ -207,6 +207,40 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
        checkBounds(rootView.getChildAt(1), l = 95, t = 0, r = 190, b = 100)
    }

    @Test
    fun animatesRootAndChildren_withExcludedViews() {
        setUpRootWithChildren()

        val success = ViewHierarchyAnimator.animate(
            rootView,
            excludedViews = setOf(rootView.getChildAt(0))
        )
        // Change all bounds.
        rootView.measure(
                View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
        )
        rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)

        assertTrue(success)
        assertNotNull(rootView.getTag(R.id.tag_animator))
        assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
        assertNotNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
        // The initial values for the affected views should be those of the previous layout, while
        // the excluded view should be at the final values from the beginning.
        checkBounds(rootView, l = 0, t = 0, r = 200, b = 100)
        checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
        checkBounds(rootView.getChildAt(1), l = 100, t = 0, r = 200, b = 100)
        endAnimation(rootView)
        assertNull(rootView.getTag(R.id.tag_animator))
        assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
        assertNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
        // The end values should be those of the latest layout.
        checkBounds(rootView, l = 10, t = 20, r = 200, b = 120)
        checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
        checkBounds(rootView.getChildAt(1), l = 90, t = 0, r = 180, b = 100)
    }

    @Test
    fun animatesInvisibleViews() {
        rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)