diff --git a/docs/downloads/training/Animations.zip b/docs/downloads/training/Animations.zip new file mode 100644 index 0000000000000..5063dd1a5c845 Binary files /dev/null and b/docs/downloads/training/Animations.zip differ diff --git a/docs/html/training/animation/anim_card_flip.mp4 b/docs/html/training/animation/anim_card_flip.mp4 new file mode 100755 index 0000000000000..e885f987c413d Binary files /dev/null and b/docs/html/training/animation/anim_card_flip.mp4 differ diff --git a/docs/html/training/animation/anim_card_flip.ogv b/docs/html/training/animation/anim_card_flip.ogv new file mode 100755 index 0000000000000..33cd86c23e092 Binary files /dev/null and b/docs/html/training/animation/anim_card_flip.ogv differ diff --git a/docs/html/training/animation/anim_card_flip.webm b/docs/html/training/animation/anim_card_flip.webm new file mode 100755 index 0000000000000..a670d7831c8a4 Binary files /dev/null and b/docs/html/training/animation/anim_card_flip.webm differ diff --git a/docs/html/training/animation/anim_crossfade.mp4 b/docs/html/training/animation/anim_crossfade.mp4 new file mode 100644 index 0000000000000..ced7cc9e7c53b Binary files /dev/null and b/docs/html/training/animation/anim_crossfade.mp4 differ diff --git a/docs/html/training/animation/anim_crossfade.ogv b/docs/html/training/animation/anim_crossfade.ogv new file mode 100644 index 0000000000000..7ec417a802b8e Binary files /dev/null and b/docs/html/training/animation/anim_crossfade.ogv differ diff --git a/docs/html/training/animation/anim_crossfade.webm b/docs/html/training/animation/anim_crossfade.webm new file mode 100644 index 0000000000000..21e7228887978 Binary files /dev/null and b/docs/html/training/animation/anim_crossfade.webm differ diff --git a/docs/html/training/animation/anim_layout_changes.mp4 b/docs/html/training/animation/anim_layout_changes.mp4 new file mode 100644 index 0000000000000..0709601f432fb Binary files /dev/null and b/docs/html/training/animation/anim_layout_changes.mp4 differ diff --git a/docs/html/training/animation/anim_layout_changes.ogv b/docs/html/training/animation/anim_layout_changes.ogv new file mode 100644 index 0000000000000..75f5250107f66 Binary files /dev/null and b/docs/html/training/animation/anim_layout_changes.ogv differ diff --git a/docs/html/training/animation/anim_layout_changes.webm b/docs/html/training/animation/anim_layout_changes.webm new file mode 100644 index 0000000000000..a99a5666ba75d Binary files /dev/null and b/docs/html/training/animation/anim_layout_changes.webm differ diff --git a/docs/html/training/animation/anim_screenslide.mp4 b/docs/html/training/animation/anim_screenslide.mp4 new file mode 100755 index 0000000000000..3e6502642e644 Binary files /dev/null and b/docs/html/training/animation/anim_screenslide.mp4 differ diff --git a/docs/html/training/animation/anim_screenslide.ogv b/docs/html/training/animation/anim_screenslide.ogv new file mode 100755 index 0000000000000..c45ebd44db53a Binary files /dev/null and b/docs/html/training/animation/anim_screenslide.ogv differ diff --git a/docs/html/training/animation/anim_screenslide.webm b/docs/html/training/animation/anim_screenslide.webm new file mode 100755 index 0000000000000..c72adbcb13849 Binary files /dev/null and b/docs/html/training/animation/anim_screenslide.webm differ diff --git a/docs/html/training/animation/anim_zoom.mp4 b/docs/html/training/animation/anim_zoom.mp4 new file mode 100644 index 0000000000000..4326c35b3033e Binary files /dev/null and b/docs/html/training/animation/anim_zoom.mp4 differ diff --git a/docs/html/training/animation/anim_zoom.ogv b/docs/html/training/animation/anim_zoom.ogv new file mode 100644 index 0000000000000..e5793f3a76651 Binary files /dev/null and b/docs/html/training/animation/anim_zoom.ogv differ diff --git a/docs/html/training/animation/anim_zoom.webm b/docs/html/training/animation/anim_zoom.webm new file mode 100644 index 0000000000000..b3b7566c5e8c4 Binary files /dev/null and b/docs/html/training/animation/anim_zoom.webm differ diff --git a/docs/html/training/animation/cardflip.jd b/docs/html/training/animation/cardflip.jd new file mode 100644 index 0000000000000..ab3eb3af8c0f1 --- /dev/null +++ b/docs/html/training/animation/cardflip.jd @@ -0,0 +1,365 @@ +page.title=Displaying Card Flip Animations +trainingnavtop=true + +@jd:body +
This lesson shows you how to do a card flip + animation with custom fragment animations. + Card flips animate between views of content by showing an animation that emulates + a card flipping over. +
+Here's what a card flip looks like: +
+ ++ If you want to jump ahead and see a full working example, + download and + run the sample app and select the Card Flip example. See the following + files for the code implementation: +
+src/CardFlipActivity.java
+ animator/card_flip_right_in.xml
+ animator/card_flip_right_out.xml
+ animator/card_flip_right_in.xml
+ animator/card_flip_left_out.xml
+ layout/fragment_card_back.xml
+ layout/fragment_card_front.xml
+ + Create the animations for the card flips. You'll need two animators for when the front + of the card animates out and to the left and in and from the left. You'll also need two animators + for when the back of the card animates in and from the right and out and to the right. +
++<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Before rotating, immediately set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:duration="0" /> + + <!-- Rotate. --> + <objectAnimator + android:valueFrom="-180" + android:valueTo="0" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 1. --> + <objectAnimator + android:valueFrom="0.0" + android:valueTo="1.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> +</set> ++
+<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Rotate. --> + <objectAnimator + android:valueFrom="0" + android:valueTo="180" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> +</set> ++
+<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Before rotating, immediately set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:duration="0" /> + + <!-- Rotate. --> + <objectAnimator + android:valueFrom="180" + android:valueTo="0" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 1. --> + <objectAnimator + android:valueFrom="0.0" + android:valueTo="1.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> + + ++
+<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Rotate. --> + <objectAnimator + android:valueFrom="0" + android:valueTo="-180" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> +</set> ++
+ Each side of the "card" is a separate layout that can contain any content you want, + such as two screens of text, two images, or any combination of views to flip between. You'll then + use the two layouts in the fragments that you'll later animate. The following layouts + create one side of a card that shows text: +
+ ++<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="#a6c" + android:padding="16dp" + android:gravity="bottom"> + + <TextView android:id="@android:id/text1" + style="?android:textAppearanceLarge" + android:textStyle="bold" + android:textColor="#fff" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/card_back_title" /> + + <TextView style="?android:textAppearanceSmall" + android:textAllCaps="true" + android:textColor="#80ffffff" + android:textStyle="bold" + android:lineSpacingMultiplier="1.2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/card_back_description" /> + +</LinearLayout> ++
+and the other side of the card that displays an {@link android.widget.ImageView}: +
++<ImageView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:src="@drawable/image1" + android:scaleType="centerCrop" + android:contentDescription="@string/description_image_1" /> ++
+ Create fragment classes for the front and back of the card. These classes return the layouts + that you created previously in the {@link android.app.Fragment#onCreateView onCreateView()} method + of each fragment. You can then create instances of this fragment in the parent activity + where you want to show the card. The following example shows nested fragment classes inside + of the parent activity that uses them: +
+
+public class CardFlipActivity extends Activity {
+ ...
+ /**
+ * A fragment representing the front of the card.
+ */
+ public class CardFrontFragment extends Fragment {
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_card_front, container, false);
+ }
+ }
+
+ /**
+ * A fragment representing the back of the card.
+ */
+ public class CardBackFragment extends Fragment {
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_card_back, container, false);
+ }
+ }
+}
+
+ Now, you'll need to display the fragments inside of a parent activity. + To do this, first create the layout for your activity. The following example creates a + {@link android.widget.FrameLayout} that you + can add fragments to at runtime:
+ ++<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> ++ +
In the activity code, set the content view to be the layout that you just created. It's also + good idea to show a default fragment when the activity is created, so the following example + activity shows you how to display the front of the card by default: +
+ + + +
+public class CardFlipActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_activity_card_flip);
+
+ if (savedInstanceState == null) {
+ getFragmentManager()
+ .beginTransaction()
+ .add(R.id.container, new CardFrontFragment())
+ .commit();
+ }
+ }
+ ...
+}
+
+ + Now that you have the front of the card showing, you can show the back of the card + with the flip animation at an appropriate time. Create a method to show the other + side of the card that does the following things: +
+
+private void flipCard() {
+ if (mShowingBack) {
+ getFragmentManager().popBackStack();
+ return;
+ }
+
+ // Flip to the back.
+
+ mShowingBack = true;
+
+ // Create and commit a new fragment transaction that adds the fragment for the back of
+ // the card, uses custom animations, and is part of the fragment manager's back stack.
+
+ getFragmentManager()
+ .beginTransaction()
+
+ // Replace the default fragment animations with animator resources representing
+ // rotations when switching to the back of the card, as well as animator
+ // resources representing rotations when flipping back to the front (e.g. when
+ // the system Back button is pressed).
+ .setCustomAnimations(
+ R.animator.card_flip_right_in, R.animator.card_flip_right_out,
+ R.animator.card_flip_left_in, R.animator.card_flip_left_out)
+
+ // Replace any fragments currently in the container view with a fragment
+ // representing the next page (indicated by the just-incremented currentPage
+ // variable).
+ .replace(R.id.container, new CardBackFragment())
+
+ // Add this transaction to the back stack, allowing users to press Back
+ // to get to the front of the card.
+ .addToBackStack(null)
+
+ // Commit the transaction.
+ .commit();
+}
+
\ No newline at end of file
diff --git a/docs/html/training/animation/crossfade.jd b/docs/html/training/animation/crossfade.jd
new file mode 100644
index 0000000000000..99e879bbc0033
--- /dev/null
+++ b/docs/html/training/animation/crossfade.jd
@@ -0,0 +1,208 @@
+page.title=Crossfading Two Views
+trainingnavtop=true
+
+
+@jd:body
+
+ + Crossfade animations (also know as dissolve) gradually fade out one UI component while simultaneously fading in + another. This animation is useful for situations where you want to switch content or views + in your app. Crossfades are very subtle and short but offer a fluid transition from one screen to the + next. When you don't use them, however, transitions often feel abrupt or hurried. +
+Here's an example of a crossfade from a progress indicator to some text content. +
+ ++ If you want to jump ahead and see a full working example, + download + and run the sample app and select the Crossfade example. + See the following files for the code implementation: +
+src/CrossfadeActivity.java
+ layout/activity_crossfade.xml
+ menu/activity_crossfade.xml
+ + Create the two views that you want to crossfade. The following example creates a progress + indicator and a scrollable text view: +
++<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView style="?android:textAppearanceMedium" + android:lineSpacingMultiplier="1.2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/lorem_ipsum" + android:padding="16dp" /> + + </ScrollView> + + <ProgressBar android:id="@+id/loading_spinner" + style="?android:progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" /> + +</FrameLayout> ++
+ To set up the animation: +
+{@link android.R.integer#config_shortAnimTime}
+ system property in a member variable. This property defines a standard
+ "short" duration for the animation. This duration is ideal for subtle animations or
+ animations that occur very frequently. {@link android.R.integer#config_longAnimTime} and
+ {@link android.R.integer#config_mediumAnimTime} are also available if you wish to use them.
+ + Here's an example using the layout from the previous code snippet as the activity content + view: +
+
+public class CrossfadeActivity extends Activity {
+
+ private View mContentView;
+ private View mLoadingView;
+ private int mShortAnimationDuration;
+
+ ...
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_crossfade);
+
+ mContentView = findViewById(R.id.content);
+ mLoadingView = findViewById(R.id.loading_spinner);
+
+ // Initially hide the content view.
+ mContentView.setVisibility(View.GONE);
+
+ // Retrieve and cache the system's default "short" animation time.
+ mShortAnimationDuration = getResources().getInteger(
+ android.R.integer.config_shortAnimTime);
+ }
+
+
+ + Now that the views are properly set up, crossfade them by doing the following: +
+0 and the visibility
+ to {@link android.view.View#VISIBLE}. (Remember that it was initially set to {@link
+ android.view.View#GONE}.) This makes the view visible but completely transparent.
+ 0 to
+ 1. At the same time, for the view that is fading out, animate the alpha value
+ from 1 to 0.
+ 0,
+ setting the view's visibility to {@link android.view.View#GONE} prevents the view from taking
+ up layout space and omits it from layout calculations, speeding up processing.
+ + The following method shows an example of how to do this: +
+
+private View mContentView;
+private View mLoadingView;
+private int mShortAnimationDuration;
+
+...
+
+private void crossfade() {
+
+ // Set the content view to 0% opacity but visible, so that it is visible
+ // (but fully transparent) during the animation.
+ mContentView.setAlpha(0f);
+ mContentView.setVisibility(View.VISIBLE);
+
+ // Animate the content view to 100% opacity, and clear any animation
+ // listener set on the view.
+ mContentView.animate()
+ .alpha(1f)
+ .setDuration(mShortAnimationDuration)
+ .setListener(null);
+
+ // Animate the loading view to 0% opacity. After the animation ends,
+ // set its visibility to GONE as an optimization step (it won't
+ // participate in layout passes, etc.)
+ mHideView.animate()
+ .alpha(0f)
+ .setDuration(mShortAnimationDuration)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHideView.setVisibility(View.GONE);
+ }
+ });
+}
+
\ No newline at end of file
diff --git a/docs/html/training/animation/index.jd b/docs/html/training/animation/index.jd
new file mode 100644
index 0000000000000..9cc7e6c405c0e
--- /dev/null
+++ b/docs/html/training/animation/index.jd
@@ -0,0 +1,86 @@
+page.title=Adding Animations
+trainingnavtop=true
+startpage=true
+
+@jd:body
+ + Animations.zip +
++ Animations can add subtle visual cues that notify users about what's going on in your app and + improve their mental model of your app's interface. Animations are especially useful when the + screen changes state, such as when content loads or new actions become available. Animations + can also add a polished look to your app, which gives your app a higher quality feel. +
++ Keep in mind though, that overusing animations or using them at the wrong time can be + detrimental, such as when they cause delays. This training class shows you how to + implement some common types of animations that can increase usability and add flair without + annoying your users. +
+ +A layout animation is a pre-loaded animation that the system runs each time you make a change + to the layout configuration. All you need to do is set an attribute in the layout to tell the + Android system to animate these layout changes, and system-default animations are carried out for you. +
+Tip: If you want to supply custom layout animations, +create a {@link android.animation.LayoutTransition} object and supply it to +the layout with the {@link android.view.ViewGroup#setLayoutTransition setLayoutTransition()} +method. +
+ Here's what a default layout animation looks like when adding items to a list: + + +If you want to jump ahead and see a full working example, +download and +run the sample app and select the Crossfade example. See the following files for the +code implementation:
+src/LayoutChangesActivity.javalayout/activity_layout_changes.xmlmenu/activity_layout_changes.xmlIn your activity's layout XML file, set the android:animateLayoutChanges
+ attribute to true for the layout that you want to enable animations for.
+ For instance:
+<LinearLayout android:id="@+id/container" + android:animateLayoutChanges="true" + ... +/> ++ +
+Now, all you need to do is add, remove, or update items in the layout +and the items are animated automatically: +
+
+private ViewGroup mContainerView;
+...
+private void addItem() {
+ View newView;
+ ...
+ mContainerView.addView(newView, 0);
+}
+
diff --git a/docs/html/training/animation/screen-slide.jd b/docs/html/training/animation/screen-slide.jd
new file mode 100755
index 0000000000000..8a7af672e916f
--- /dev/null
+++ b/docs/html/training/animation/screen-slide.jd
@@ -0,0 +1,185 @@
+page.title=Using ViewPager for Screen Slides
+trainingnavtop=true
+
+@jd:body
+
+ + Screen slides are transitions between one entire screen to another and are common with UIs + like setup wizards or slideshows. This lesson shows you how to do screen slides with + a {@link android.support.v4.view.ViewPager} provided by the support library. + {@link android.support.v4.view.ViewPager}s can animate screen slides + automatically. Here's what a screen slide looks like that transitions from + one screen of content to the next: +
+ +If you want to jump ahead and see a full working example, +download +and run the sample app and select the Screen Slide example. See the +following files for the code implementation:
+src/ScreenSlidePageFragment.javasrc/ScreenSlideActivity.javalayout/activity_screen_slide.xmllayout/fragment_screen_slide_page.xmlCreate a layout file that you'll later use for the content of a fragment. The following example + contains a text view to display some text: + +
+<com.example.android.animationsdemo.ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView style="?android:textAppearanceMedium" + android:padding="16dp" + android:lineSpacingMultiplier="1.2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/lorem_ipsum" /> + +</com.example.android.animationsdemo.ScrollView> ++ +
Create a {@link android.support.v4.app.Fragment} class that returns the layout +that you just created in the {@link android.app.Fragment#onCreateView onCreateView()} + method. You can then create instances of this fragment in the parent activity whenever you need a new page to + display to the user:
+ + +
+public class ScreenSlidePageFragment extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ ViewGroup rootView = (ViewGroup) inflater.inflate(
+ R.layout.fragment_screen_slide_page, container, false);
+
+ return rootView;
+ }
+}
+
+
+{@link android.support.v4.view.ViewPager}s have built-in swipe gestures to transition + through pages, and they display screen slide animations by default, so you don't need to create any. {@link android.support.v4.view.ViewPager}s use +{@link android.support.v4.view.PagerAdapter}s as a supply for new pages to display, so the {@link android.support.v4.view.PagerAdapter} will use the +fragment class that you created earlier. +
+ +To begin, create a layout that contains a {@link android.support.v4.view.ViewPager}:
+ ++<android.support.v4.view.ViewPager + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/pager" + android:layout_width="match_parent" + android:layout_height="match_parent" /> ++ +
Create an activity that does the following things: +
+ +ScreenSlidePageFragment as new pages. The pager adapter also requires that you implement the
+ {@link android.support.v4.view.PagerAdapter#getCount getCount()} method, which returns the amount of pages the adapter will create (five in the example).
+
+public class ScreenSlidePagerActivity extends FragmentActivity {
+ /**
+ * The number of pages (wizard steps) to show in this demo.
+ */
+ private static final int NUM_PAGES = 5;
+
+ /**
+ * The pager widget, which handles animation and allows swiping horizontally to access previous
+ * and next wizard steps.
+ */
+ private ViewPager mPager;
+
+ /**
+ * The pager adapter, which provides the pages to the view pager widget.
+ */
+ private PagerAdapter mPagerAdapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_screen_slide_pager);
+
+ // Instantiate a ViewPager and a PagerAdapter.
+ mPager = (ViewPager) findViewById(R.id.pager);
+ mPagerAdapter = new ScreenSlidePagerAdapter(getFragmentManager());
+ mPager.setAdapter(mPagerAdapter);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mPager.getCurrentItem() == 0) {
+ // If the user is currently looking at the first step, allow the system to handle the
+ // Back button. This calls finish() on this activity and pops the back stack.
+ super.onBackPressed();
+ } else {
+ // Otherwise, select the previous step.
+ mPager.setCurrentItem(mPager.getCurrentItem() - 1);
+ }
+ }
+
+ /**
+ * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in
+ * sequence.
+ */
+ private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
+ public ScreenSlidePagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ return new ScreenSlidePageFragment();
+ }
+
+ @Override
+ public int getCount() {
+ return NUM_PAGES;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/docs/html/training/animation/zoom.jd b/docs/html/training/animation/zoom.jd
new file mode 100644
index 0000000000000..5dc2b6c25b925
--- /dev/null
+++ b/docs/html/training/animation/zoom.jd
@@ -0,0 +1,320 @@
+page.title=Zooming a View
+trainingnavtop=true
+
+@jd:body
+
+ + This lesson demonstrates how to do a touch-to-zoom animation, which is useful for apps such as photo + galleries to animate a view from a thumbnail to a full-size image that fills the screen. +
+Here's what a touch-to-zoom animation looks like that + expands an image thumbnail to fill the screen: +
+ ++ If you want to jump ahead and see a full working example, + download and + run the sample app and select the + Zoom example. See the following files for the code implementation: +
+src/TouchHighlightImageButton.java (a simple helper class that shows a blue
+ touch highlight when the image button is pressed)
+ src/ZoomActivity.java
+ layout/activity_zoom.xml
+ + Create a layout file that contains the small and large version of the content that you want + to zoom. The following example creates an {@link android.widget.ImageButton} for clickable image thumbnail + and an {@link android.widget.ImageView} that displays the enlarged view of the image: +
++<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="16dp"> + + <ImageButton + android:id="@+id/thumb_button_1" + android:layout_width="100dp" + android:layout_height="75dp" + android:layout_marginRight="1dp" + android:src="@drawable/thumb1" + android:scaleType="centerCrop" + android:contentDescription="@string/description_image_1" /> + + </LinearLayout> + + <!-- This initially-hidden ImageView will hold the expanded/zoomed version of + the images above. Without transformations applied, it takes up the entire + screen. To achieve the "zoom" animation, this view's bounds are animated + from the bounds of the thumbnail button above, to its final laid-out + bounds. + --> + + <ImageView + android:id="@+id/expanded_image" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="invisible" + android:contentDescription="@string/description_zoom_touch_close" /> + +</FrameLayout> ++
+ Once you apply your layout, set up the event handlers that trigger the zoom animation. + The following example adds a {@link android.view.View.OnClickListener} to the {@link + android.widget.ImageButton} to execute the zoom animation when the user + clicks the image button: +
+
+public class ZoomActivity extends FragmentActivity {
+ // Hold a reference to the current animator,
+ // so that it can be canceled mid-way.
+ private Animator mCurrentAnimator;
+
+ // The system "short" animation time duration, in milliseconds. This
+ // duration is ideal for subtle animations or animations that occur
+ // very frequently.
+ private int mShortAnimationDuration;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_zoom);
+
+ // Hook up clicks on the thumbnail views.
+
+ final View thumb1View = findViewById(R.id.thumb_button_1);
+ thumb1View.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ zoomImageFromThumb(thumb1View, R.drawable.image1);
+ }
+ });
+
+ // Retrieve and cache the system's default "short" animation time.
+ mShortAnimationDuration = getResources().getInteger(
+ android.R.integer.config_shortAnimTime);
+ }
+ ...
+}
+
+ + You'll now need to animate from the normal sized view to the zoomed view + when appropriate. In general, you need to animate from the bounds of the normal-sized view to the + bounds of the larger-sized view. The following method shows you how to implement a zoom animation that + zooms from an image thumbnail to an enlarged view by doing the following things: +
+{@link
+ android.view.View#X}, {@link android.view.View#Y}, ({@link
+ android.view.View#SCALE_X}, and {@link android.view.View#SCALE_Y})
+ simultaneously, from the starting bounds to the ending bounds. These four animations are
+ added to an {@link android.animation.AnimatorSet} so that they can be started at the same
+ time.
+
+private void zoomImageFromThumb(final View thumbView, int imageResId) {
+ // If there's an animation in progress, cancel it
+ // immediately and proceed with this one.
+ if (mCurrentAnimator != null) {
+ mCurrentAnimator.cancel();
+ }
+
+ // Load the high-resolution "zoomed-in" image.
+ final ImageView expandedImageView = (ImageView) findViewById(
+ R.id.expanded_image);
+ expandedImageView.setImageResource(imageResId);
+
+ // Calculate the starting and ending bounds for the zoomed-in image.
+ // This step involves lots of math. Yay, math.
+ final Rect startBounds = new Rect();
+ final Rect finalBounds = new Rect();
+ final Point globalOffset = new Point();
+
+ // The start bounds are the global visible rectangle of the thumbnail,
+ // and the final bounds are the global visible rectangle of the container
+ // view. Also set the container view's offset as the origin for the
+ // bounds, since that's the origin for the positioning animation
+ // properties (X, Y).
+ thumbView.getGlobalVisibleRect(startBounds);
+ findViewById(R.id.container)
+ .getGlobalVisibleRect(finalBounds, globalOffset);
+ startBounds.offset(-globalOffset.x, -globalOffset.y);
+ finalBounds.offset(-globalOffset.x, -globalOffset.y);
+
+ // Adjust the start bounds to be the same aspect ratio as the final
+ // bounds using the "center crop" technique. This prevents undesirable
+ // stretching during the animation. Also calculate the start scaling
+ // factor (the end scaling factor is always 1.0).
+ float startScale;
+ if ((float) finalBounds.width() / finalBounds.height()
+ > (float) startBounds.width() / startBounds.height()) {
+ // Extend start bounds horizontally
+ startScale = (float) startBounds.height() / finalBounds.height();
+ float startWidth = startScale * finalBounds.width();
+ float deltaWidth = (startWidth - startBounds.width()) / 2;
+ startBounds.left -= deltaWidth;
+ startBounds.right += deltaWidth;
+ } else {
+ // Extend start bounds vertically
+ startScale = (float) startBounds.width() / finalBounds.width();
+ float startHeight = startScale * finalBounds.height();
+ float deltaHeight = (startHeight - startBounds.height()) / 2;
+ startBounds.top -= deltaHeight;
+ startBounds.bottom += deltaHeight;
+ }
+
+ // Hide the thumbnail and show the zoomed-in view. When the animation
+ // begins, it will position the zoomed-in view in the place of the
+ // thumbnail.
+ thumbView.setAlpha(0f);
+ expandedImageView.setVisibility(View.VISIBLE);
+
+ // Set the pivot point for SCALE_X and SCALE_Y transformations
+ // to the top-left corner of the zoomed-in view (the default
+ // is the center of the view).
+ expandedImageView.setPivotX(0f);
+ expandedImageView.setPivotY(0f);
+
+ // Construct and run the parallel animation of the four translation and
+ // scale properties (X, Y, SCALE_X, and SCALE_Y).
+ AnimatorSet set = new AnimatorSet();
+ set
+ .play(ObjectAnimator.ofFloat(expandedImageView, View.X,
+ startBounds.left, finalBounds.left))
+ .with(ObjectAnimator.ofFloat(expandedImageView, View.Y,
+ startBounds.top, finalBounds.top))
+ .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X,
+ startScale, 1f)).with(ObjectAnimator.ofFloat(expandedImageView,
+ View.SCALE_Y, startScale, 1f));
+ set.setDuration(mShortAnimationDuration);
+ set.setInterpolator(new DecelerateInterpolator());
+ set.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentAnimator = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCurrentAnimator = null;
+ }
+ });
+ set.start();
+ mCurrentAnimator = set;
+
+ // Upon clicking the zoomed-in image, it should zoom back down
+ // to the original bounds and show the thumbnail instead of
+ // the expanded image.
+ final float startScaleFinal = startScale;
+ expandedImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mCurrentAnimator != null) {
+ mCurrentAnimator.cancel();
+ }
+
+ // Animate the four positioning/sizing properties in parallel,
+ // back to their original values.
+ AnimatorSet set = new AnimatorSet();
+ set.play(ObjectAnimator
+ .ofFloat(expandedImageView, View.X, startBounds.left))
+ .with(ObjectAnimator
+ .ofFloat(expandedImageView,
+ View.Y,startBounds.top))
+ .with(ObjectAnimator
+ .ofFloat(expandedImageView,
+ View.SCALE_X, startScaleFinal))
+ .with(ObjectAnimator
+ .ofFloat(expandedImageView,
+ View.SCALE_Y, startScaleFinal));
+ set.setDuration(mShortAnimationDuration);
+ set.setInterpolator(new DecelerateInterpolator());
+ set.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ thumbView.setAlpha(1f);
+ expandedImageView.setVisibility(View.GONE);
+ mCurrentAnimator = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ thumbView.setAlpha(1f);
+ expandedImageView.setVisibility(View.GONE);
+ mCurrentAnimator = null;
+ }
+ });
+ set.start();
+ mCurrentAnimator = set;
+ }
+ });
+}
+
\ No newline at end of file
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 4ad13533bfc28..4a5b0fadc5ddd 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -287,6 +287,34 @@
+
+