• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.tv.ui.sidepanel;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorInflater;
21 import android.animation.AnimatorListenerAdapter;
22 import android.app.Activity;
23 import android.app.FragmentManager;
24 import android.app.FragmentTransaction;
25 import android.os.Handler;
26 import android.view.View;
27 import android.view.ViewTreeObserver;
28 
29 import com.android.tv.R;
30 
31 public class SideFragmentManager {
32     private static final String FIRST_BACKSTACK_RECORD_NAME = "0";
33 
34     private final Activity mActivity;
35     private final FragmentManager mFragmentManager;
36     private final Runnable mPreShowRunnable;
37     private final Runnable mPostHideRunnable;
38     private ViewTreeObserver.OnGlobalLayoutListener mShowOnGlobalLayoutListener;
39 
40     // To get the count reliably while using popBackStack(),
41     // instead of using getBackStackEntryCount() with popBackStackImmediate().
42     private int mFragmentCount;
43 
44     private final View mPanel;
45     private final Animator mShowAnimator;
46     private final Animator mHideAnimator;
47 
48     private final Handler mHandler = new Handler();
49     private final Runnable mHideAllRunnable = new Runnable() {
50         @Override
51         public void run() {
52             hideAll(true);
53         }
54     };
55     private final long mShowDurationMillis;
56 
SideFragmentManager(Activity activity, Runnable preShowRunnable, Runnable postHideRunnable)57     public SideFragmentManager(Activity activity, Runnable preShowRunnable,
58             Runnable postHideRunnable) {
59         mActivity = activity;
60         mFragmentManager = mActivity.getFragmentManager();
61         mPreShowRunnable = preShowRunnable;
62         mPostHideRunnable = postHideRunnable;
63 
64         mPanel = mActivity.findViewById(R.id.side_panel);
65         mShowAnimator = AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_enter);
66         mShowAnimator.setTarget(mPanel);
67         mHideAnimator = AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_exit);
68         mHideAnimator.setTarget(mPanel);
69         mHideAnimator.addListener(new AnimatorListenerAdapter() {
70             @Override
71             public void onAnimationEnd(Animator animation) {
72                 // Animation is still in running state at this point.
73                 hideAllInternal();
74             }
75         });
76 
77         mShowDurationMillis = mActivity.getResources().getInteger(
78                 R.integer.side_panel_show_duration);
79     }
80 
getCount()81     public int getCount() {
82         return mFragmentCount;
83     }
84 
isActive()85     public boolean isActive() {
86         return mFragmentCount != 0 && !isHiding();
87     }
88 
isHiding()89     public boolean isHiding() {
90         return mHideAnimator.isStarted();
91     }
92 
93     /**
94      * Shows the given {@link SideFragment}.
95      */
show(SideFragment sideFragment)96     public void show(SideFragment sideFragment) {
97         show(sideFragment, true);
98     }
99 
100     /**
101      * Shows the given {@link SideFragment}.
102      */
show(SideFragment sideFragment, boolean showEnterAnimation)103     public void show(SideFragment sideFragment, boolean showEnterAnimation) {
104         if (isHiding()) {
105             mHideAnimator.end();
106         }
107         boolean isFirst = (mFragmentCount == 0);
108         FragmentTransaction ft = mFragmentManager.beginTransaction();
109         if (!isFirst) {
110             ft.setCustomAnimations(
111                     showEnterAnimation ? R.animator.side_panel_fragment_enter : 0,
112                     R.animator.side_panel_fragment_exit,
113                     R.animator.side_panel_fragment_pop_enter,
114                     R.animator.side_panel_fragment_pop_exit);
115         }
116         ft.replace(R.id.side_fragment_container, sideFragment)
117                 .addToBackStack(Integer.toString(mFragmentCount)).commit();
118         mFragmentCount++;
119 
120         if (isFirst) {
121             // We should wait for fragment transition and intital layouting finished to start the
122             // slide-in animation to prevent jankiness resulted by performing transition and
123             // layouting at the same time with animation.
124             mPanel.setVisibility(View.VISIBLE);
125             mShowOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
126                 @Override
127                 public void onGlobalLayout() {
128                     mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(this);
129                     mShowOnGlobalLayoutListener = null;
130                     if (mPreShowRunnable != null) {
131                         mPreShowRunnable.run();
132                     }
133                     mShowAnimator.start();
134                 }
135             };
136             mPanel.getViewTreeObserver().addOnGlobalLayoutListener(mShowOnGlobalLayoutListener);
137         }
138         scheduleHideAll();
139     }
140 
popSideFragment()141     public void popSideFragment() {
142         if (!isActive()) {
143             return;
144         } else if (mFragmentCount == 1) {
145             // Show closing animation with the last fragment.
146             hideAll(true);
147             return;
148         }
149         mFragmentManager.popBackStack();
150         mFragmentCount--;
151     }
152 
hideAll(boolean withAnimation)153     public void hideAll(boolean withAnimation) {
154         if (mShowAnimator.isStarted()) {
155             mShowAnimator.end();
156         }
157         if (mShowOnGlobalLayoutListener != null) {
158             // The show operation maybe requested but the show animator is not started yet, in this
159             // case, we show still run mPreShowRunnable.
160             mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(mShowOnGlobalLayoutListener);
161             mShowOnGlobalLayoutListener = null;
162             if (mPreShowRunnable != null) {
163                 mPreShowRunnable.run();
164             }
165         }
166         if (withAnimation) {
167             if (!isHiding()) {
168                 mHideAnimator.start();
169             }
170             return;
171         }
172         if (isHiding()) {
173             mHideAnimator.end();
174             return;
175         }
176         hideAllInternal();
177     }
178 
hideAllInternal()179     private void hideAllInternal() {
180         mHandler.removeCallbacksAndMessages(null);
181         if (mFragmentCount == 0) {
182             return;
183         }
184 
185         mPanel.setVisibility(View.GONE);
186         mFragmentManager.popBackStack(FIRST_BACKSTACK_RECORD_NAME,
187                 FragmentManager.POP_BACK_STACK_INCLUSIVE);
188         mFragmentCount = 0;
189 
190         if (mPostHideRunnable != null) {
191             mPostHideRunnable.run();
192         }
193     }
194 
195     /**
196      * Show the side panel with animation. If there are many entries in the fragment stack,
197      * the animation look like that there's only one fragment.
198      *
199      * @param withAnimation specifies if animation should be shown.
200      */
showSidePanel(boolean withAnimation)201     public void showSidePanel(boolean withAnimation) {
202         if (mFragmentCount == 0) {
203             return;
204         }
205 
206         mPanel.setVisibility(View.VISIBLE);
207         if (withAnimation) {
208             mShowAnimator.start();
209         }
210         scheduleHideAll();
211     }
212 
213     /**
214      * Hide the side panel. This method just hide the panel and preserves the back
215      * stack. If you want to empty the back stack, call {@link #hideAll}.
216      */
hideSidePanel(boolean withAnimation)217     public void hideSidePanel(boolean withAnimation) {
218         mHandler.removeCallbacks(mHideAllRunnable);
219         if (withAnimation) {
220             Animator hideAnimator =
221                     AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_exit);
222             hideAnimator.setTarget(mPanel);
223             hideAnimator.start();
224             hideAnimator.addListener(new AnimatorListenerAdapter() {
225                 @Override
226                 public void onAnimationEnd(Animator animation) {
227                     mPanel.setVisibility(View.GONE);
228                 }
229             });
230         } else {
231             mPanel.setVisibility(View.GONE);
232         }
233     }
234 
isSidePanelVisible()235     public boolean isSidePanelVisible() {
236         return mPanel.getVisibility() == View.VISIBLE;
237     }
238 
239     /**
240      * Resets the timer for hiding side fragment.
241      */
scheduleHideAll()242     public void scheduleHideAll() {
243         mHandler.removeCallbacks(mHideAllRunnable);
244         mHandler.postDelayed(mHideAllRunnable, mShowDurationMillis);
245     }
246 
247     /**
248      * Should {@code keyCode} hide the current panel.
249      */
isHideKeyForCurrentPanel(int keyCode)250     public boolean isHideKeyForCurrentPanel(int keyCode) {
251         if (isActive()) {
252             SideFragment current = (SideFragment) mFragmentManager.findFragmentById(
253                     R.id.side_fragment_container);
254             return current != null && current.isHideKeyForThisPanel(keyCode);
255         }
256         return false;
257     }
258 }
259