Home | History | Annotate | Download | only in app
      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")
    138     BaseSupportFragment() {
    139     }
    140 
    141     @Override
    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 
    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 
    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
    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      */
    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      */
    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      */
    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      */
    239     protected void onEntranceTransitionPrepare() {
    240     }
    241 
    242     /**
    243      * Callback when entrance transition is started.  This is when fragment should
    244      * stop processing layout.
    245      */
    246     protected void onEntranceTransitionStart() {
    247     }
    248 
    249     /**
    250      * Callback when entrance transition is ended.
    251      */
    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      */
    266     public void startEntranceTransition() {
    267         mStateMachine.fireEvent(EVT_START_ENTRANCE);
    268     }
    269 
    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 
    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      */
    316     public final ProgressBarManager getProgressBarManager() {
    317         return mProgressBarManager;
    318     }
    319 }
    320