| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.tv.ui.sidepanel; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorInflater; |
| import android.animation.AnimatorListenerAdapter; |
| import android.app.Activity; |
| import android.app.FragmentManager; |
| import android.app.FragmentTransaction; |
| import android.view.View; |
| import android.view.ViewTreeObserver; |
| import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; |
| import com.android.tv.R; |
| import com.android.tv.ui.hideable.AutoHideScheduler; |
| |
| /** Manages {@link SideFragment}s. */ |
| public class SideFragmentManager implements AccessibilityStateChangeListener { |
| private static final String FIRST_BACKSTACK_RECORD_NAME = "0"; |
| |
| private final Activity mActivity; |
| private final FragmentManager mFragmentManager; |
| private final Runnable mPreShowRunnable; |
| private final Runnable mPostHideRunnable; |
| private ViewTreeObserver.OnGlobalLayoutListener mShowOnGlobalLayoutListener; |
| |
| // To get the count reliably while using popBackStack(), |
| // instead of using getBackStackEntryCount() with popBackStackImmediate(). |
| private int mFragmentCount; |
| |
| private final View mPanel; |
| private final Animator mShowAnimator; |
| private final Animator mHideAnimator; |
| |
| private final AutoHideScheduler mAutoHideScheduler; |
| private final long mShowDurationMillis; |
| |
| public SideFragmentManager( |
| Activity activity, Runnable preShowRunnable, Runnable postHideRunnable) { |
| mActivity = activity; |
| mFragmentManager = mActivity.getFragmentManager(); |
| mPreShowRunnable = preShowRunnable; |
| mPostHideRunnable = postHideRunnable; |
| |
| mPanel = mActivity.findViewById(R.id.side_panel); |
| mShowAnimator = AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_enter); |
| mShowAnimator.setTarget(mPanel); |
| mHideAnimator = AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_exit); |
| mHideAnimator.setTarget(mPanel); |
| mHideAnimator.addListener( |
| new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| // Animation is still in running state at this point. |
| hideAllInternal(); |
| } |
| }); |
| |
| mShowDurationMillis = |
| mActivity.getResources().getInteger(R.integer.side_panel_show_duration); |
| mAutoHideScheduler = new AutoHideScheduler(activity, () -> hideAll(true)); |
| } |
| |
| public int getCount() { |
| return mFragmentCount; |
| } |
| |
| public boolean isActive() { |
| return mFragmentCount != 0 && !isHiding(); |
| } |
| |
| public boolean isHiding() { |
| return mHideAnimator.isStarted(); |
| } |
| |
| /** Shows the given {@link SideFragment}. */ |
| public void show(SideFragment sideFragment) { |
| show(sideFragment, true); |
| } |
| |
| /** Shows the given {@link SideFragment}. */ |
| public void show(SideFragment sideFragment, boolean showEnterAnimation) { |
| if (isHiding()) { |
| mHideAnimator.end(); |
| } |
| boolean isFirst = (mFragmentCount == 0); |
| FragmentTransaction ft = mFragmentManager.beginTransaction(); |
| if (!isFirst) { |
| ft.setCustomAnimations( |
| showEnterAnimation ? R.animator.side_panel_fragment_enter : 0, |
| R.animator.side_panel_fragment_exit, |
| R.animator.side_panel_fragment_pop_enter, |
| R.animator.side_panel_fragment_pop_exit); |
| } |
| ft.replace(R.id.side_fragment_container, sideFragment) |
| .addToBackStack(Integer.toString(mFragmentCount)) |
| .commit(); |
| mFragmentCount++; |
| |
| if (isFirst) { |
| // We should wait for fragment transition and intital layouting finished to start the |
| // slide-in animation to prevent jankiness resulted by performing transition and |
| // layouting at the same time with animation. |
| mPanel.setVisibility(View.VISIBLE); |
| mShowOnGlobalLayoutListener = |
| new ViewTreeObserver.OnGlobalLayoutListener() { |
| @Override |
| public void onGlobalLayout() { |
| mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(this); |
| mShowOnGlobalLayoutListener = null; |
| if (mPreShowRunnable != null) { |
| mPreShowRunnable.run(); |
| } |
| mShowAnimator.start(); |
| } |
| }; |
| mPanel.getViewTreeObserver().addOnGlobalLayoutListener(mShowOnGlobalLayoutListener); |
| } |
| scheduleHideAll(); |
| } |
| |
| public void popSideFragment() { |
| if (!isActive()) { |
| return; |
| } else if (mFragmentCount == 1) { |
| // Show closing animation with the last fragment. |
| hideAll(true); |
| return; |
| } |
| mFragmentManager.popBackStack(); |
| mFragmentCount--; |
| } |
| |
| public void hideAll(boolean withAnimation) { |
| if (mShowAnimator.isStarted()) { |
| mShowAnimator.end(); |
| } |
| if (mShowOnGlobalLayoutListener != null) { |
| // The show operation maybe requested but the show animator is not started yet, in this |
| // case, we show still run mPreShowRunnable. |
| mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(mShowOnGlobalLayoutListener); |
| mShowOnGlobalLayoutListener = null; |
| if (mPreShowRunnable != null) { |
| mPreShowRunnable.run(); |
| } |
| } |
| if (withAnimation) { |
| if (!isHiding()) { |
| mHideAnimator.start(); |
| } |
| return; |
| } |
| if (isHiding()) { |
| mHideAnimator.end(); |
| return; |
| } |
| hideAllInternal(); |
| } |
| |
| private void hideAllInternal() { |
| mAutoHideScheduler.cancel(); |
| if (mFragmentCount == 0) { |
| return; |
| } |
| |
| mPanel.setVisibility(View.GONE); |
| mFragmentManager.popBackStack( |
| FIRST_BACKSTACK_RECORD_NAME, FragmentManager.POP_BACK_STACK_INCLUSIVE); |
| mFragmentCount = 0; |
| |
| if (mPostHideRunnable != null) { |
| mPostHideRunnable.run(); |
| } |
| } |
| |
| /** |
| * Show the side panel with animation. If there are many entries in the fragment stack, the |
| * animation look like that there's only one fragment. |
| * |
| * @param withAnimation specifies if animation should be shown. |
| */ |
| public void showSidePanel(boolean withAnimation) { |
| if (mFragmentCount == 0) { |
| return; |
| } |
| |
| mPanel.setVisibility(View.VISIBLE); |
| if (withAnimation) { |
| mShowAnimator.start(); |
| } |
| scheduleHideAll(); |
| } |
| |
| /** |
| * Hide the side panel. This method just hide the panel and preserves the back stack. If you |
| * want to empty the back stack, call {@link #hideAll}. |
| */ |
| public void hideSidePanel(boolean withAnimation) { |
| mAutoHideScheduler.cancel(); |
| if (withAnimation) { |
| Animator hideAnimator = |
| AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_exit); |
| hideAnimator.setTarget(mPanel); |
| hideAnimator.start(); |
| hideAnimator.addListener( |
| new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| mPanel.setVisibility(View.GONE); |
| } |
| }); |
| } else { |
| mPanel.setVisibility(View.GONE); |
| } |
| } |
| |
| public boolean isSidePanelVisible() { |
| return mPanel.getVisibility() == View.VISIBLE; |
| } |
| |
| /** Resets the timer for hiding side fragment. */ |
| public void scheduleHideAll() { |
| mAutoHideScheduler.schedule(mShowDurationMillis); |
| } |
| |
| /** Should {@code keyCode} hide the current panel. */ |
| public boolean isHideKeyForCurrentPanel(int keyCode) { |
| if (isActive()) { |
| SideFragment current = |
| (SideFragment) mFragmentManager.findFragmentById(R.id.side_fragment_container); |
| return current != null && current.isHideKeyForThisPanel(keyCode); |
| } |
| return false; |
| } |
| |
| @Override |
| public void onAccessibilityStateChanged(boolean enabled) { |
| mAutoHideScheduler.onAccessibilityStateChanged(enabled); |
| } |
| } |