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