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

Commit 5affef07 authored by George Mount's avatar George Mount
Browse files

Fix Fade transition interrupt.

Bug 26963113

When a Fade transition is interrupted and reversed, the
View started the animation from the beginning. This change
captures the previous transitionAlpha and starts the animation
from the previous alpha state.

Change-Id: I801fe9ade6af4cf8446838e231bdc71841668a18
(cherry picked from commit 3cf9fa3d)
parent 95728a5d
Loading
Loading
Loading
Loading
+21 −16
Original line number Diff line number Diff line
@@ -57,9 +57,9 @@ import android.view.ViewGroup;
 * tag <code>fade</code>, along with the standard
 * attributes of {@link android.R.styleable#Fade} and
 * {@link android.R.styleable#Transition}.</p>

 */
public class Fade extends Visibility {
    static final String PROPNAME_TRANSITION_ALPHA = "android:fade:transitionAlpha";

    private static boolean DBG = Transition.DBG && false;

@@ -105,6 +105,13 @@ public class Fade extends Visibility {
        setMode(fadingMode);
    }

    @Override
    public void captureStartValues(TransitionValues transitionValues) {
        super.captureStartValues(transitionValues);
        transitionValues.values.put(PROPNAME_TRANSITION_ALPHA,
                transitionValues.view.getTransitionAlpha());
    }

    /**
     * Utility method to handle creating and running the Animator.
     */
@@ -119,7 +126,6 @@ public class Fade extends Visibility {
        }
        final FadeAnimatorListener listener = new FadeAnimatorListener(view);
        anim.addListener(listener);
        anim.addPauseListener(listener);
        addListener(new TransitionListenerAdapter() {
            @Override
            public void onTransitionEnd(Transition transition) {
@@ -138,18 +144,28 @@ public class Fade extends Visibility {
            Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " +
                    startView + ", " + view);
        }
        return createAnimation(view, 0, 1);
        float startAlpha = 0;
        if (startValues != null) {
            startAlpha = (Float) startValues.values.get(PROPNAME_TRANSITION_ALPHA);
            if (startAlpha == 1) {
                startAlpha = 0;
            }
        }
        return createAnimation(view, startAlpha, 1);
    }

    @Override
    public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,
            TransitionValues endValues) {
        return createAnimation(view, 1, 0);
        float startAlpha = 1;
        if (startValues != null) {
            startAlpha = (Float) startValues.values.get(PROPNAME_TRANSITION_ALPHA);
        }
        return createAnimation(view, startAlpha, 0);
    }

    private static class FadeAnimatorListener extends AnimatorListenerAdapter {
        private final View mView;
        private float mPausedAlpha = -1;
        private boolean mLayerTypeChanged = false;

        public FadeAnimatorListener(View view) {
@@ -171,16 +187,5 @@ public class Fade extends Visibility {
                mView.setLayerType(View.LAYER_TYPE_NONE, null);
            }
        }

        @Override
        public void onAnimationPause(Animator animator) {
            mPausedAlpha = mView.getTransitionAlpha();
            mView.setTransitionAlpha(1);
        }

        @Override
        public void onAnimationResume(Animator animator) {
            mView.setTransitionAlpha(mPausedAlpha);
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -4,7 +4,8 @@
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/white"
    android:orientation="horizontal">
    android:orientation="horizontal"
    android:id="@+id/container">
    <View
        android:layout_width="50dp"
        android:layout_height="50dp"
+200 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.transition;

import android.animation.AnimatorSetActivity;
import android.app.Activity;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.transition.Transition.TransitionListener;
import android.transition.Transition.TransitionListenerAdapter;
import android.view.View;
import android.view.ViewGroup;

import com.android.frameworks.coretests.R;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static android.support.test.espresso.Espresso.onView;

public class FadeTransitionTest extends ActivityInstrumentationTestCase2<AnimatorSetActivity> {
    Activity mActivity;
    public FadeTransitionTest() {
        super(AnimatorSetActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        mActivity = getActivity();
    }

    @SmallTest
    public void testFadeOutAndIn() throws Throwable {
        View square1 = mActivity.findViewById(R.id.square1);
        Fade fadeOut = new Fade(Fade.MODE_OUT);
        TransitionLatch latch = setVisibilityInTransition(fadeOut, R.id.square1, View.INVISIBLE);
        assertTrue(latch.startLatch.await(200, TimeUnit.MILLISECONDS));
        assertEquals(View.VISIBLE, square1.getVisibility());
        Thread.sleep(100);
        assertFalse(square1.getTransitionAlpha() == 0 || square1.getTransitionAlpha() == 1);
        assertTrue(latch.endLatch.await(400, TimeUnit.MILLISECONDS));
        assertEquals(1.0f, square1.getTransitionAlpha());
        assertEquals(View.INVISIBLE, square1.getVisibility());

        Fade fadeIn = new Fade(Fade.MODE_IN);
        latch = setVisibilityInTransition(fadeIn, R.id.square1, View.VISIBLE);
        assertTrue(latch.startLatch.await(200, TimeUnit.MILLISECONDS));
        assertEquals(View.VISIBLE, square1.getVisibility());
        Thread.sleep(100);
        final float transitionAlpha = square1.getTransitionAlpha();
        assertTrue("expecting transitionAlpha to be between 0 and 1. Was " + transitionAlpha,
                transitionAlpha > 0 && transitionAlpha < 1);
        assertTrue(latch.endLatch.await(400, TimeUnit.MILLISECONDS));
        assertEquals(1.0f, square1.getTransitionAlpha());
        assertEquals(View.VISIBLE, square1.getVisibility());
    }

    @SmallTest
    public void testFadeOutInterrupt() throws Throwable {
        View square1 = mActivity.findViewById(R.id.square1);
        Fade fadeOut = new Fade(Fade.MODE_OUT);
        FadeValueCheck fadeOutValueCheck = new FadeValueCheck(square1);
        fadeOut.addListener(fadeOutValueCheck);
        TransitionLatch outLatch = setVisibilityInTransition(fadeOut, R.id.square1, View.INVISIBLE);
        assertTrue(outLatch.startLatch.await(200, TimeUnit.MILLISECONDS));
        Thread.sleep(100);

        Fade fadeIn = new Fade(Fade.MODE_IN);
        FadeValueCheck fadeInValueCheck = new FadeValueCheck(square1);
        fadeIn.addListener(fadeInValueCheck);
        TransitionLatch inLatch = setVisibilityInTransition(fadeIn, R.id.square1, View.VISIBLE);
        assertTrue(inLatch.startLatch.await(200, TimeUnit.MILLISECONDS));

        assertEquals(fadeOutValueCheck.pauseTransitionAlpha, fadeInValueCheck.startTransitionAlpha);
        assertTrue("expecting transitionAlpha to be between 0 and 1. Was " +
                fadeOutValueCheck.pauseTransitionAlpha,
                fadeOutValueCheck.pauseTransitionAlpha > 0 &&
                        fadeOutValueCheck.pauseTransitionAlpha < 1);

        assertTrue(inLatch.endLatch.await(400, TimeUnit.MILLISECONDS));
        assertEquals(1.0f, square1.getTransitionAlpha());
        assertEquals(View.VISIBLE, square1.getVisibility());
    }

    @SmallTest
    public void testFadeInInterrupt() throws Throwable {
        final View square1 = mActivity.findViewById(R.id.square1);
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                square1.setVisibility(View.INVISIBLE);
            }
        });
        Fade fadeIn = new Fade(Fade.MODE_IN);
        FadeValueCheck fadeInValueCheck = new FadeValueCheck(square1);
        fadeIn.addListener(fadeInValueCheck);
        TransitionLatch inLatch = setVisibilityInTransition(fadeIn, R.id.square1, View.VISIBLE);
        assertTrue(inLatch.startLatch.await(200, TimeUnit.MILLISECONDS));
        Thread.sleep(100);

        Fade fadeOut = new Fade(Fade.MODE_OUT);
        FadeValueCheck fadeOutValueCheck = new FadeValueCheck(square1);
        fadeOut.addListener(fadeOutValueCheck);
        TransitionLatch outLatch = setVisibilityInTransition(fadeOut, R.id.square1, View.INVISIBLE);
        assertTrue(outLatch.startLatch.await(200, TimeUnit.MILLISECONDS));

        assertEquals(fadeOutValueCheck.pauseTransitionAlpha, fadeInValueCheck.startTransitionAlpha);
        assertTrue("expecting transitionAlpha to be between 0 and 1. Was " +
                        fadeInValueCheck.pauseTransitionAlpha,
                fadeInValueCheck.pauseTransitionAlpha > 0 &&
                        fadeInValueCheck.pauseTransitionAlpha < 1);

        assertTrue(outLatch.endLatch.await(400, TimeUnit.MILLISECONDS));
        assertEquals(1.0f, square1.getTransitionAlpha());
        assertEquals(View.INVISIBLE, square1.getVisibility());
    }

    public TransitionLatch setVisibilityInTransition(final Transition transition, int viewId,
            final int visibility) throws Throwable {
        final ViewGroup sceneRoot = (ViewGroup) mActivity.findViewById(R.id.container);
        final View view = sceneRoot.findViewById(viewId);
        TransitionLatch latch = new TransitionLatch();
        transition.addListener(latch);
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                TransitionManager.beginDelayedTransition(sceneRoot, transition);
                view.setVisibility(visibility);
            }
        });
        return latch;
    }

    public static class TransitionLatch implements TransitionListener {
        public CountDownLatch startLatch = new CountDownLatch(1);
        public CountDownLatch endLatch = new CountDownLatch(1);
        public CountDownLatch cancelLatch = new CountDownLatch(1);
        public CountDownLatch pauseLatch = new CountDownLatch(1);
        public CountDownLatch resumeLatch = new CountDownLatch(1);

        @Override
        public void onTransitionStart(Transition transition) {
            startLatch.countDown();
        }

        @Override
        public void onTransitionEnd(Transition transition) {
            endLatch.countDown();
            transition.removeListener(this);
        }

        @Override
        public void onTransitionCancel(Transition transition) {
            cancelLatch.countDown();
        }

        @Override
        public void onTransitionPause(Transition transition) {
            pauseLatch.countDown();
        }

        @Override
        public void onTransitionResume(Transition transition) {
            resumeLatch.countDown();
        }
    }

    private static class FadeValueCheck extends TransitionListenerAdapter {
        public float startTransitionAlpha;
        public float pauseTransitionAlpha;
        private final View mView;

        public FadeValueCheck(View view) {
            mView = view;
        }
        @Override
        public void onTransitionStart(Transition transition) {
            startTransitionAlpha = mView.getTransitionAlpha();
        }

        @Override
        public void onTransitionPause(Transition transition) {
            pauseTransitionAlpha = mView.getTransitionAlpha();
        }
    }
}