SideFragmentManager.java revision 816a4be1a0f34f6a48877c8afd3dbbca19eac435
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.tv.ui.sidepanel;
18
19import android.animation.Animator;
20import android.animation.AnimatorInflater;
21import android.animation.AnimatorListenerAdapter;
22import android.app.Activity;
23import android.app.FragmentManager;
24import android.app.FragmentTransaction;
25import android.view.View;
26
27import com.android.tv.R;
28
29public class SideFragmentManager {
30    private static final String FIRST_BACKSTACK_RECORD_NAME = "0";
31
32    private final Activity mActivity;
33    private final FragmentManager mFragmentManager;
34    private final Runnable mPreShowRunnable;
35    private final Runnable mPostHideRunnable;
36
37    // To get the count reliably while using popBackStack(),
38    // instead of using getBackStackEntryCount() with popBackStackImmediate().
39    private int mFragmentCount;
40
41    private final View mPanel;
42    private final Animator mShowAnimator;
43    private final Animator mHideAnimator;
44
45    private final Runnable mHideAllRunnable = new Runnable() {
46        @Override
47        public void run() {
48            hideAll(true);
49        }
50    };
51    private final long mShowDurationMillis;
52
53    public SideFragmentManager(Activity activity, Runnable preShowRunnable,
54            Runnable postHideRunnable) {
55        mActivity = activity;
56        mFragmentManager = mActivity.getFragmentManager();
57        mPreShowRunnable = preShowRunnable;
58        mPostHideRunnable = postHideRunnable;
59
60        mPanel = mActivity.findViewById(R.id.side_panel);
61        mShowAnimator = AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_enter);
62        mShowAnimator.setTarget(mPanel);
63        mHideAnimator = AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_exit);
64        mHideAnimator.setTarget(mPanel);
65        mHideAnimator.addListener(new AnimatorListenerAdapter() {
66            @Override
67            public void onAnimationEnd(Animator animation) {
68                // Animation is still in running state at this point.
69                hideAllInternal();
70            }
71        });
72
73        mShowDurationMillis = mActivity.getResources().getInteger(
74                R.integer.side_panel_show_duration);
75    }
76
77    public int getCount() {
78        return mFragmentCount;
79    }
80
81    public boolean isActive() {
82        return mFragmentCount != 0 && !isHiding();
83    }
84
85    public boolean isHiding() {
86        return mHideAnimator.isStarted();
87    }
88
89    public void show(SideFragment sideFragment) {
90        SideFragment.preloadRecycledViews(mActivity);
91        if (isHiding()) {
92            mHideAnimator.end();
93        }
94        boolean isFirst = (mFragmentCount == 0);
95        if (isFirst) {
96            if (mPreShowRunnable != null) {
97                mPreShowRunnable.run();
98            }
99        }
100
101        FragmentTransaction ft = mFragmentManager.beginTransaction();
102        if (!isFirst) {
103            ft.setCustomAnimations(
104                    R.animator.side_panel_fragment_enter,
105                    R.animator.side_panel_fragment_exit,
106                    R.animator.side_panel_fragment_pop_enter,
107                    R.animator.side_panel_fragment_pop_exit);
108        }
109        ft.replace(R.id.side_fragment_container, sideFragment)
110                .addToBackStack(Integer.toString(mFragmentCount)).commit();
111        mFragmentCount++;
112
113        if (isFirst) {
114            mPanel.setVisibility(View.VISIBLE);
115            mShowAnimator.start();
116        }
117        scheduleHideAll();
118    }
119
120    public void popSideFragment() {
121        if (!isActive()) {
122            return;
123        } else if (mFragmentCount == 1) {
124            // Show closing animation with the last fragment.
125            hideAll(true);
126            return;
127        }
128        mFragmentManager.popBackStack();
129        mFragmentCount--;
130    }
131
132    public void hideAll(boolean withAnimation) {
133        if (withAnimation) {
134            if (!isHiding()) {
135                mHideAnimator.start();
136            }
137            return;
138        }
139        if (isHiding()) {
140            mHideAnimator.end();
141            return;
142        }
143        hideAllInternal();
144    }
145
146    private void hideAllInternal() {
147        if (mFragmentCount == 0) {
148            return;
149        }
150
151        mPanel.setVisibility(View.GONE);
152        mFragmentManager.popBackStack(FIRST_BACKSTACK_RECORD_NAME,
153                FragmentManager.POP_BACK_STACK_INCLUSIVE);
154        mFragmentCount = 0;
155
156        if (mPostHideRunnable != null) {
157            mPostHideRunnable.run();
158        }
159    }
160
161    /**
162     * Show the side panel with animation. If there are many entries in the fragment stack,
163     * the animation look like that there's only one fragment.
164     *
165     * @param withAnimation specifies if animation should be shown.
166     */
167    public void showSidePanel(boolean withAnimation) {
168        SideFragment.preloadRecycledViews(mActivity);
169        if (mFragmentCount == 0) {
170            return;
171        }
172
173        mPanel.setVisibility(View.VISIBLE);
174        if (withAnimation) {
175            mShowAnimator.start();
176        }
177        scheduleHideAll();
178    }
179
180    /**
181     * Hide the side panel. This method just hide the panel and preserves the back
182     * stack. If you want to empty the back stack, call {@link #hideAll}.
183     */
184    public void hideSidePanel(boolean withAnimation) {
185        if (withAnimation) {
186            mPanel.removeCallbacks(mHideAllRunnable);
187            Animator hideAnimator =
188                    AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_exit);
189            hideAnimator.setTarget(mPanel);
190            hideAnimator.start();
191            hideAnimator.addListener(new AnimatorListenerAdapter() {
192                @Override
193                public void onAnimationEnd(Animator animation) {
194                    mPanel.setVisibility(View.GONE);
195                }
196            });
197        } else {
198            mPanel.setVisibility(View.GONE);
199        }
200    }
201
202    public boolean isSidePanelVisible() {
203        return mPanel.getVisibility() == View.VISIBLE;
204    }
205
206    public void scheduleHideAll() {
207        mPanel.removeCallbacks(mHideAllRunnable);
208        mPanel.postDelayed(mHideAllRunnable, mShowDurationMillis);
209    }
210
211    /**
212     * Should {@code keyCode} hide the current panel.
213     */
214    public boolean isHideKeyForCurrentPanel(int keyCode) {
215        if (isActive()) {
216            SideFragment current = (SideFragment) mFragmentManager.findFragmentById(
217                    R.id.side_fragment_container);
218            return current != null && current.isHideKeyForThisPanel(keyCode);
219        }
220        return false;
221    }
222}
223