TvTransitionManager.java revision 7d67089aa1e9aa2123c3cd2f386d7019a1544db1
1/*
2 * Copyright (C) 2015 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 com.android.tv.ui;
18
19import android.animation.Animator;
20import android.animation.AnimatorInflater;
21import android.support.annotation.IntDef;
22import android.transition.Fade;
23import android.transition.Scene;
24import android.transition.Transition;
25import android.transition.TransitionInflater;
26import android.transition.TransitionManager;
27import android.transition.TransitionSet;
28import android.transition.TransitionValues;
29import android.view.View;
30import android.view.ViewGroup;
31import android.widget.FrameLayout;
32import android.widget.FrameLayout.LayoutParams;
33
34import com.android.tv.MainActivity;
35import com.android.tv.R;
36
37import java.lang.annotation.Retention;
38import java.lang.annotation.RetentionPolicy;
39
40public class TvTransitionManager extends TransitionManager {
41    @Retention(RetentionPolicy.SOURCE)
42    @IntDef({SCENE_TYPE_EMPTY, SCENE_TYPE_CHANNEL_BANNER, SCENE_TYPE_INPUT_BANNER,
43        SCENE_TYPE_KEYPAD_CHANNEL_SWITCH, SCENE_TYPE_SELECT_INPUT})
44    public @interface SceneType {}
45    public static final int SCENE_TYPE_EMPTY = 0;
46    public static final int SCENE_TYPE_CHANNEL_BANNER = 1;
47    public static final int SCENE_TYPE_INPUT_BANNER = 2;
48    public static final int SCENE_TYPE_KEYPAD_CHANNEL_SWITCH = 3;
49    public static final int SCENE_TYPE_SELECT_INPUT = 4;
50
51    private final MainActivity mMainActivity;
52    private final ViewGroup mSceneContainer;
53    private final ChannelBannerView mChannelBannerView;
54    private final InputBannerView mInputBannerView;
55    private final KeypadChannelSwitchView mKeypadChannelSwitchView;
56    private final SelectInputView mSelectInputView;
57    private final FrameLayout mEmptyView;
58    private ViewGroup mCurrentSceneView;
59    private Animator mEnterAnimator;
60    private Animator mExitAnimator;
61
62    private boolean mInitialized;
63    private Scene mEmptyScene;
64    private Scene mChannelBannerScene;
65    private Scene mInputBannerScene;
66    private Scene mKeypadChannelSwitchScene;
67    private Scene mSelectInputScene;
68    private Scene mCurrentScene;
69
70    private Listener mListener;
71
72    public TvTransitionManager(MainActivity mainActivity, ViewGroup sceneContainer,
73            ChannelBannerView channelBannerView, InputBannerView inputBannerView,
74            KeypadChannelSwitchView keypadChannelSwitchView, SelectInputView selectInputView) {
75        mMainActivity = mainActivity;
76        mSceneContainer = sceneContainer;
77        mChannelBannerView = channelBannerView;
78        mInputBannerView = inputBannerView;
79        mKeypadChannelSwitchView = keypadChannelSwitchView;
80        mSelectInputView = selectInputView;
81        mEmptyView = (FrameLayout) mMainActivity.getLayoutInflater().inflate(
82                R.layout.empty_info_banner, sceneContainer, false);
83        mCurrentSceneView = mEmptyView;
84    }
85
86    public void goToEmptyScene(boolean withAnimation) {
87        if (mCurrentScene == mEmptyScene) {
88            return;
89        }
90        initIfNeeded();
91        if (withAnimation) {
92            transitionTo(mEmptyScene);
93        } else {
94            TransitionManager.go(mEmptyScene, null);
95        }
96    }
97
98    public void goToChannelBannerScene() {
99        initIfNeeded();
100        if (mMainActivity.getCurrentChannel().isPassthrough()) {
101            if (mCurrentScene != mInputBannerScene) {
102                // Show the input banner instead.
103                LayoutParams lp = (LayoutParams) mInputBannerView.getLayoutParams();
104                lp.width = mCurrentScene == mSelectInputScene ? mSelectInputView.getWidth()
105                        : FrameLayout.LayoutParams.WRAP_CONTENT;
106                mInputBannerView.setLayoutParams(lp);
107                mInputBannerView.updateLabel();
108                transitionTo(mInputBannerScene);
109            }
110        } else if (mCurrentScene != mChannelBannerScene) {
111            transitionTo(mChannelBannerScene);
112        }
113    }
114
115    public void goToKeypadChannelSwitchScene() {
116        initIfNeeded();
117        if (mCurrentScene != mKeypadChannelSwitchScene) {
118            transitionTo(mKeypadChannelSwitchScene);
119        }
120    }
121
122    public void goToSelectInputScene() {
123        initIfNeeded();
124        if (mCurrentScene != mSelectInputScene) {
125            transitionTo(mSelectInputScene);
126            mSelectInputView.setCurrentChannel(mMainActivity.getCurrentChannel());
127        }
128    }
129
130    public boolean isSceneActive() {
131        return mCurrentScene != mEmptyScene;
132    }
133
134    public boolean isKeypadChannelSwitchActive() {
135        return mCurrentScene != null && mCurrentScene == mKeypadChannelSwitchScene;
136    }
137
138    public boolean isSelectInputActive() {
139        return mCurrentScene != null && mCurrentScene == mSelectInputScene;
140    }
141
142    public void setListener(Listener listener) {
143        mListener = listener;
144    }
145
146    public void initIfNeeded() {
147        if (mInitialized) {
148            return;
149        }
150        mEnterAnimator = AnimatorInflater.loadAnimator(mMainActivity,
151                R.animator.channel_banner_enter);
152        mExitAnimator = AnimatorInflater.loadAnimator(mMainActivity,
153                R.animator.channel_banner_exit);
154
155        mEmptyScene = new Scene(mSceneContainer, mEmptyView);
156        mEmptyScene.setEnterAction(new Runnable() {
157            @Override
158            public void run() {
159                FrameLayout.LayoutParams emptySceneLayoutParams =
160                        (FrameLayout.LayoutParams) mEmptyView.getLayoutParams();
161                ViewGroup.MarginLayoutParams lp =
162                        (ViewGroup.MarginLayoutParams) mCurrentSceneView.getLayoutParams();
163                emptySceneLayoutParams.topMargin = mCurrentSceneView.getTop();
164                emptySceneLayoutParams.setMarginStart(lp.getMarginStart());
165                emptySceneLayoutParams.height = mCurrentSceneView.getHeight();
166                emptySceneLayoutParams.width = mCurrentSceneView.getWidth();
167                mEmptyView.setLayoutParams(emptySceneLayoutParams);
168                setCurrentScene(mEmptyScene, mEmptyView);
169            }
170        });
171        mEmptyScene.setExitAction(new Runnable() {
172            @Override
173            public void run() {
174                removeAllViewsFromOverlay();
175            }
176        });
177
178        mChannelBannerScene = buildScene(mSceneContainer, mChannelBannerView);
179        mInputBannerScene = buildScene(mSceneContainer, mInputBannerView);
180        mKeypadChannelSwitchScene = buildScene(mSceneContainer, mKeypadChannelSwitchView);
181        mSelectInputScene = buildScene(mSceneContainer, mSelectInputView);
182        mCurrentScene = mEmptyScene;
183
184        // Enter transitions
185        TransitionSet enter = new TransitionSet()
186                .addTransition(new SceneTransition(SceneTransition.ENTER))
187                .addTransition(new Fade(Fade.IN));
188        setTransition(mEmptyScene, mChannelBannerScene, enter);
189        setTransition(mEmptyScene, mInputBannerScene, enter);
190        setTransition(mEmptyScene, mKeypadChannelSwitchScene, enter);
191        setTransition(mEmptyScene, mSelectInputScene, enter);
192
193        // Exit transitions
194        TransitionSet exit = new TransitionSet()
195                .addTransition(new SceneTransition(SceneTransition.EXIT))
196                .addTransition(new Fade(Fade.OUT));
197        setTransition(mChannelBannerScene, mEmptyScene, exit);
198        setTransition(mInputBannerScene, mEmptyScene, exit);
199        setTransition(mKeypadChannelSwitchScene, mEmptyScene, exit);
200        setTransition(mSelectInputScene, mEmptyScene, exit);
201
202        // All other possible transitions between scenes
203        TransitionInflater ti = TransitionInflater.from(mMainActivity);
204        Transition transition = ti.inflateTransition(R.transition.transition_between_scenes);
205        setTransition(mChannelBannerScene, mKeypadChannelSwitchScene, transition);
206        setTransition(mChannelBannerScene, mSelectInputScene, transition);
207        setTransition(mInputBannerScene, mSelectInputScene, transition);
208        setTransition(mKeypadChannelSwitchScene, mChannelBannerScene, transition);
209        setTransition(mKeypadChannelSwitchScene, mSelectInputScene, transition);
210        setTransition(mSelectInputScene, mChannelBannerScene, transition);
211        setTransition(mSelectInputScene, mInputBannerScene, transition);
212
213        mInitialized = true;
214    }
215
216    /**
217     * Returns the type of the given scene.
218     */
219    @SceneType public int getSceneType(Scene scene) {
220        if (scene == mChannelBannerScene) {
221            return SCENE_TYPE_CHANNEL_BANNER;
222        } else if (scene == mInputBannerScene) {
223            return SCENE_TYPE_INPUT_BANNER;
224        } else if (scene == mKeypadChannelSwitchScene) {
225            return SCENE_TYPE_KEYPAD_CHANNEL_SWITCH;
226        } else if (scene == mSelectInputScene) {
227            return SCENE_TYPE_SELECT_INPUT;
228        }
229        return SCENE_TYPE_EMPTY;
230    }
231
232    private void setCurrentScene(Scene scene, ViewGroup sceneView) {
233        if (mListener != null) {
234            mListener.onSceneChanged(getSceneType(mCurrentScene), getSceneType(scene));
235        }
236        mCurrentScene = scene;
237        mCurrentSceneView = sceneView;
238        // TODO: Is this a still valid call?
239        mMainActivity.updateKeyInputFocus();
240    }
241
242    public interface TransitionLayout {
243        // TODO: remove the parameter fromEmptyScene once a bug regarding transition alpha
244        // is fixed. The bug is that the transition alpha is not reset after the transition is
245        // canceled.
246        void onEnterAction(boolean fromEmptyScene);
247
248        void onExitAction();
249    }
250
251    private Scene buildScene(ViewGroup sceneRoot, final TransitionLayout layout) {
252        final Scene scene = new Scene(sceneRoot, (View) layout);
253        scene.setEnterAction(new Runnable() {
254            @Override
255            public void run() {
256                boolean wasEmptyScene = (mCurrentScene == mEmptyScene);
257                setCurrentScene(scene, (ViewGroup) layout);
258                layout.onEnterAction(wasEmptyScene);
259            }
260        });
261        scene.setExitAction(new Runnable() {
262            @Override
263            public void run() {
264                removeAllViewsFromOverlay();
265                layout.onExitAction();
266            }
267        });
268        return scene;
269    }
270
271    private void removeAllViewsFromOverlay() {
272        // Clean up all the animations which can be still running.
273        mSceneContainer.getOverlay().remove(mChannelBannerView);
274        mSceneContainer.getOverlay().remove(mInputBannerView);
275        mSceneContainer.getOverlay().remove(mKeypadChannelSwitchView);
276        mSceneContainer.getOverlay().remove(mSelectInputView);
277    }
278
279    private class SceneTransition extends Transition {
280        static final int ENTER = 0;
281        static final int EXIT = 1;
282
283        private final Animator mAnimator;
284
285        SceneTransition(int mode) {
286            mAnimator = mode == ENTER ? mEnterAnimator : mExitAnimator;
287        }
288
289        @Override
290        public void captureStartValues(TransitionValues transitionValues) {
291        }
292
293        @Override
294        public void captureEndValues(TransitionValues transitionValues) {
295        }
296
297        @Override
298        public Animator createAnimator(
299                ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
300            Animator animator = mAnimator.clone();
301            animator.setTarget(sceneRoot);
302            animator.addListener(new HardwareLayerAnimatorListenerAdapter(sceneRoot));
303            return animator;
304        }
305    }
306
307    /**
308     * An interface for notification of the scene transition.
309     */
310    public interface Listener {
311        /**
312         * Called when the scene changes. This method is called just before the scene transition.
313         */
314        void onSceneChanged(@SceneType int fromSceneType, @SceneType int toSceneType);
315    }
316}
317