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