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.view.View; 26 27 import com.android.tv.R; 28 29 public 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 SideFragmentManager(Activity activity, Runnable preShowRunnable, Runnable postHideRunnable)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 getCount()77 public int getCount() { 78 return mFragmentCount; 79 } 80 isActive()81 public boolean isActive() { 82 return mFragmentCount != 0 && !isHiding(); 83 } 84 isHiding()85 public boolean isHiding() { 86 return mHideAnimator.isStarted(); 87 } 88 89 /** 90 * Shows the given {@link SideFragment}. 91 */ show(SideFragment sideFragment)92 public void show(SideFragment sideFragment) { 93 show(sideFragment, true); 94 } 95 96 /** 97 * Shows the given {@link SideFragment}. 98 */ show(SideFragment sideFragment, boolean showEnterAnimation)99 public void show(SideFragment sideFragment, boolean showEnterAnimation) { 100 SideFragment.preloadRecycledViews(mActivity); 101 if (isHiding()) { 102 mHideAnimator.end(); 103 } 104 boolean isFirst = (mFragmentCount == 0); 105 if (isFirst) { 106 if (mPreShowRunnable != null) { 107 mPreShowRunnable.run(); 108 } 109 } 110 111 FragmentTransaction ft = mFragmentManager.beginTransaction(); 112 if (!isFirst) { 113 ft.setCustomAnimations( 114 showEnterAnimation ? R.animator.side_panel_fragment_enter : 0, 115 R.animator.side_panel_fragment_exit, 116 R.animator.side_panel_fragment_pop_enter, 117 R.animator.side_panel_fragment_pop_exit); 118 } 119 ft.replace(R.id.side_fragment_container, sideFragment) 120 .addToBackStack(Integer.toString(mFragmentCount)).commit(); 121 mFragmentCount++; 122 123 if (isFirst) { 124 mPanel.setVisibility(View.VISIBLE); 125 mShowAnimator.start(); 126 } 127 scheduleHideAll(); 128 } 129 popSideFragment()130 public void popSideFragment() { 131 if (!isActive()) { 132 return; 133 } else if (mFragmentCount == 1) { 134 // Show closing animation with the last fragment. 135 hideAll(true); 136 return; 137 } 138 mFragmentManager.popBackStack(); 139 mFragmentCount--; 140 } 141 hideAll(boolean withAnimation)142 public void hideAll(boolean withAnimation) { 143 if (withAnimation) { 144 if (!isHiding()) { 145 mHideAnimator.start(); 146 } 147 return; 148 } 149 if (isHiding()) { 150 mHideAnimator.end(); 151 return; 152 } 153 hideAllInternal(); 154 } 155 hideAllInternal()156 private void hideAllInternal() { 157 if (mFragmentCount == 0) { 158 return; 159 } 160 161 mPanel.setVisibility(View.GONE); 162 mFragmentManager.popBackStack(FIRST_BACKSTACK_RECORD_NAME, 163 FragmentManager.POP_BACK_STACK_INCLUSIVE); 164 mFragmentCount = 0; 165 166 if (mPostHideRunnable != null) { 167 mPostHideRunnable.run(); 168 } 169 } 170 171 /** 172 * Show the side panel with animation. If there are many entries in the fragment stack, 173 * the animation look like that there's only one fragment. 174 * 175 * @param withAnimation specifies if animation should be shown. 176 */ showSidePanel(boolean withAnimation)177 public void showSidePanel(boolean withAnimation) { 178 SideFragment.preloadRecycledViews(mActivity); 179 if (mFragmentCount == 0) { 180 return; 181 } 182 183 mPanel.setVisibility(View.VISIBLE); 184 if (withAnimation) { 185 mShowAnimator.start(); 186 } 187 scheduleHideAll(); 188 } 189 190 /** 191 * Hide the side panel. This method just hide the panel and preserves the back 192 * stack. If you want to empty the back stack, call {@link #hideAll}. 193 */ hideSidePanel(boolean withAnimation)194 public void hideSidePanel(boolean withAnimation) { 195 if (withAnimation) { 196 mPanel.removeCallbacks(mHideAllRunnable); 197 Animator hideAnimator = 198 AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_exit); 199 hideAnimator.setTarget(mPanel); 200 hideAnimator.start(); 201 hideAnimator.addListener(new AnimatorListenerAdapter() { 202 @Override 203 public void onAnimationEnd(Animator animation) { 204 mPanel.setVisibility(View.GONE); 205 } 206 }); 207 } else { 208 mPanel.setVisibility(View.GONE); 209 } 210 } 211 isSidePanelVisible()212 public boolean isSidePanelVisible() { 213 return mPanel.getVisibility() == View.VISIBLE; 214 } 215 scheduleHideAll()216 public void scheduleHideAll() { 217 mPanel.removeCallbacks(mHideAllRunnable); 218 mPanel.postDelayed(mHideAllRunnable, mShowDurationMillis); 219 } 220 221 /** 222 * Should {@code keyCode} hide the current panel. 223 */ isHideKeyForCurrentPanel(int keyCode)224 public boolean isHideKeyForCurrentPanel(int keyCode) { 225 if (isActive()) { 226 SideFragment current = (SideFragment) mFragmentManager.findFragmentById( 227 R.id.side_fragment_container); 228 return current != null && current.isHideKeyForThisPanel(keyCode); 229 } 230 return false; 231 } 232 } 233