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