/* * Copyright (C) 2013 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.example.android.slidingfragments; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.app.Activity; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.os.Bundle; import android.view.View; /** * This application shows a simple technique to animate and overlay two fragments * on top of each other in order to provide a more immersive experience, * as opposed to only having full screen transitions. When additional content * (text) related to the currently displayed content (image) is to be shown, * the currently visible content can be moved into the background instead of * being removed from the screen entirely. This effect can therefore * provide a more natural way of displaying additional information to the user * using a different fragment. * * In this specific demo, tapping on the screen toggles between the two * animated states of the fragment. When the animation is called, * the fragment with an image animates into the background while the fragment * containing text slides up on top of it. When the animation is toggled once * more, the text fragment slides back down and the image fragment regains * focus. */ public class SlidingFragments extends Activity implements OnTextFragmentAnimationEndListener, FragmentManager.OnBackStackChangedListener { ImageFragment mImageFragment; TextFragment mTextFragment; View mDarkHoverView; boolean mDidSlideOut = false; boolean mIsAnimating = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.sliding_fragments_layout); mDarkHoverView = findViewById(R.id.dark_hover_view); mDarkHoverView.setAlpha(0); mImageFragment = (ImageFragment) getFragmentManager().findFragmentById(R.id.move_fragment); mTextFragment = new TextFragment(); getFragmentManager().addOnBackStackChangedListener(this); mImageFragment.setClickListener(mClickListener); mTextFragment.setClickListener(mClickListener); mTextFragment.setOnTextFragmentAnimationEnd(this); mDarkHoverView.setOnClickListener(mClickListener); } View.OnClickListener mClickListener = new View.OnClickListener () { @Override public void onClick(View view) { switchFragments(); } }; /** * This method is used to toggle between the two fragment states by * calling the appropriate animations between them. The entry and exit * animations of the text fragment are specified in R.animator resource * files. The entry and exit animations of the image fragment are * specified in the slideBack and slideForward methods below. The reason * for separating the animation logic in this way is because the translucent * dark hover view must fade in at the same time as the image fragment * animates into the background, which would be difficult to time * properly given that the setCustomAnimations method can only modify the * two fragments in the transaction. */ private void switchFragments () { if (mIsAnimating) { return; } mIsAnimating = true; if (mDidSlideOut) { mDidSlideOut = false; getFragmentManager().popBackStack(); } else { mDidSlideOut = true; AnimatorListener listener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator arg0) { FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.setCustomAnimations(R.animator.slide_fragment_in, 0, 0, R.animator.slide_fragment_out); transaction.add(R.id.move_to_back_container, mTextFragment); transaction.addToBackStack(null); transaction.commit(); } }; slideBack (listener); } } @Override public void onBackStackChanged() { if (!mDidSlideOut) { slideForward(null); } } /** * This method animates the image fragment into the background by both * scaling and rotating the fragment's view, as well as adding a * translucent dark hover view to inform the user that it is inactive. */ public void slideBack(AnimatorListener listener) { View movingFragmentView = mImageFragment.getView(); PropertyValuesHolder rotateX = PropertyValuesHolder.ofFloat("rotationX", 40f); PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.8f); PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.8f); ObjectAnimator movingFragmentAnimator = ObjectAnimator. ofPropertyValuesHolder(movingFragmentView, rotateX, scaleX, scaleY); ObjectAnimator darkHoverViewAnimator = ObjectAnimator. ofFloat(mDarkHoverView, "alpha", 0.0f, 0.5f); ObjectAnimator movingFragmentRotator = ObjectAnimator. ofFloat(movingFragmentView, "rotationX", 0); movingFragmentRotator.setStartDelay(getResources(). getInteger(R.integer.half_slide_up_down_duration)); AnimatorSet s = new AnimatorSet(); s.playTogether(movingFragmentAnimator, darkHoverViewAnimator, movingFragmentRotator); s.addListener(listener); s.start(); } /** * This method animates the image fragment into the foreground by both * scaling and rotating the fragment's view, while also removing the * previously added translucent dark hover view. Upon the completion of * this animation, the image fragment regains focus since this method is * called from the onBackStackChanged method. */ public void slideForward(AnimatorListener listener) { View movingFragmentView = mImageFragment.getView(); PropertyValuesHolder rotateX = PropertyValuesHolder.ofFloat("rotationX", 40f); PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); ObjectAnimator movingFragmentAnimator = ObjectAnimator. ofPropertyValuesHolder(movingFragmentView, rotateX, scaleX, scaleY); ObjectAnimator darkHoverViewAnimator = ObjectAnimator. ofFloat(mDarkHoverView, "alpha", 0.5f, 0.0f); ObjectAnimator movingFragmentRotator = ObjectAnimator. ofFloat(movingFragmentView, "rotationX", 0); movingFragmentRotator.setStartDelay( getResources().getInteger(R.integer.half_slide_up_down_duration)); AnimatorSet s = new AnimatorSet(); s.playTogether(movingFragmentAnimator, movingFragmentRotator, darkHoverViewAnimator); s.setStartDelay(getResources().getInteger(R.integer.slide_up_down_duration)); s.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mIsAnimating = false; } }); s.start(); } public void onAnimationEnd() { mIsAnimating = false; } }