blob: af507be0a495175d3a71d849cdf08fa3dfb30041 [file] [log] [blame]
/*
* 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 androidx.transition;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.MediumTest;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
@MediumTest
public class FadeTest extends BaseTest {
private View mView;
private ViewGroup mRoot;
@UiThreadTest
@Before
public void setUp() {
mRoot = rule.getActivity().getRoot();
mView = new View(rule.getActivity());
mRoot.addView(mView, new ViewGroup.LayoutParams(100, 100));
}
@Test
public void testMode() {
assertThat(Fade.IN, is(Visibility.MODE_IN));
assertThat(Fade.OUT, is(Visibility.MODE_OUT));
final Fade fade = new Fade();
assertThat(fade.getMode(), is(Visibility.MODE_IN | Visibility.MODE_OUT));
fade.setMode(Visibility.MODE_IN);
assertThat(fade.getMode(), is(Visibility.MODE_IN));
}
@Test
@UiThreadTest
public void testDisappear() {
final Fade fade = new Fade();
final TransitionValues startValues = new TransitionValues();
startValues.view = mView;
fade.captureStartValues(startValues);
mView.setVisibility(View.INVISIBLE);
final TransitionValues endValues = new TransitionValues();
endValues.view = mView;
fade.captureEndValues(endValues);
Animator animator = fade.createAnimator(mRoot, startValues, endValues);
assertThat(animator, is(notNullValue()));
}
@Test
@UiThreadTest
public void testAppear() {
mView.setVisibility(View.INVISIBLE);
final Fade fade = new Fade();
final TransitionValues startValues = new TransitionValues();
startValues.view = mView;
fade.captureStartValues(startValues);
mView.setVisibility(View.VISIBLE);
final TransitionValues endValues = new TransitionValues();
endValues.view = mView;
fade.captureEndValues(endValues);
Animator animator = fade.createAnimator(mRoot, startValues, endValues);
assertThat(animator, is(notNullValue()));
}
@Test
@UiThreadTest
public void testNoChange() {
final Fade fade = new Fade();
final TransitionValues startValues = new TransitionValues();
startValues.view = mView;
fade.captureStartValues(startValues);
final TransitionValues endValues = new TransitionValues();
endValues.view = mView;
fade.captureEndValues(endValues);
Animator animator = fade.createAnimator(mRoot, startValues, endValues);
// No visibility change; no animation should happen
assertThat(animator, is(nullValue()));
}
@Test
public void testFadeOutThenIn() throws Throwable {
// Fade out
final Runnable interrupt = mock(Runnable.class);
float[] valuesOut = new float[2];
final InterruptibleFade fadeOut = new InterruptibleFade(Fade.MODE_OUT, interrupt,
valuesOut);
final Transition.TransitionListener listenerOut = mock(Transition.TransitionListener.class);
fadeOut.addListener(listenerOut);
changeVisibility(fadeOut, mRoot, mView, View.INVISIBLE);
verify(listenerOut, timeout(3000)).onTransitionStart(any(Transition.class));
// The view is in the middle of fading out
verify(interrupt, timeout(3000)).run();
// Fade in
float[] valuesIn = new float[2];
final InterruptibleFade fadeIn = new InterruptibleFade(Fade.MODE_IN, null, valuesIn);
final Transition.TransitionListener listenerIn = mock(Transition.TransitionListener.class);
fadeIn.addListener(listenerIn);
changeVisibility(fadeIn, mRoot, mView, View.VISIBLE);
verify(listenerOut, timeout(3000)).onTransitionPause(any(Transition.class));
verify(listenerIn, timeout(3000)).onTransitionStart(any(Transition.class));
assertThat(valuesOut[1], allOf(greaterThan(0f), lessThan(1f)));
if (Build.VERSION.SDK_INT >= 19 && fadeOut.mInitialAlpha >= 0) {
// These won't match on API levels 18 and below due to lack of Animator pause.
assertEquals(valuesOut[1], valuesIn[0], 0.01f);
}
verify(listenerIn, timeout(3000)).onTransitionEnd(any(Transition.class));
assertThat(mView.getVisibility(), is(View.VISIBLE));
assertEquals(valuesIn[1], 1.f, 0.01f);
}
@Test
public void testFadeInThenOut() throws Throwable {
changeVisibility(null, mRoot, mView, View.INVISIBLE);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
// Fade in
final Runnable interrupt = mock(Runnable.class);
float[] valuesIn = new float[2];
final InterruptibleFade fadeIn = new InterruptibleFade(Fade.MODE_IN, interrupt, valuesIn);
final Transition.TransitionListener listenerIn = mock(Transition.TransitionListener.class);
fadeIn.addListener(listenerIn);
changeVisibility(fadeIn, mRoot, mView, View.VISIBLE);
verify(listenerIn, timeout(3000)).onTransitionStart(any(Transition.class));
// The view is in the middle of fading in
verify(interrupt, timeout(3000)).run();
// Fade out
float[] valuesOut = new float[2];
final InterruptibleFade fadeOut = new InterruptibleFade(Fade.MODE_OUT, null, valuesOut);
final Transition.TransitionListener listenerOut = mock(Transition.TransitionListener.class);
fadeOut.addListener(listenerOut);
changeVisibility(fadeOut, mRoot, mView, View.INVISIBLE);
verify(listenerIn, timeout(3000)).onTransitionPause(any(Transition.class));
verify(listenerOut, timeout(3000)).onTransitionStart(any(Transition.class));
assertThat(valuesIn[1], allOf(greaterThan(0f), lessThan(1f)));
if (Build.VERSION.SDK_INT >= 19 && fadeIn.mInitialAlpha >= 0) {
// These won't match on API levels 18 and below due to lack of Animator pause.
assertEquals(valuesIn[1], valuesOut[0], 0.01f);
}
verify(listenerOut, timeout(3000)).onTransitionEnd(any(Transition.class));
assertThat(mView.getVisibility(), is(View.INVISIBLE));
}
@Test
public void testFadeWithAlpha() throws Throwable {
// Set the view alpha to 0.5
rule.runOnUiThread(new Runnable() {
@Override
public void run() {
mView.setAlpha(0.5f);
}
});
// Fade out
final Fade fadeOut = new Fade(Fade.OUT);
final Transition.TransitionListener listenerOut = mock(Transition.TransitionListener.class);
fadeOut.addListener(listenerOut);
changeVisibility(fadeOut, mRoot, mView, View.INVISIBLE);
verify(listenerOut, timeout(3000)).onTransitionStart(any(Transition.class));
verify(listenerOut, timeout(3000)).onTransitionEnd(any(Transition.class));
// Fade in
final Fade fadeIn = new Fade(Fade.IN);
final Transition.TransitionListener listenerIn = mock(Transition.TransitionListener.class);
fadeIn.addListener(listenerIn);
changeVisibility(fadeIn, mRoot, mView, View.VISIBLE);
verify(listenerIn, timeout(3000)).onTransitionStart(any(Transition.class));
verify(listenerIn, timeout(3000)).onTransitionEnd(any(Transition.class));
// Confirm that the view still has the original alpha value
assertThat(mView.getVisibility(), is(View.VISIBLE));
assertEquals(0.5f, mView.getAlpha(), 0.01f);
}
private void changeVisibility(final Fade fade, final ViewGroup container, final View target,
final int visibility) throws Throwable {
rule.runOnUiThread(new Runnable() {
@Override
public void run() {
if (fade != null) {
TransitionManager.beginDelayedTransition(container, fade);
}
target.setVisibility(visibility);
}
});
}
/**
* A special version of {@link Fade} that runs a specified {@link Runnable} soon after the
* target starts fading in or out.
*/
private static class InterruptibleFade extends Fade {
static final float ALPHA_THRESHOLD = 0.2f;
float mInitialAlpha = -1;
Runnable mMiddle;
final float[] mAlphaValues;
InterruptibleFade(int mode, Runnable middle, float[] alphaValues) {
super(mode);
mMiddle = middle;
mAlphaValues = alphaValues;
}
@Nullable
@Override
public Animator createAnimator(@NonNull ViewGroup sceneRoot,
@Nullable final TransitionValues startValues,
@Nullable final TransitionValues endValues) {
final Animator animator = super.createAnimator(sceneRoot, startValues, endValues);
if (animator instanceof ObjectAnimator) {
((ObjectAnimator) animator).addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final float alpha = (float) animation.getAnimatedValue();
mAlphaValues[1] = alpha;
if (mInitialAlpha < 0) {
mInitialAlpha = alpha;
mAlphaValues[0] = mInitialAlpha;
} else if (Math.abs(alpha - mInitialAlpha) > ALPHA_THRESHOLD) {
if (mMiddle != null) {
mMiddle.run();
mMiddle = null;
}
}
}
});
}
return animator;
}
}
}