1 /*
2  * Copyright (C) 2013 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.example.android.slidingfragments;
18 
19 import android.animation.Animator;
20 import android.animation.Animator.AnimatorListener;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.animation.ObjectAnimator;
24 import android.animation.PropertyValuesHolder;
25 import android.app.Activity;
26 import android.app.FragmentManager;
27 import android.app.FragmentTransaction;
28 import android.os.Bundle;
29 import android.view.View;
30 
31 /**
32  * This application shows a simple technique to animate and overlay two fragments
33  * on top of each other in order to provide a more immersive experience,
34  * as opposed to only having full screen transitions. When additional content
35  * (text) related to the currently displayed content (image) is to be shown,
36  * the currently visible content can be moved into the background instead of
37  * being removed from the screen entirely. This effect can therefore
38  * provide a more natural way of displaying additional information to the user
39  * using a different fragment.
40  *
41  * In this specific demo, tapping on the screen toggles between the two
42  * animated states of the fragment. When the animation is called,
43  * the fragment with an image animates into the background while the fragment
44  * containing text slides up on top of it. When the animation is toggled once
45  * more, the text fragment slides back down and the image fragment regains
46  * focus.
47  */
48 public class SlidingFragments extends Activity implements
49         OnTextFragmentAnimationEndListener, FragmentManager.OnBackStackChangedListener {
50 
51     ImageFragment mImageFragment;
52     TextFragment mTextFragment;
53     View mDarkHoverView;
54 
55     boolean mDidSlideOut = false;
56     boolean mIsAnimating = false;
57 
58     @Override
onCreate(Bundle savedInstanceState)59     protected void onCreate(Bundle savedInstanceState) {
60         super.onCreate(savedInstanceState);
61         setContentView(R.layout.sliding_fragments_layout);
62 
63         mDarkHoverView = findViewById(R.id.dark_hover_view);
64         mDarkHoverView.setAlpha(0);
65 
66         mImageFragment = (ImageFragment) getFragmentManager().findFragmentById(R.id.move_fragment);
67         mTextFragment = new TextFragment();
68 
69         getFragmentManager().addOnBackStackChangedListener(this);
70 
71         mImageFragment.setClickListener(mClickListener);
72         mTextFragment.setClickListener(mClickListener);
73         mTextFragment.setOnTextFragmentAnimationEnd(this);
74         mDarkHoverView.setOnClickListener(mClickListener);
75 
76     }
77 
78     View.OnClickListener mClickListener = new View.OnClickListener () {
79         @Override
80         public void onClick(View view) {
81             switchFragments();
82         }
83     };
84 
85     /**
86      * This method is used to toggle between the two fragment states by
87      * calling the appropriate animations between them. The entry and exit
88      * animations of the text fragment are specified in R.animator resource
89      * files. The entry and exit animations of the image fragment are
90      * specified in the slideBack and slideForward methods below. The reason
91      * for separating the animation logic in this way is because the translucent
92      * dark hover view must fade in at the same time as the image fragment
93      * animates into the background, which would be difficult to time
94      * properly given that the setCustomAnimations method can only modify the
95      * two fragments in the transaction.
96      */
switchFragments()97     private void switchFragments () {
98         if (mIsAnimating) {
99             return;
100         }
101         mIsAnimating = true;
102         if (mDidSlideOut) {
103             mDidSlideOut = false;
104             getFragmentManager().popBackStack();
105         } else {
106             mDidSlideOut = true;
107 
108             AnimatorListener listener = new AnimatorListenerAdapter() {
109                 @Override
110                 public void onAnimationEnd(Animator arg0) {
111                     FragmentTransaction transaction = getFragmentManager().beginTransaction();
112                     transaction.setCustomAnimations(R.animator.slide_fragment_in, 0, 0,
113                             R.animator.slide_fragment_out);
114                     transaction.add(R.id.move_to_back_container, mTextFragment);
115                     transaction.addToBackStack(null);
116                     transaction.commit();
117                 }
118             };
119             slideBack (listener);
120         }
121     }
122 
123     @Override
onBackStackChanged()124     public void onBackStackChanged() {
125         if (!mDidSlideOut) {
126             slideForward(null);
127         }
128 
129     }
130 
131     /**
132      * This method animates the image fragment into the background by both
133      * scaling and rotating the fragment's view, as well as adding a
134      * translucent dark hover view to inform the user that it is inactive.
135      */
slideBack(AnimatorListener listener)136     public void slideBack(AnimatorListener listener)
137     {
138         View movingFragmentView = mImageFragment.getView();
139 
140         PropertyValuesHolder rotateX =  PropertyValuesHolder.ofFloat("rotationX", 40f);
141         PropertyValuesHolder scaleX =  PropertyValuesHolder.ofFloat("scaleX", 0.8f);
142         PropertyValuesHolder scaleY =  PropertyValuesHolder.ofFloat("scaleY", 0.8f);
143         ObjectAnimator movingFragmentAnimator = ObjectAnimator.
144                 ofPropertyValuesHolder(movingFragmentView, rotateX, scaleX, scaleY);
145 
146         ObjectAnimator darkHoverViewAnimator = ObjectAnimator.
147                 ofFloat(mDarkHoverView, "alpha", 0.0f, 0.5f);
148 
149         ObjectAnimator movingFragmentRotator = ObjectAnimator.
150                 ofFloat(movingFragmentView, "rotationX", 0);
151         movingFragmentRotator.setStartDelay(getResources().
152                 getInteger(R.integer.half_slide_up_down_duration));
153 
154         AnimatorSet s = new AnimatorSet();
155         s.playTogether(movingFragmentAnimator, darkHoverViewAnimator, movingFragmentRotator);
156         s.addListener(listener);
157         s.start();
158     }
159 
160     /**
161      * This method animates the image fragment into the foreground by both
162      * scaling and rotating the fragment's view, while also removing the
163      * previously added translucent dark hover view. Upon the completion of
164      * this animation, the image fragment regains focus since this method is
165      * called from the onBackStackChanged method.
166      */
slideForward(AnimatorListener listener)167     public void slideForward(AnimatorListener listener)
168     {
169         View movingFragmentView = mImageFragment.getView();
170 
171         PropertyValuesHolder rotateX =  PropertyValuesHolder.ofFloat("rotationX", 40f);
172         PropertyValuesHolder scaleX =  PropertyValuesHolder.ofFloat("scaleX", 1.0f);
173         PropertyValuesHolder scaleY =  PropertyValuesHolder.ofFloat("scaleY", 1.0f);
174         ObjectAnimator movingFragmentAnimator = ObjectAnimator.
175                 ofPropertyValuesHolder(movingFragmentView, rotateX, scaleX, scaleY);
176 
177         ObjectAnimator darkHoverViewAnimator = ObjectAnimator.
178                 ofFloat(mDarkHoverView, "alpha", 0.5f, 0.0f);
179 
180         ObjectAnimator movingFragmentRotator = ObjectAnimator.
181                 ofFloat(movingFragmentView, "rotationX", 0);
182         movingFragmentRotator.setStartDelay(
183                 getResources().getInteger(R.integer.half_slide_up_down_duration));
184 
185         AnimatorSet s = new AnimatorSet();
186         s.playTogether(movingFragmentAnimator, movingFragmentRotator, darkHoverViewAnimator);
187         s.setStartDelay(getResources().getInteger(R.integer.slide_up_down_duration));
188         s.addListener(new AnimatorListenerAdapter() {
189             @Override
190             public void onAnimationEnd(Animator animation) {
191                 mIsAnimating = false;
192             }
193         });
194         s.start();
195     }
196 
onAnimationEnd()197     public void onAnimationEnd() {
198         mIsAnimating = false;
199     }
200 }
201