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 */ 14package android.support.v17.leanback.app; 15 16import android.os.Bundle; 17import android.support.v17.leanback.R; 18import android.support.v17.leanback.transition.TransitionHelper; 19import android.support.v17.leanback.transition.TransitionListener; 20import android.view.View; 21import android.view.ViewTreeObserver; 22 23import android.support.v17.leanback.util.StateMachine; 24import android.support.v17.leanback.util.StateMachine.State; 25 26import static android.support.v17.leanback.util.StateMachine.*; 27 28/** 29 * @hide 30 */ 31class BaseFragment extends BrandedFragment { 32 33 /** 34 * Condition: {@link TransitionHelper#systemSupportsEntranceTransitions()} is true 35 * Action: none 36 */ 37 private final State STATE_ALLOWED = new State() { 38 @Override 39 public boolean canRun() { 40 return TransitionHelper.systemSupportsEntranceTransitions(); 41 } 42 43 @Override 44 public void run() { 45 mProgressBarManager.show(); 46 } 47 }; 48 49 /** 50 * Condition: {@link #isReadyForPrepareEntranceTransition()} is true 51 * Action: {@link #onEntranceTransitionPrepare()} } 52 */ 53 private final State STATE_PREPARE = new State() { 54 @Override 55 public boolean canRun() { 56 return isReadyForPrepareEntranceTransition(); 57 } 58 59 @Override 60 public void run() { 61 onEntranceTransitionPrepare(); 62 } 63 }; 64 65 /** 66 * Condition: {@link #isReadyForStartEntranceTransition()} is true 67 * Action: {@link #onExecuteEntranceTransition()} } 68 */ 69 private final State STATE_START = new State() { 70 @Override 71 public boolean canRun() { 72 return isReadyForStartEntranceTransition(); 73 } 74 75 @Override 76 public void run() { 77 mProgressBarManager.hide(); 78 onExecuteEntranceTransition(); 79 } 80 }; 81 82 final StateMachine mEnterTransitionStates; 83 84 private Object mEntranceTransition; 85 private final ProgressBarManager mProgressBarManager = new ProgressBarManager(); 86 87 BaseFragment() { 88 mEnterTransitionStates = new StateMachine(); 89 mEnterTransitionStates.addState(STATE_ALLOWED); 90 mEnterTransitionStates.addState(STATE_PREPARE); 91 mEnterTransitionStates.addState(STATE_START); 92 } 93 94 @Override 95 public void onViewCreated(View view, Bundle savedInstanceState) { 96 super.onViewCreated(view, savedInstanceState); 97 performPendingStates(); 98 } 99 100 final void performPendingStates() { 101 mEnterTransitionStates.runPendingStates(); 102 } 103 104 /** 105 * Enables entrance transition.<p> 106 * Entrance transition is the standard slide-in transition that shows rows of data in 107 * browse screen and details screen. 108 * <p> 109 * The method is ignored before LOLLIPOP (API21). 110 * <p> 111 * This method must be called in or 112 * before onCreate(). Typically entrance transition should be enabled when savedInstance is 113 * null so that fragment restored from instanceState does not run an extra entrance transition. 114 * When the entrance transition is enabled, the fragment will make headers and content 115 * hidden initially. 116 * When data of rows are ready, app must call {@link #startEntranceTransition()} to kick off 117 * the transition, otherwise the rows will be invisible forever. 118 * <p> 119 * It is similar to android:windowsEnterTransition and can be considered a late-executed 120 * android:windowsEnterTransition controlled by app. There are two reasons that app needs it: 121 * <li> Workaround the problem that activity transition is not available between launcher and 122 * app. Browse activity must programmatically start the slide-in transition.</li> 123 * <li> Separates DetailsOverviewRow transition from other rows transition. So that 124 * the DetailsOverviewRow transition can be executed earlier without waiting for all rows 125 * to be loaded.</li> 126 * <p> 127 * Transition object is returned by createEntranceTransition(). Typically the app does not need 128 * override the default transition that browse and details provides. 129 */ 130 public void prepareEntranceTransition() { 131 mEnterTransitionStates.runState(STATE_ALLOWED); 132 mEnterTransitionStates.runState(STATE_PREPARE); 133 } 134 135 /** 136 * Return true if entrance transition is enabled and not started yet. 137 * Entrance transition can only be executed once and isEntranceTransitionEnabled() 138 * is reset to false after entrance transition is started. 139 */ 140 boolean isEntranceTransitionEnabled() { 141 // Enabled when passed STATE_ALLOWED in prepareEntranceTransition call. 142 return STATE_ALLOWED.getStatus() == STATUS_EXECUTED; 143 } 144 145 /** 146 * Create entrance transition. Subclass can override to load transition from 147 * resource or construct manually. Typically app does not need to 148 * override the default transition that browse and details provides. 149 */ 150 protected Object createEntranceTransition() { 151 return null; 152 } 153 154 /** 155 * Run entrance transition. Subclass may use TransitionManager to perform 156 * go(Scene) or beginDelayedTransition(). App should not override the default 157 * implementation of browse and details fragment. 158 */ 159 protected void runEntranceTransition(Object entranceTransition) { 160 } 161 162 /** 163 * Callback when entrance transition is prepared. This is when fragment should 164 * stop user input and animations. 165 */ 166 protected void onEntranceTransitionPrepare() { 167 } 168 169 /** 170 * Callback when entrance transition is started. This is when fragment should 171 * stop processing layout. 172 */ 173 protected void onEntranceTransitionStart() { 174 } 175 176 /** 177 * Callback when entrance transition is ended. 178 */ 179 protected void onEntranceTransitionEnd() { 180 } 181 182 /** 183 * Returns true if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise. 184 * Subclass may override and add additional conditions. 185 * @return True if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise. 186 * Subclass may override and add additional conditions. 187 */ 188 boolean isReadyForPrepareEntranceTransition() { 189 return getView() != null; 190 } 191 192 /** 193 * Returns true if it is ready to perform {@link #startEntranceTransition()}, false otherwise. 194 * Subclass may override and add additional conditions. 195 * @return True if it is ready to perform {@link #startEntranceTransition()}, false otherwise. 196 * Subclass may override and add additional conditions. 197 */ 198 boolean isReadyForStartEntranceTransition() { 199 return getView() != null; 200 } 201 202 /** 203 * When fragment finishes loading data, it should call startEntranceTransition() 204 * to execute the entrance transition. 205 * startEntranceTransition() will start transition only if both two conditions 206 * are satisfied: 207 * <li> prepareEntranceTransition() was called.</li> 208 * <li> has not executed entrance transition yet.</li> 209 * <p> 210 * If startEntranceTransition() is called before onViewCreated(), it will be pending 211 * and executed when view is created. 212 */ 213 public void startEntranceTransition() { 214 mEnterTransitionStates.runState(STATE_START); 215 } 216 217 void onExecuteEntranceTransition() { 218 // wait till views get their initial position before start transition 219 final View view = getView(); 220 view.getViewTreeObserver().addOnPreDrawListener( 221 new ViewTreeObserver.OnPreDrawListener() { 222 @Override 223 public boolean onPreDraw() { 224 view.getViewTreeObserver().removeOnPreDrawListener(this); 225 internalCreateEntranceTransition(); 226 if (mEntranceTransition != null) { 227 onEntranceTransitionStart(); 228 runEntranceTransition(mEntranceTransition); 229 } 230 return false; 231 } 232 }); 233 view.invalidate(); 234 } 235 236 void internalCreateEntranceTransition() { 237 mEntranceTransition = createEntranceTransition(); 238 if (mEntranceTransition == null) { 239 return; 240 } 241 TransitionHelper.addTransitionListener(mEntranceTransition, new TransitionListener() { 242 @Override 243 public void onTransitionEnd(Object transition) { 244 mEntranceTransition = null; 245 onEntranceTransitionEnd(); 246 mEnterTransitionStates.resetStatus(); 247 } 248 }); 249 } 250 251 /** 252 * Returns the {@link ProgressBarManager}. 253 */ 254 public final ProgressBarManager getProgressBarManager() { 255 return mProgressBarManager; 256 } 257} 258