diff --git a/samples/devbytes/animation/SlidingFragments/AndroidManifest.xml b/samples/devbytes/animation/SlidingFragments/AndroidManifest.xml new file mode 100644 index 000000000..2e71b6c9c --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + diff --git a/samples/devbytes/animation/SlidingFragments/res/animator/slide_fragment_in.xml b/samples/devbytes/animation/SlidingFragments/res/animator/slide_fragment_in.xml new file mode 100644 index 000000000..7fe333b6d --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/res/animator/slide_fragment_in.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/SlidingFragments/res/animator/slide_fragment_out.xml b/samples/devbytes/animation/SlidingFragments/res/animator/slide_fragment_out.xml new file mode 100644 index 000000000..4d4134a32 --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/res/animator/slide_fragment_out.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/SlidingFragments/res/drawable-hdpi/golden_gate.jpg b/samples/devbytes/animation/SlidingFragments/res/drawable-hdpi/golden_gate.jpg new file mode 100644 index 000000000..9a3d3fd7b Binary files /dev/null and b/samples/devbytes/animation/SlidingFragments/res/drawable-hdpi/golden_gate.jpg differ diff --git a/samples/devbytes/animation/SlidingFragments/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/SlidingFragments/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..96a442e5b Binary files /dev/null and b/samples/devbytes/animation/SlidingFragments/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/SlidingFragments/res/drawable-ldpi/ic_launcher.png b/samples/devbytes/animation/SlidingFragments/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 000000000..99238729d Binary files /dev/null and b/samples/devbytes/animation/SlidingFragments/res/drawable-ldpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/SlidingFragments/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/SlidingFragments/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..359047dfa Binary files /dev/null and b/samples/devbytes/animation/SlidingFragments/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/SlidingFragments/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/SlidingFragments/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..71c6d760f Binary files /dev/null and b/samples/devbytes/animation/SlidingFragments/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/SlidingFragments/res/layout/image_fragment.xml b/samples/devbytes/animation/SlidingFragments/res/layout/image_fragment.xml new file mode 100644 index 000000000..eb555fea4 --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/res/layout/image_fragment.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/SlidingFragments/res/layout/sliding_fragments_layout.xml b/samples/devbytes/animation/SlidingFragments/res/layout/sliding_fragments_layout.xml new file mode 100644 index 000000000..a789a77c7 --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/res/layout/sliding_fragments_layout.xml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/SlidingFragments/res/layout/text_fragment.xml b/samples/devbytes/animation/SlidingFragments/res/layout/text_fragment.xml new file mode 100644 index 000000000..652acaa7f --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/res/layout/text_fragment.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/SlidingFragments/res/values/colors.xml b/samples/devbytes/animation/SlidingFragments/res/values/colors.xml new file mode 100644 index 000000000..f5eb07aad --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/res/values/colors.xml @@ -0,0 +1,23 @@ + + + + + #ff0000 + #000000 + #ffffff + #00ff00 + + \ No newline at end of file diff --git a/samples/devbytes/animation/SlidingFragments/res/values/dimens.xml b/samples/devbytes/animation/SlidingFragments/res/values/dimens.xml new file mode 100644 index 000000000..2b378c132 --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/res/values/dimens.xml @@ -0,0 +1,22 @@ + + + + + 50dp + 18dp + 10dp + + diff --git a/samples/devbytes/animation/SlidingFragments/res/values/integers.xml b/samples/devbytes/animation/SlidingFragments/res/values/integers.xml new file mode 100644 index 000000000..57c19acdd --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/res/values/integers.xml @@ -0,0 +1,22 @@ + + + + + 200 + 150 + 150 + + \ No newline at end of file diff --git a/samples/devbytes/animation/SlidingFragments/res/values/items.xml b/samples/devbytes/animation/SlidingFragments/res/values/items.xml new file mode 100644 index 000000000..2b7f0b58b --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/res/values/items.xml @@ -0,0 +1,20 @@ + + + + + 0.67 + + \ No newline at end of file diff --git a/samples/devbytes/animation/SlidingFragments/res/values/strings.xml b/samples/devbytes/animation/SlidingFragments/res/values/strings.xml new file mode 100644 index 000000000..dbc41da47 --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/res/values/strings.xml @@ -0,0 +1,29 @@ + + + + + SlidingFragments + Golden Gate Sunset by Romain Guy + Address: Golden Gate Bridge, San Francisco, CA 94129 + Elevation: 220\' (67 m) + Height: 746\' (227 m) + Construction started: 1933 + Longest span: 4,200\' (1,280 m) + Total length: 8,980\' (2,737 m) + "The Golden Gate Bridge is a suspension bridge spanning the Golden Gate, the opening of the San Francisco Bay into the Pacific Ocean." + Golden Gate Bridge + + diff --git a/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/FractionalLinearLayout.java b/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/FractionalLinearLayout.java new file mode 100644 index 000000000..55f7269da --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/FractionalLinearLayout.java @@ -0,0 +1,62 @@ +/* + * 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.content.Context; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +/** + * In order to animate the fragment containing text on/off the screen, + * it is required that we know the height of the device being used. However, + * this can only be determined at runtime, so we cannot specify the required + * translation in an xml file. Since FragmentTransaction's setCustomAnimations + * method requires an ID of an animation defined via an xml file, this linear + * layout was built as a workaround. This custom linear layout is created to specify + * the location of the fragment's layout as a fraction of the device's height. By + * animating yFraction from 0 to 1, we can animate the fragment from the top of + * the screen to the bottom of the screen, regardless of the device's specific size. + */ +public class FractionalLinearLayout extends LinearLayout { + + private float mYFraction; + private int mScreenHeight; + + public FractionalLinearLayout(Context context) { + super(context); + } + + public FractionalLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onSizeChanged (int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mScreenHeight = h; + setY(mScreenHeight); + } + + public float getYFraction() { + return mYFraction; + } + + public void setYFraction(float yFraction) { + mYFraction = yFraction; + setY((mScreenHeight > 0) ? (mScreenHeight - yFraction * mScreenHeight) : 0); + } +} diff --git a/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/ImageFragment.java b/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/ImageFragment.java new file mode 100644 index 000000000..e306c98a8 --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/ImageFragment.java @@ -0,0 +1,39 @@ +/* + * 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.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class ImageFragment extends Fragment { + + View.OnClickListener clickListener; + + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.image_fragment, container, false); + view.setOnClickListener(clickListener); + return view; + } + + public void setClickListener(View.OnClickListener clickListener) { + this.clickListener = clickListener; + } +} diff --git a/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/OnTextFragmentAnimationEndListener.java b/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/OnTextFragmentAnimationEndListener.java new file mode 100644 index 000000000..692a7cbc7 --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/OnTextFragmentAnimationEndListener.java @@ -0,0 +1,26 @@ +/* + * 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; + +/** + * This interface is used to inform the main activity when the entry + * animation of the text fragment has completed in order to avoid the + * start of a new animation before the current one has completed. + */ +public interface OnTextFragmentAnimationEndListener { + public void onAnimationEnd(); +} diff --git a/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/SlidingFragments.java b/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/SlidingFragments.java new file mode 100644 index 000000000..9bfd8d4a2 --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/SlidingFragments.java @@ -0,0 +1,200 @@ +/* + * 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; + } +} diff --git a/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/TextFragment.java b/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/TextFragment.java new file mode 100644 index 000000000..f1f245cee --- /dev/null +++ b/samples/devbytes/animation/SlidingFragments/src/com/example/android/slidingfragments/TextFragment.java @@ -0,0 +1,65 @@ +/* + * 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.AnimatorInflater; +import android.animation.AnimatorListenerAdapter; +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class TextFragment extends Fragment { + + View.OnClickListener clickListener; + OnTextFragmentAnimationEndListener mListener; + + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.text_fragment, container, false); + view.setOnClickListener(clickListener); + return view; + } + + public void setClickListener(View.OnClickListener clickListener) { + this.clickListener = clickListener; + } + + @Override + public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) + { + int id = enter ? R.animator.slide_fragment_in : R.animator.slide_fragment_out; + final Animator anim = AnimatorInflater.loadAnimator(getActivity(), id); + if (enter) { + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mListener.onAnimationEnd(); + } + }); + } + return anim; + } + + public void setOnTextFragmentAnimationEnd(OnTextFragmentAnimationEndListener listener) + { + mListener = listener; + } + +}