1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package android.support.v17.leanback.app; 15 16 import android.annotation.SuppressLint; 17 import android.os.Bundle; 18 import android.support.v17.leanback.transition.TransitionHelper; 19 import android.support.v17.leanback.transition.TransitionListener; 20 import android.support.v17.leanback.util.StateMachine; 21 import android.support.v17.leanback.util.StateMachine.Condition; 22 import android.support.v17.leanback.util.StateMachine.Event; 23 import android.support.v17.leanback.util.StateMachine.State; 24 import android.view.View; 25 import android.view.ViewTreeObserver; 26 27 /** 28 * Base class for leanback Fragments. This class is not intended to be subclassed by apps. 29 */ 30 @SuppressWarnings("FragmentNotInstantiable") 31 public class BaseSupportFragment extends BrandedSupportFragment { 32 33 /** 34 * The start state for all 35 */ 36 final State STATE_START = new State("START", true, false); 37 38 /** 39 * Initial State for ENTRNACE transition. 40 */ 41 final State STATE_ENTRANCE_INIT = new State("ENTRANCE_INIT"); 42 43 /** 44 * prepareEntranceTransition is just called, but view not ready yet. We can enable the 45 * busy spinner. 46 */ 47 final State STATE_ENTRANCE_ON_PREPARED = new State("ENTRANCE_ON_PREPARED", true, false) { 48 @Override 49 public void run() { 50 mProgressBarManager.show(); 51 } 52 }; 53 54 /** 55 * prepareEntranceTransition is called and main content view to slide in was created, so we can 56 * call {@link #onEntranceTransitionPrepare}. Note that we dont set initial content to invisible 57 * in this State, the process is very different in subclass, e.g. BrowseSupportFragment hide header 58 * views and hide main fragment view in two steps. 59 */ 60 final State STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW = new State( 61 "ENTRANCE_ON_PREPARED_ON_CREATEVIEW") { 62 @Override 63 public void run() { 64 onEntranceTransitionPrepare(); 65 } 66 }; 67 68 /** 69 * execute the entrance transition. 70 */ 71 final State STATE_ENTRANCE_PERFORM = new State("STATE_ENTRANCE_PERFORM") { 72 @Override 73 public void run() { 74 mProgressBarManager.hide(); 75 onExecuteEntranceTransition(); 76 } 77 }; 78 79 /** 80 * execute onEntranceTransitionEnd. 81 */ 82 final State STATE_ENTRANCE_ON_ENDED = new State("ENTRANCE_ON_ENDED") { 83 @Override 84 public void run() { 85 onEntranceTransitionEnd(); 86 } 87 }; 88 89 /** 90 * either entrance transition completed or skipped 91 */ 92 final State STATE_ENTRANCE_COMPLETE = new State("ENTRANCE_COMPLETE", true, false); 93 94 /** 95 * Event fragment.onCreate() 96 */ 97 final Event EVT_ON_CREATE = new Event("onCreate"); 98 99 /** 100 * Event fragment.onViewCreated() 101 */ 102 final Event EVT_ON_CREATEVIEW = new Event("onCreateView"); 103 104 /** 105 * Event for {@link #prepareEntranceTransition()} is called. 106 */ 107 final Event EVT_PREPARE_ENTRANCE = new Event("prepareEntranceTransition"); 108 109 /** 110 * Event for {@link #startEntranceTransition()} is called. 111 */ 112 final Event EVT_START_ENTRANCE = new Event("startEntranceTransition"); 113 114 /** 115 * Event for entrance transition is ended through Transition listener. 116 */ 117 final Event EVT_ENTRANCE_END = new Event("onEntranceTransitionEnd"); 118 119 /** 120 * Event for skipping entrance transition if not supported. 121 */ 122 final Condition COND_TRANSITION_NOT_SUPPORTED = new Condition("EntranceTransitionNotSupport") { 123 @Override 124 public boolean canProceed() { 125 return !TransitionHelper.systemSupportsEntranceTransitions(); 126 } 127 }; 128 129 final StateMachine mStateMachine = new StateMachine(); 130 131 Object mEntranceTransition; 132 final ProgressBarManager mProgressBarManager = new ProgressBarManager(); 133 134 @SuppressLint("ValidFragment") BaseSupportFragment()135 BaseSupportFragment() { 136 } 137 138 @Override onCreate(Bundle savedInstanceState)139 public void onCreate(Bundle savedInstanceState) { 140 createStateMachineStates(); 141 createStateMachineTransitions(); 142 mStateMachine.start(); 143 super.onCreate(savedInstanceState); 144 mStateMachine.fireEvent(EVT_ON_CREATE); 145 } 146 createStateMachineStates()147 void createStateMachineStates() { 148 mStateMachine.addState(STATE_START); 149 mStateMachine.addState(STATE_ENTRANCE_INIT); 150 mStateMachine.addState(STATE_ENTRANCE_ON_PREPARED); 151 mStateMachine.addState(STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW); 152 mStateMachine.addState(STATE_ENTRANCE_PERFORM); 153 mStateMachine.addState(STATE_ENTRANCE_ON_ENDED); 154 mStateMachine.addState(STATE_ENTRANCE_COMPLETE); 155 } 156 createStateMachineTransitions()157 void createStateMachineTransitions() { 158 mStateMachine.addTransition(STATE_START, STATE_ENTRANCE_INIT, EVT_ON_CREATE); 159 mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_ENTRANCE_COMPLETE, 160 COND_TRANSITION_NOT_SUPPORTED); 161 mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_ENTRANCE_COMPLETE, 162 EVT_ON_CREATEVIEW); 163 mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_ENTRANCE_ON_PREPARED, 164 EVT_PREPARE_ENTRANCE); 165 mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED, 166 STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW, 167 EVT_ON_CREATEVIEW); 168 mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED, 169 STATE_ENTRANCE_PERFORM, 170 EVT_START_ENTRANCE); 171 mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW, 172 STATE_ENTRANCE_PERFORM); 173 mStateMachine.addTransition(STATE_ENTRANCE_PERFORM, 174 STATE_ENTRANCE_ON_ENDED, 175 EVT_ENTRANCE_END); 176 mStateMachine.addTransition(STATE_ENTRANCE_ON_ENDED, STATE_ENTRANCE_COMPLETE); 177 } 178 179 @Override onViewCreated(View view, Bundle savedInstanceState)180 public void onViewCreated(View view, Bundle savedInstanceState) { 181 super.onViewCreated(view, savedInstanceState); 182 mStateMachine.fireEvent(EVT_ON_CREATEVIEW); 183 } 184 185 /** 186 * Enables entrance transition.<p> 187 * Entrance transition is the standard slide-in transition that shows rows of data in 188 * browse screen and details screen. 189 * <p> 190 * The method is ignored before LOLLIPOP (API21). 191 * <p> 192 * This method must be called in or 193 * before onCreate(). Typically entrance transition should be enabled when savedInstance is 194 * null so that fragment restored from instanceState does not run an extra entrance transition. 195 * When the entrance transition is enabled, the fragment will make headers and content 196 * hidden initially. 197 * When data of rows are ready, app must call {@link #startEntranceTransition()} to kick off 198 * the transition, otherwise the rows will be invisible forever. 199 * <p> 200 * It is similar to android:windowsEnterTransition and can be considered a late-executed 201 * android:windowsEnterTransition controlled by app. There are two reasons that app needs it: 202 * <li> Workaround the problem that activity transition is not available between launcher and 203 * app. Browse activity must programmatically start the slide-in transition.</li> 204 * <li> Separates DetailsOverviewRow transition from other rows transition. So that 205 * the DetailsOverviewRow transition can be executed earlier without waiting for all rows 206 * to be loaded.</li> 207 * <p> 208 * Transition object is returned by createEntranceTransition(). Typically the app does not need 209 * override the default transition that browse and details provides. 210 */ prepareEntranceTransition()211 public void prepareEntranceTransition() { 212 mStateMachine.fireEvent(EVT_PREPARE_ENTRANCE); 213 } 214 215 /** 216 * Create entrance transition. Subclass can override to load transition from 217 * resource or construct manually. Typically app does not need to 218 * override the default transition that browse and details provides. 219 */ createEntranceTransition()220 protected Object createEntranceTransition() { 221 return null; 222 } 223 224 /** 225 * Run entrance transition. Subclass may use TransitionManager to perform 226 * go(Scene) or beginDelayedTransition(). App should not override the default 227 * implementation of browse and details fragment. 228 */ runEntranceTransition(Object entranceTransition)229 protected void runEntranceTransition(Object entranceTransition) { 230 } 231 232 /** 233 * Callback when entrance transition is prepared. This is when fragment should 234 * stop user input and animations. 235 */ onEntranceTransitionPrepare()236 protected void onEntranceTransitionPrepare() { 237 } 238 239 /** 240 * Callback when entrance transition is started. This is when fragment should 241 * stop processing layout. 242 */ onEntranceTransitionStart()243 protected void onEntranceTransitionStart() { 244 } 245 246 /** 247 * Callback when entrance transition is ended. 248 */ onEntranceTransitionEnd()249 protected void onEntranceTransitionEnd() { 250 } 251 252 /** 253 * When fragment finishes loading data, it should call startEntranceTransition() 254 * to execute the entrance transition. 255 * startEntranceTransition() will start transition only if both two conditions 256 * are satisfied: 257 * <li> prepareEntranceTransition() was called.</li> 258 * <li> has not executed entrance transition yet.</li> 259 * <p> 260 * If startEntranceTransition() is called before onViewCreated(), it will be pending 261 * and executed when view is created. 262 */ startEntranceTransition()263 public void startEntranceTransition() { 264 mStateMachine.fireEvent(EVT_START_ENTRANCE); 265 } 266 onExecuteEntranceTransition()267 void onExecuteEntranceTransition() { 268 // wait till views get their initial position before start transition 269 final View view = getView(); 270 if (view == null) { 271 // fragment view destroyed, transition not needed 272 return; 273 } 274 view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 275 @Override 276 public boolean onPreDraw() { 277 view.getViewTreeObserver().removeOnPreDrawListener(this); 278 if (getContext() == null || getView() == null) { 279 // bail out if fragment is destroyed immediately after startEntranceTransition 280 return true; 281 } 282 internalCreateEntranceTransition(); 283 onEntranceTransitionStart(); 284 if (mEntranceTransition != null) { 285 runEntranceTransition(mEntranceTransition); 286 } else { 287 mStateMachine.fireEvent(EVT_ENTRANCE_END); 288 } 289 return false; 290 } 291 }); 292 view.invalidate(); 293 } 294 internalCreateEntranceTransition()295 void internalCreateEntranceTransition() { 296 mEntranceTransition = createEntranceTransition(); 297 if (mEntranceTransition == null) { 298 return; 299 } 300 TransitionHelper.addTransitionListener(mEntranceTransition, new TransitionListener() { 301 @Override 302 public void onTransitionEnd(Object transition) { 303 mEntranceTransition = null; 304 mStateMachine.fireEvent(EVT_ENTRANCE_END); 305 } 306 }); 307 } 308 309 /** 310 * Returns the {@link ProgressBarManager}. 311 * @return The {@link ProgressBarManager}. 312 */ getProgressBarManager()313 public final ProgressBarManager getProgressBarManager() { 314 return mProgressBarManager; 315 } 316 } 317