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