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