Fade.java revision db860514bf4c9aa50169d2c9964d83aec53dabec
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 android.support.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 android.support.annotation.NonNull;
26import android.support.v4.content.res.TypedArrayUtils;
27import android.support.v4.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