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.app.Fragment;
17 import android.os.Bundle;
18 import android.support.v17.leanback.R;
19 import android.support.v17.leanback.transition.TransitionHelper;
20 import android.support.v17.leanback.transition.TransitionListener;
21 import android.view.View;
22 import android.view.ViewTreeObserver;
23 
24 /**
25  * @hide
26  */
27 class BaseFragment extends Fragment {
28 
29     private boolean mEntranceTransitionEnabled = false;
30     private boolean mStartEntranceTransitionPending = false;
31     private Object mEntranceTransition;
32 
33     static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
34 
35     @Override
onViewCreated(View view, Bundle savedInstanceState)36     public void onViewCreated(View view, Bundle savedInstanceState) {
37         super.onViewCreated(view, savedInstanceState);
38         if (mStartEntranceTransitionPending) {
39             mStartEntranceTransitionPending = false;
40             startEntranceTransition();
41         }
42     }
43 
44     /**
45      * Enables entrance transition.<p>
46      * Entrance transition is the standard slide-in transition that shows rows of data in
47      * browse screen and details screen.
48      * <p>
49      * The method is ignored before LOLLIPOP (API21).
50      * <p>
51      * This method must be called in or
52      * before onCreate().  Typically entrance transition should be enabled when savedInstance is
53      * null so that fragment restored from instanceState does not run an extra entrance transition.
54      * When the entrance transition is enabled, the fragment will make headers and content
55      * hidden initially.
56      * When data of rows are ready, app must call {@link #startEntranceTransition()} to kick off
57      * the transition, otherwise the rows will be invisible forever.
58      * <p>
59      * It is similar to android:windowsEnterTransition and can be considered a late-executed
60      * android:windowsEnterTransition controlled by app.  There are two reasons that app needs it:
61      * <li> Workaround the problem that activity transition is not available between launcher and
62      * app.  Browse activity must programmatically start the slide-in transition.</li>
63      * <li> Separates DetailsOverviewRow transition from other rows transition.  So that
64      * the DetailsOverviewRow transition can be executed earlier without waiting for all rows
65      * to be loaded.</li>
66      * <p>
67      * Transition object is returned by createEntranceTransition().  Typically the app does not need
68      * override the default transition that browse and details provides.
69      */
prepareEntranceTransition()70     public void prepareEntranceTransition() {
71         if (TransitionHelper.systemSupportsEntranceTransitions()) {
72             mEntranceTransitionEnabled = true;
73         }
74     }
75 
76     /**
77      * Return true if entrance transition is enabled and not started yet.
78      * Entrance transition can only be executed once and isEntranceTransitionEnabled()
79      * is reset to false after entrance transition is started.
80      */
isEntranceTransitionEnabled()81     boolean isEntranceTransitionEnabled() {
82         return mEntranceTransitionEnabled;
83     }
84 
85     /**
86      * Create entrance transition.  Subclass can override to load transition from
87      * resource or construct manually.  Typically app does not need to
88      * override the default transition that browse and details provides.
89      */
createEntranceTransition()90     protected Object createEntranceTransition() {
91         return null;
92     }
93 
94     /**
95      * Run entrance transition.  Subclass may use TransitionManager to perform
96      * go(Scene) or beginDelayedTransition().  App should not override the default
97      * implementation of browse and details fragment.
98      */
runEntranceTransition(Object entranceTransition)99     protected void runEntranceTransition(Object entranceTransition) {
100     }
101 
102     /**
103      * Callback when entrance transition is started.
104      */
onEntranceTransitionStart()105     protected void onEntranceTransitionStart() {
106     }
107 
108     /**
109      * Callback when entrance transition is ended.
110      */
onEntranceTransitionEnd()111     protected void onEntranceTransitionEnd() {
112     }
113 
114     /**
115      * When fragment finishes loading data, it should call startEntranceTransition()
116      * to execute the entrance transition.
117      * startEntranceTransition() will start transition only if both two conditions
118      * are satisfied:
119      * <li> prepareEntranceTransition() was called.</li>
120      * <li> has not executed entrance transition yet.</li>
121      * <p>
122      * If startEntranceTransition() is called before onViewCreated(), it will be pending
123      * and executed when view is created.
124      */
startEntranceTransition()125     public void startEntranceTransition() {
126         if (!mEntranceTransitionEnabled || mEntranceTransition != null) {
127             return;
128         }
129         // if view is not created yet, delay until onViewCreated()
130         if (getView() == null) {
131             mStartEntranceTransitionPending = true;
132             return;
133         }
134         // wait till views get their initial position before start transition
135         final View view = getView();
136         view.getViewTreeObserver().addOnPreDrawListener(
137                 new ViewTreeObserver.OnPreDrawListener() {
138             @Override
139             public boolean onPreDraw() {
140                 view.getViewTreeObserver().removeOnPreDrawListener(this);
141                 internalCreateEntranceTransition();
142                 mEntranceTransitionEnabled = false;
143                 runEntranceTransition(mEntranceTransition);
144                 return false;
145             }
146         });
147         view.invalidate();
148     }
149 
internalCreateEntranceTransition()150     void internalCreateEntranceTransition() {
151         mEntranceTransition = createEntranceTransition();
152         if (mEntranceTransition == null) {
153             return;
154         }
155         sTransitionHelper.setTransitionListener(mEntranceTransition, new TransitionListener() {
156             @Override
157             public void onTransitionStart(Object transition) {
158                 onEntranceTransitionStart();
159             }
160             @Override
161             public void onTransitionEnd(Object transition) {
162                 mEntranceTransition = null;
163                 onEntranceTransitionEnd();
164             }
165         });
166     }
167 }
168