• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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