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 InputBannerViewBase 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, InputBannerViewBase inputBannerView, KeypadChannelSwitchView keypadChannelSwitchView, SelectInputView selectInputView)77 public TvTransitionManager( 78 MainActivity mainActivity, 79 ViewGroup sceneContainer, 80 ChannelBannerView channelBannerView, 81 InputBannerViewBase 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 isInputBannerActive()162 public boolean isInputBannerActive() { 163 return mCurrentScene != null && mCurrentScene == mInputBannerScene; 164 } 165 setListener(Listener listener)166 public void setListener(Listener listener) { 167 mListener = listener; 168 } 169 initIfNeeded()170 public void initIfNeeded() { 171 if (mInitialized) { 172 return; 173 } 174 mEnterAnimator = 175 AnimatorInflater.loadAnimator(mMainActivity, R.animator.channel_banner_enter); 176 mExitAnimator = 177 AnimatorInflater.loadAnimator(mMainActivity, R.animator.channel_banner_exit); 178 179 mEmptyScene = new Scene(mSceneContainer, (View) mEmptyView); 180 mEmptyScene.setEnterAction( 181 () -> { 182 FrameLayout.LayoutParams emptySceneLayoutParams = 183 (FrameLayout.LayoutParams) mEmptyView.getLayoutParams(); 184 ViewGroup.MarginLayoutParams lp = 185 (ViewGroup.MarginLayoutParams) mCurrentSceneView.getLayoutParams(); 186 emptySceneLayoutParams.topMargin = mCurrentSceneView.getTop(); 187 emptySceneLayoutParams.setMarginStart(lp.getMarginStart()); 188 emptySceneLayoutParams.height = mCurrentSceneView.getHeight(); 189 emptySceneLayoutParams.width = mCurrentSceneView.getWidth(); 190 mEmptyView.setLayoutParams(emptySceneLayoutParams); 191 setCurrentScene(mEmptyScene, mEmptyView); 192 }); 193 mEmptyScene.setExitAction(this::removeAllViewsFromOverlay); 194 195 mChannelBannerScene = buildScene(mSceneContainer, mChannelBannerView); 196 mInputBannerScene = buildScene(mSceneContainer, mInputBannerView); 197 mKeypadChannelSwitchScene = buildScene(mSceneContainer, mKeypadChannelSwitchView); 198 mSelectInputScene = buildScene(mSceneContainer, mSelectInputView); 199 mCurrentScene = mEmptyScene; 200 201 // Enter transitions 202 TransitionSet enter = 203 new TransitionSet() 204 .addTransition(new SceneTransition(SceneTransition.ENTER)) 205 .addTransition(new Fade(Fade.IN)); 206 setTransition(mEmptyScene, mChannelBannerScene, enter); 207 setTransition(mEmptyScene, mInputBannerScene, enter); 208 setTransition(mEmptyScene, mKeypadChannelSwitchScene, enter); 209 setTransition(mEmptyScene, mSelectInputScene, enter); 210 211 // Exit transitions 212 TransitionSet exit = 213 new TransitionSet() 214 .addTransition(new SceneTransition(SceneTransition.EXIT)) 215 .addTransition(new Fade(Fade.OUT)); 216 setTransition(mChannelBannerScene, mEmptyScene, exit); 217 setTransition(mInputBannerScene, mEmptyScene, exit); 218 setTransition(mKeypadChannelSwitchScene, mEmptyScene, exit); 219 setTransition(mSelectInputScene, mEmptyScene, exit); 220 221 // All other possible transitions between scenes 222 TransitionInflater ti = TransitionInflater.from(mMainActivity); 223 Transition transition = ti.inflateTransition(R.transition.transition_between_scenes); 224 setTransition(mChannelBannerScene, mKeypadChannelSwitchScene, transition); 225 setTransition(mChannelBannerScene, mSelectInputScene, transition); 226 setTransition(mInputBannerScene, mSelectInputScene, transition); 227 setTransition(mKeypadChannelSwitchScene, mChannelBannerScene, transition); 228 setTransition(mKeypadChannelSwitchScene, mSelectInputScene, transition); 229 setTransition(mSelectInputScene, mChannelBannerScene, transition); 230 setTransition(mSelectInputScene, mInputBannerScene, transition); 231 232 mInitialized = true; 233 } 234 235 /** Returns the type of the given scene. */ 236 @SceneType getSceneType(Scene scene)237 public int getSceneType(Scene scene) { 238 if (scene == mChannelBannerScene) { 239 return SCENE_TYPE_CHANNEL_BANNER; 240 } else if (scene == mInputBannerScene) { 241 return SCENE_TYPE_INPUT_BANNER; 242 } else if (scene == mKeypadChannelSwitchScene) { 243 return SCENE_TYPE_KEYPAD_CHANNEL_SWITCH; 244 } else if (scene == mSelectInputScene) { 245 return SCENE_TYPE_SELECT_INPUT; 246 } 247 return SCENE_TYPE_EMPTY; 248 } 249 setCurrentScene(Scene scene, ViewGroup sceneView)250 private void setCurrentScene(Scene scene, ViewGroup sceneView) { 251 if (mListener != null) { 252 mListener.onSceneChanged(getSceneType(mCurrentScene), getSceneType(scene)); 253 } 254 mCurrentScene = scene; 255 mCurrentSceneView = sceneView; 256 // TODO: Is this a still valid call? 257 mMainActivity.updateKeyInputFocus(); 258 } 259 260 public interface TransitionLayout { 261 // TODO: remove the parameter fromEmptyScene once a bug regarding transition alpha 262 // is fixed. The bug is that the transition alpha is not reset after the transition is 263 // canceled. onEnterAction(boolean fromEmptyScene)264 void onEnterAction(boolean fromEmptyScene); 265 onExitAction()266 void onExitAction(); 267 } 268 buildScene(ViewGroup sceneRoot, final TransitionLayout layout)269 private Scene buildScene(ViewGroup sceneRoot, final TransitionLayout layout) { 270 final Scene scene = new Scene(sceneRoot, (View) layout); 271 scene.setEnterAction( 272 () -> { 273 boolean wasEmptyScene = (mCurrentScene == mEmptyScene); 274 setCurrentScene(scene, (ViewGroup) layout); 275 layout.onEnterAction(wasEmptyScene); 276 }); 277 scene.setExitAction( 278 () -> { 279 removeAllViewsFromOverlay(); 280 layout.onExitAction(); 281 }); 282 return scene; 283 } 284 removeAllViewsFromOverlay()285 private void removeAllViewsFromOverlay() { 286 // Clean up all the animations which can be still running. 287 mSceneContainer.getOverlay().remove(mChannelBannerView); 288 mSceneContainer.getOverlay().remove(mInputBannerView); 289 mSceneContainer.getOverlay().remove(mKeypadChannelSwitchView); 290 mSceneContainer.getOverlay().remove(mSelectInputView); 291 } 292 293 private class SceneTransition extends Transition { 294 static final int ENTER = 0; 295 static final int EXIT = 1; 296 297 private final Animator mAnimator; 298 SceneTransition(int mode)299 SceneTransition(int mode) { 300 mAnimator = mode == ENTER ? mEnterAnimator : mExitAnimator; 301 } 302 303 @Override captureStartValues(TransitionValues transitionValues)304 public void captureStartValues(TransitionValues transitionValues) {} 305 306 @Override captureEndValues(TransitionValues transitionValues)307 public void captureEndValues(TransitionValues transitionValues) {} 308 309 @Override createAnimator( ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)310 public Animator createAnimator( 311 ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { 312 Animator animator = mAnimator.clone(); 313 animator.setTarget(sceneRoot); 314 animator.addListener(new HardwareLayerAnimatorListenerAdapter(sceneRoot)); 315 return animator; 316 } 317 } 318 319 /** An interface for notification of the scene transition. */ 320 public interface Listener { 321 /** 322 * Called when the scene changes. This method is called just before the scene transition. 323 */ onSceneChanged(@ceneType int fromSceneType, @SceneType int toSceneType)324 void onSceneChanged(@SceneType int fromSceneType, @SceneType int toSceneType); 325 } 326 } 327