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 TvTransitionManager( MainActivity mainActivity, ViewGroup sceneContainer, ChannelBannerView channelBannerView, InputBannerView inputBannerView, KeypadChannelSwitchView keypadChannelSwitchView, SelectInputView selectInputView)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 goToEmptyScene(boolean withAnimation)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 goToChannelBannerScene()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 goToKeypadChannelSwitchScene()135 public void goToKeypadChannelSwitchScene() { 136 initIfNeeded(); 137 if (mCurrentScene != mKeypadChannelSwitchScene) { 138 transitionTo(mKeypadChannelSwitchScene); 139 } 140 } 141 goToSelectInputScene()142 public void goToSelectInputScene() { 143 initIfNeeded(); 144 if (mCurrentScene != mSelectInputScene) { 145 mSelectInputView.setCurrentChannel(mMainActivity.getCurrentChannel()); 146 transitionTo(mSelectInputScene); 147 } 148 } 149 isSceneActive()150 public boolean isSceneActive() { 151 return mCurrentScene != mEmptyScene; 152 } 153 isKeypadChannelSwitchActive()154 public boolean isKeypadChannelSwitchActive() { 155 return mCurrentScene != null && mCurrentScene == mKeypadChannelSwitchScene; 156 } 157 isSelectInputActive()158 public boolean isSelectInputActive() { 159 return mCurrentScene != null && mCurrentScene == mSelectInputScene; 160 } 161 setListener(Listener listener)162 public void setListener(Listener listener) { 163 mListener = listener; 164 } 165 initIfNeeded()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 getSceneType(Scene scene)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 setCurrentScene(Scene scene, ViewGroup sceneView)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. onEnterAction(boolean fromEmptyScene)269 void onEnterAction(boolean fromEmptyScene); 270 onExitAction()271 void onExitAction(); 272 } 273 buildScene(ViewGroup sceneRoot, final TransitionLayout layout)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 removeAllViewsFromOverlay()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 SceneTransition(int mode)310 SceneTransition(int mode) { 311 mAnimator = mode == ENTER ? mEnterAnimator : mExitAnimator; 312 } 313 314 @Override captureStartValues(TransitionValues transitionValues)315 public void captureStartValues(TransitionValues transitionValues) {} 316 317 @Override captureEndValues(TransitionValues transitionValues)318 public void captureEndValues(TransitionValues transitionValues) {} 319 320 @Override createAnimator( ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)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 */ onSceneChanged(@ceneType int fromSceneType, @SceneType int toSceneType)335 void onSceneChanged(@SceneType int fromSceneType, @SceneType int toSceneType); 336 } 337 } 338