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