Home | History | Annotate | Download | only in sidepanel
      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 
     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 
     81     public int getCount() {
     82         return mFragmentCount;
     83     }
     84 
     85     public boolean isActive() {
     86         return mFragmentCount != 0 && !isHiding();
     87     }
     88 
     89     public boolean isHiding() {
     90         return mHideAnimator.isStarted();
     91     }
     92 
     93     /**
     94      * Shows the given {@link SideFragment}.
     95      */
     96     public void show(SideFragment sideFragment) {
     97         show(sideFragment, true);
     98     }
     99 
    100     /**
    101      * Shows the given {@link SideFragment}.
    102      */
    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 
    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 
    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 
    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      */
    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      */
    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 
    235     public boolean isSidePanelVisible() {
    236         return mPanel.getVisibility() == View.VISIBLE;
    237     }
    238 
    239     /**
    240      * Resets the timer for hiding side fragment.
    241      */
    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      */
    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