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