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