Fade.java revision b31c3281d870e9abb673db239234d580dcc4feff
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.transition; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.content.Context; 23import android.content.res.TypedArray; 24import android.content.res.XmlResourceParser; 25import androidx.annotation.NonNull; 26import androidx.core.content.res.TypedArrayUtils; 27import androidx.core.view.ViewCompat; 28import android.util.AttributeSet; 29import android.util.Log; 30import android.view.View; 31import android.view.ViewGroup; 32 33/** 34 * This transition tracks changes to the visibility of target views in the 35 * start and end scenes and fades views in or out when they become visible 36 * or non-visible. Visibility is determined by both the 37 * {@link View#setVisibility(int)} state of the view as well as whether it 38 * is parented in the current view hierarchy. 39 * 40 * <p>The ability of this transition to fade out a particular view, and the 41 * way that that fading operation takes place, is based on 42 * the situation of the view in the view hierarchy. For example, if a view was 43 * simply removed from its parent, then the view will be added into a {@link 44 * android.view.ViewGroupOverlay} while fading. If a visible view is 45 * changed to be {@link View#GONE} or {@link View#INVISIBLE}, then the 46 * visibility will be changed to {@link View#VISIBLE} for the duration of 47 * the animation. However, if a view is in a hierarchy which is also altering 48 * its visibility, the situation can be more complicated. In general, if a 49 * view that is no longer in the hierarchy in the end scene still has a 50 * parent (so its parent hierarchy was removed, but it was not removed from 51 * its parent), then it will be left alone to avoid side-effects from 52 * improperly removing it from its parent. The only exception to this is if 53 * the previous {@link Scene} was 54 * {@link Scene#getSceneForLayout(android.view.ViewGroup, int, android.content.Context) 55 * created from a layout resource file}, then it is considered safe to un-parent 56 * the starting scene view in order to fade it out.</p> 57 * 58 * <p>A Fade transition can be described in a resource file by using the 59 * tag <code>fade</code>, along with the standard 60 * attributes of {@code Fade} and {@link Transition}.</p> 61 */ 62public class Fade extends Visibility { 63 64 private static final String PROPNAME_TRANSITION_ALPHA = "android:fade:transitionAlpha"; 65 66 private static final String LOG_TAG = "Fade"; 67 68 /** 69 * Fading mode used in {@link #Fade(int)} to make the transition 70 * operate on targets that are appearing. Maybe be combined with 71 * {@link #OUT} to fade both in and out. 72 */ 73 public static final int IN = Visibility.MODE_IN; 74 75 /** 76 * Fading mode used in {@link #Fade(int)} to make the transition 77 * operate on targets that are disappearing. Maybe be combined with 78 * {@link #IN} to fade both in and out. 79 */ 80 public static final int OUT = Visibility.MODE_OUT; 81 82 /** 83 * Constructs a Fade transition that will fade targets in 84 * and/or out, according to the value of fadingMode. 85 * 86 * @param fadingMode The behavior of this transition, a combination of 87 * {@link #IN} and {@link #OUT}. 88 */ 89 public Fade(int fadingMode) { 90 setMode(fadingMode); 91 } 92 93 /** 94 * Constructs a Fade transition that will fade targets in and out. 95 */ 96 public Fade() { 97 } 98 99 public Fade(Context context, AttributeSet attrs) { 100 super(context, attrs); 101 TypedArray a = context.obtainStyledAttributes(attrs, Styleable.FADE); 102 @Mode 103 int fadingMode = TypedArrayUtils.getNamedInt(a, (XmlResourceParser) attrs, "fadingMode", 104 Styleable.Fade.FADING_MODE, getMode()); 105 setMode(fadingMode); 106 a.recycle(); 107 } 108 109 @Override 110 public void captureStartValues(@NonNull TransitionValues transitionValues) { 111 super.captureStartValues(transitionValues); 112 transitionValues.values.put(PROPNAME_TRANSITION_ALPHA, 113 ViewUtils.getTransitionAlpha(transitionValues.view)); 114 } 115 116 /** 117 * Utility method to handle creating and running the Animator. 118 */ 119 private Animator createAnimation(final View view, float startAlpha, float endAlpha) { 120 if (startAlpha == endAlpha) { 121 return null; 122 } 123 ViewUtils.setTransitionAlpha(view, startAlpha); 124 final ObjectAnimator anim = ObjectAnimator.ofFloat(view, ViewUtils.TRANSITION_ALPHA, 125 endAlpha); 126 if (DBG) { 127 Log.d(LOG_TAG, "Created animator " + anim); 128 } 129 FadeAnimatorListener listener = new FadeAnimatorListener(view); 130 anim.addListener(listener); 131 addListener(new TransitionListenerAdapter() { 132 @Override 133 public void onTransitionEnd(@NonNull Transition transition) { 134 ViewUtils.setTransitionAlpha(view, 1); 135 ViewUtils.clearNonTransitionAlpha(view); 136 transition.removeListener(this); 137 } 138 }); 139 return anim; 140 } 141 142 @Override 143 public Animator onAppear(ViewGroup sceneRoot, View view, 144 TransitionValues startValues, 145 TransitionValues endValues) { 146 if (DBG) { 147 View startView = (startValues != null) ? startValues.view : null; 148 Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " 149 + startView + ", " + view); 150 } 151 float startAlpha = getStartAlpha(startValues, 0); 152 if (startAlpha == 1) { 153 startAlpha = 0; 154 } 155 return createAnimation(view, startAlpha, 1); 156 } 157 158 @Override 159 public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues, 160 TransitionValues endValues) { 161 ViewUtils.saveNonTransitionAlpha(view); 162 float startAlpha = getStartAlpha(startValues, 1); 163 return createAnimation(view, startAlpha, 0); 164 } 165 166 private static float getStartAlpha(TransitionValues startValues, float fallbackValue) { 167 float startAlpha = fallbackValue; 168 if (startValues != null) { 169 Float startAlphaFloat = (Float) startValues.values.get(PROPNAME_TRANSITION_ALPHA); 170 if (startAlphaFloat != null) { 171 startAlpha = startAlphaFloat; 172 } 173 } 174 return startAlpha; 175 } 176 177 private static class FadeAnimatorListener extends AnimatorListenerAdapter { 178 179 private final View mView; 180 private boolean mLayerTypeChanged = false; 181 182 FadeAnimatorListener(View view) { 183 mView = view; 184 } 185 186 @Override 187 public void onAnimationStart(Animator animation) { 188 if (ViewCompat.hasOverlappingRendering(mView) 189 && mView.getLayerType() == View.LAYER_TYPE_NONE) { 190 mLayerTypeChanged = true; 191 mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 192 } 193 } 194 195 @Override 196 public void onAnimationEnd(Animator animation) { 197 ViewUtils.setTransitionAlpha(mView, 1); 198 if (mLayerTypeChanged) { 199 mView.setLayerType(View.LAYER_TYPE_NONE, null); 200 } 201 } 202 203 } 204 205} 206