diff --git a/samples/training/AnimationsDemo/AndroidManifest.xml b/samples/training/AnimationsDemo/AndroidManifest.xml new file mode 100755 index 000000000..dcb911a12 --- /dev/null +++ b/samples/training/AnimationsDemo/AndroidManifest.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + > + + + + + + diff --git a/samples/training/AnimationsDemo/libs/android-support-v13.jar b/samples/training/AnimationsDemo/libs/android-support-v13.jar new file mode 100644 index 000000000..df7f9fc24 Binary files /dev/null and b/samples/training/AnimationsDemo/libs/android-support-v13.jar differ diff --git a/samples/training/AnimationsDemo/project.properties b/samples/training/AnimationsDemo/project.properties new file mode 100755 index 000000000..895c9ce2e --- /dev/null +++ b/samples/training/AnimationsDemo/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-16 diff --git a/samples/training/AnimationsDemo/res/animator/card_flip_left_in.xml b/samples/training/AnimationsDemo/res/animator/card_flip_left_in.xml new file mode 100644 index 000000000..1a105f148 --- /dev/null +++ b/samples/training/AnimationsDemo/res/animator/card_flip_left_in.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/animator/card_flip_left_out.xml b/samples/training/AnimationsDemo/res/animator/card_flip_left_out.xml new file mode 100644 index 000000000..2de7e9593 --- /dev/null +++ b/samples/training/AnimationsDemo/res/animator/card_flip_left_out.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/animator/card_flip_right_in.xml b/samples/training/AnimationsDemo/res/animator/card_flip_right_in.xml new file mode 100644 index 000000000..8fe41732c --- /dev/null +++ b/samples/training/AnimationsDemo/res/animator/card_flip_right_in.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/animator/card_flip_right_out.xml b/samples/training/AnimationsDemo/res/animator/card_flip_right_out.xml new file mode 100644 index 000000000..099db6c56 --- /dev/null +++ b/samples/training/AnimationsDemo/res/animator/card_flip_right_out.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/drawable-hdpi/ic_action_info.png b/samples/training/AnimationsDemo/res/drawable-hdpi/ic_action_info.png new file mode 100644 index 000000000..6eaf08aec Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-hdpi/ic_action_info.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-hdpi/ic_action_new.png b/samples/training/AnimationsDemo/res/drawable-hdpi/ic_action_new.png new file mode 100644 index 000000000..ad8ada6bd Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-hdpi/ic_action_new.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-hdpi/ic_action_photo.png b/samples/training/AnimationsDemo/res/drawable-hdpi/ic_action_photo.png new file mode 100644 index 000000000..0f650ee25 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-hdpi/ic_action_photo.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-hdpi/ic_launcher.png b/samples/training/AnimationsDemo/res/drawable-hdpi/ic_launcher.png new file mode 100755 index 000000000..9c8635efe Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-hdpi/ic_list_remove.png b/samples/training/AnimationsDemo/res/drawable-hdpi/ic_list_remove.png new file mode 100644 index 000000000..cde36e1fa Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-hdpi/ic_list_remove.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-mdpi/ic_action_info.png b/samples/training/AnimationsDemo/res/drawable-mdpi/ic_action_info.png new file mode 100644 index 000000000..d7b7e6986 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-mdpi/ic_action_info.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-mdpi/ic_action_new.png b/samples/training/AnimationsDemo/res/drawable-mdpi/ic_action_new.png new file mode 100644 index 000000000..4d5d484b3 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-mdpi/ic_action_new.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-mdpi/ic_action_photo.png b/samples/training/AnimationsDemo/res/drawable-mdpi/ic_action_photo.png new file mode 100644 index 000000000..d6235554b Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-mdpi/ic_action_photo.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-mdpi/ic_launcher.png b/samples/training/AnimationsDemo/res/drawable-mdpi/ic_launcher.png new file mode 100755 index 000000000..2497b0986 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-mdpi/ic_list_remove.png b/samples/training/AnimationsDemo/res/drawable-mdpi/ic_list_remove.png new file mode 100644 index 000000000..9f4c3d6a2 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-mdpi/ic_list_remove.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-nodpi/image1.png b/samples/training/AnimationsDemo/res/drawable-nodpi/image1.png new file mode 100644 index 000000000..6606479e7 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-nodpi/image1.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-nodpi/image2.png b/samples/training/AnimationsDemo/res/drawable-nodpi/image2.png new file mode 100644 index 000000000..ab3a52fff Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-nodpi/image2.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-nodpi/thumb1.png b/samples/training/AnimationsDemo/res/drawable-nodpi/thumb1.png new file mode 100644 index 000000000..245d5ad20 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-nodpi/thumb1.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-nodpi/thumb2.png b/samples/training/AnimationsDemo/res/drawable-nodpi/thumb2.png new file mode 100644 index 000000000..573397c10 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-nodpi/thumb2.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_action_info.png b/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_action_info.png new file mode 100644 index 000000000..4ee903f07 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_action_info.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_action_new.png b/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_action_new.png new file mode 100644 index 000000000..23b9a1c18 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_action_new.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_action_photo.png b/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_action_photo.png new file mode 100644 index 000000000..3f93e4f65 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_action_photo.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_launcher.png b/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_launcher.png new file mode 100755 index 000000000..017ced125 Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_list_remove.png b/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_list_remove.png new file mode 100644 index 000000000..ca7d159fd Binary files /dev/null and b/samples/training/AnimationsDemo/res/drawable-xhdpi/ic_list_remove.png differ diff --git a/samples/training/AnimationsDemo/res/layout/activity_card_flip.xml b/samples/training/AnimationsDemo/res/layout/activity_card_flip.xml new file mode 100755 index 000000000..30d659fe1 --- /dev/null +++ b/samples/training/AnimationsDemo/res/layout/activity_card_flip.xml @@ -0,0 +1,20 @@ + + + diff --git a/samples/training/AnimationsDemo/res/layout/activity_crossfade.xml b/samples/training/AnimationsDemo/res/layout/activity_crossfade.xml new file mode 100644 index 000000000..819e03daf --- /dev/null +++ b/samples/training/AnimationsDemo/res/layout/activity_crossfade.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/layout/activity_layout_changes.xml b/samples/training/AnimationsDemo/res/layout/activity_layout_changes.xml new file mode 100644 index 000000000..1758cd648 --- /dev/null +++ b/samples/training/AnimationsDemo/res/layout/activity_layout_changes.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/layout/activity_main.xml b/samples/training/AnimationsDemo/res/layout/activity_main.xml new file mode 100755 index 000000000..b33be1fe8 --- /dev/null +++ b/samples/training/AnimationsDemo/res/layout/activity_main.xml @@ -0,0 +1,22 @@ + + + diff --git a/samples/training/AnimationsDemo/res/layout/activity_screen_slide.xml b/samples/training/AnimationsDemo/res/layout/activity_screen_slide.xml new file mode 100755 index 000000000..339b592d5 --- /dev/null +++ b/samples/training/AnimationsDemo/res/layout/activity_screen_slide.xml @@ -0,0 +1,20 @@ + + + diff --git a/samples/training/AnimationsDemo/res/layout/activity_zoom.xml b/samples/training/AnimationsDemo/res/layout/activity_zoom.xml new file mode 100644 index 000000000..712dc6143 --- /dev/null +++ b/samples/training/AnimationsDemo/res/layout/activity_zoom.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/layout/excerpt_content.xml b/samples/training/AnimationsDemo/res/layout/excerpt_content.xml new file mode 100644 index 000000000..0237f2bad --- /dev/null +++ b/samples/training/AnimationsDemo/res/layout/excerpt_content.xml @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/layout/fragment_card_back.xml b/samples/training/AnimationsDemo/res/layout/fragment_card_back.xml new file mode 100644 index 000000000..39bd784cf --- /dev/null +++ b/samples/training/AnimationsDemo/res/layout/fragment_card_back.xml @@ -0,0 +1,42 @@ + + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/layout/fragment_card_front.xml b/samples/training/AnimationsDemo/res/layout/fragment_card_front.xml new file mode 100644 index 000000000..e887c14a8 --- /dev/null +++ b/samples/training/AnimationsDemo/res/layout/fragment_card_front.xml @@ -0,0 +1,22 @@ + + + diff --git a/samples/training/AnimationsDemo/res/layout/fragment_screen_slide_page.xml b/samples/training/AnimationsDemo/res/layout/fragment_screen_slide_page.xml new file mode 100644 index 000000000..8aa472b87 --- /dev/null +++ b/samples/training/AnimationsDemo/res/layout/fragment_screen_slide_page.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/layout/list_item_example.xml b/samples/training/AnimationsDemo/res/layout/list_item_example.xml new file mode 100644 index 000000000..3822932e3 --- /dev/null +++ b/samples/training/AnimationsDemo/res/layout/list_item_example.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/menu/activity_crossfade.xml b/samples/training/AnimationsDemo/res/menu/activity_crossfade.xml new file mode 100644 index 000000000..bb21a3838 --- /dev/null +++ b/samples/training/AnimationsDemo/res/menu/activity_crossfade.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/menu/activity_layout_changes.xml b/samples/training/AnimationsDemo/res/menu/activity_layout_changes.xml new file mode 100644 index 000000000..e919ae0df --- /dev/null +++ b/samples/training/AnimationsDemo/res/menu/activity_layout_changes.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/menu/activity_screen_slide.xml b/samples/training/AnimationsDemo/res/menu/activity_screen_slide.xml new file mode 100644 index 000000000..7e31b34e8 --- /dev/null +++ b/samples/training/AnimationsDemo/res/menu/activity_screen_slide.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/values/ids.xml b/samples/training/AnimationsDemo/res/values/ids.xml new file mode 100644 index 000000000..bbc0ecedb --- /dev/null +++ b/samples/training/AnimationsDemo/res/values/ids.xml @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/samples/training/AnimationsDemo/res/values/integers.xml b/samples/training/AnimationsDemo/res/values/integers.xml new file mode 100644 index 000000000..bf09638eb --- /dev/null +++ b/samples/training/AnimationsDemo/res/values/integers.xml @@ -0,0 +1,20 @@ + + + + 300 + 150 + diff --git a/samples/training/AnimationsDemo/res/values/strings.xml b/samples/training/AnimationsDemo/res/values/strings.xml new file mode 100755 index 000000000..7d41cd8b7 --- /dev/null +++ b/samples/training/AnimationsDemo/res/values/strings.xml @@ -0,0 +1,53 @@ + + + + + + Animations Demo + Simple Crossfade + Card Flip + Screen Slide + Zoom + Layout Changes + Step %1$d: Lorem + Ipsum + + + Toggle indicator + Add item + Remove item + Previous + Next + Finish + Photo info + View photo + + + Add an item above to get started. + Touch a photo to expand it. + + + Image 1 + Image 2 + Expanded image (touch to close) + + + Angry Red Android + + + + diff --git a/samples/training/AnimationsDemo/res/values/styles.xml b/samples/training/AnimationsDemo/res/values/styles.xml new file mode 100644 index 000000000..56d30db44 --- /dev/null +++ b/samples/training/AnimationsDemo/res/values/styles.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/CardFlipActivity.java b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/CardFlipActivity.java new file mode 100644 index 000000000..f091d37dc --- /dev/null +++ b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/CardFlipActivity.java @@ -0,0 +1,191 @@ +/* + * Copyright 2012 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.animationsdemo; + +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.NavUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +/** + * Demonstrates a "card-flip" animation using custom fragment transactions ({@link + * android.app.FragmentTransaction#setCustomAnimations(int, int)}). + * + *

This sample shows an "info" action bar button that shows the back of a "card", rotating the + * front of the card out and the back of the card in. The reverse animation is played when the user + * presses the system Back button or the "photo" action bar button.

+ */ +public class CardFlipActivity extends Activity + implements FragmentManager.OnBackStackChangedListener { + /** + * A handler object, used for deferring UI operations. + */ + private Handler mHandler = new Handler(); + + /** + * Whether or not we're showing the back of the card (otherwise showing the front). + */ + private boolean mShowingBack = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_card_flip); + + if (savedInstanceState == null) { + // If there is no saved instance state, add a fragment representing the + // front of the card to this activity. If there is saved instance state, + // this fragment will have already been added to the activity. + getFragmentManager() + .beginTransaction() + .add(R.id.container, new CardFrontFragment()) + .commit(); + } else { + mShowingBack = (getFragmentManager().getBackStackEntryCount() > 0); + } + + // Monitor back stack changes to ensure the action bar shows the appropriate + // button (either "photo" or "info"). + getFragmentManager().addOnBackStackChangedListener(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + // Add either a "photo" or "finish" button to the action bar, depending on which page + // is currently selected. + MenuItem item = menu.add(Menu.NONE, R.id.action_flip, Menu.NONE, + mShowingBack + ? R.string.action_photo + : R.string.action_info); + item.setIcon(mShowingBack + ? R.drawable.ic_action_photo + : R.drawable.ic_action_info); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + // Navigate "up" the demo structure to the launchpad activity. + // See http://developer.android.com/design/patterns/navigation.html for more. + NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class)); + return true; + + case R.id.action_flip: + flipCard(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + 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(); + + // Defer an invalidation of the options menu (on modern devices, the action bar). This + // can't be done immediately because the transaction may not yet be committed. Commits + // are asynchronous in that they are posted to the main thread's message loop. + mHandler.post(new Runnable() { + @Override + public void run() { + invalidateOptionsMenu(); + } + }); + } + + @Override + public void onBackStackChanged() { + mShowingBack = (getFragmentManager().getBackStackEntryCount() > 0); + + // When the back stack changes, invalidate the options menu (action bar). + invalidateOptionsMenu(); + } + + /** + * A fragment representing the front of the card. + */ + public static class CardFrontFragment extends Fragment { + public CardFrontFragment() { + } + + @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 static class CardBackFragment extends Fragment { + public CardBackFragment() { + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_card_back, container, false); + } + } +} diff --git a/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/CrossfadeActivity.java b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/CrossfadeActivity.java new file mode 100644 index 000000000..b0f2509f8 --- /dev/null +++ b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/CrossfadeActivity.java @@ -0,0 +1,137 @@ +/* + * Copyright 2012 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.animationsdemo; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.NavUtils; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +/** + * This sample demonstrates cross-fading between two overlapping views. + * + *

In this sample, the two overlapping views are a loading indicator and some text content. The + * active view is toggled by touching the toggle button in the action bar. In real-world + * applications, this toggle would occur as soon as content was available. Note that if content is + * immediately available, a loading spinner shouldn't be presented and there should be no + * animation.

+ */ +public class CrossfadeActivity extends Activity { + /** + * The flag indicating whether content is loaded (text is shown) or not (loading spinner is + * shown). + */ + private boolean mContentLoaded; + + /** + * The view (or view group) containing the content. This is one of two overlapping views. + */ + private View mContentView; + + /** + * The view containing the loading indicator. This is the other of two overlapping views. + */ + private View mLoadingView; + + /** + * 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_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); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.activity_crossfade, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + // Navigate "up" the demo structure to the launchpad activity. + // See http://developer.android.com/design/patterns/navigation.html for more. + NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class)); + return true; + + case R.id.action_toggle: + // Toggle whether content is loaded. + mContentLoaded = !mContentLoaded; + showContentOrLoadingIndicator(mContentLoaded); + return true; + } + + return super.onOptionsItemSelected(item); + } + + /** + * Cross-fades between {@link #mContentView} and {@link #mLoadingView}. + */ + private void showContentOrLoadingIndicator(boolean contentLoaded) { + // Decide which view to hide and which to show. + final View showView = contentLoaded ? mContentView : mLoadingView; + final View hideView = contentLoaded ? mLoadingView : mContentView; + + // Set the "show" view to 0% opacity but visible, so that it is visible + // (but fully transparent) during the animation. + showView.setAlpha(0f); + showView.setVisibility(View.VISIBLE); + + // Animate the "show" view to 100% opacity, and clear any animation listener set on + // the view. Remember that listeners are not limited to the specific animation + // describes in the chained method calls. Listeners are set on the + // ViewPropertyAnimator object for the view, which persists across several + // animations. + showView.animate() + .alpha(1f) + .setDuration(mShortAnimationDuration) + .setListener(null); + + // Animate the "hide" 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.) + hideView.animate() + .alpha(0f) + .setDuration(mShortAnimationDuration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + hideView.setVisibility(View.GONE); + } + }); + } +} diff --git a/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/LayoutChangesActivity.java b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/LayoutChangesActivity.java new file mode 100644 index 000000000..42e3475a1 --- /dev/null +++ b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/LayoutChangesActivity.java @@ -0,0 +1,117 @@ +/* + * Copyright 2012 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.animationsdemo; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.NavUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +/** + * This sample demonstrates how to use system-provided, automatic layout transitions. Layout + * transitions are animations that occur when views are added to, removed from, or changed within + * a {@link ViewGroup}. + * + *

In this sample, the user can add rows to and remove rows from a vertical + * {@link android.widget.LinearLayout}.

+ */ +public class LayoutChangesActivity extends Activity { + /** + * The container view which has layout change animations turned on. In this sample, this view + * is a {@link android.widget.LinearLayout}. + */ + private ViewGroup mContainerView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_layout_changes); + + mContainerView = (ViewGroup) findViewById(R.id.container); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.activity_layout_changes, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + // Navigate "up" the demo structure to the launchpad activity. + // See http://developer.android.com/design/patterns/navigation.html for more. + NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class)); + return true; + + case R.id.action_add_item: + // Hide the "empty" view since there is now at least one item in the list. + findViewById(android.R.id.empty).setVisibility(View.GONE); + addItem(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + private void addItem() { + // Instantiate a new "row" view. + final ViewGroup newView = (ViewGroup) LayoutInflater.from(this).inflate( + R.layout.list_item_example, mContainerView, false); + + // Set the text in the new row to a random country. + ((TextView) newView.findViewById(android.R.id.text1)).setText( + COUNTRIES[(int) (Math.random() * COUNTRIES.length)]); + + // Set a click listener for the "X" button in the row that will remove the row. + newView.findViewById(R.id.delete_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // Remove the row from its parent (the container view). + // Because mContainerView has android:animateLayoutChanges set to true, + // this removal is automatically animated. + mContainerView.removeView(newView); + + // If there are no rows remaining, show the empty view. + if (mContainerView.getChildCount() == 0) { + findViewById(android.R.id.empty).setVisibility(View.VISIBLE); + } + } + }); + + // Because mContainerView has android:animateLayoutChanges set to true, + // adding this view is automatically animated. + mContainerView.addView(newView, 0); + } + + /** + * A static list of country names. + */ + private static final String[] COUNTRIES = new String[]{ + "Belgium", "France", "Italy", "Germany", "Spain", + "Austria", "Russia", "Poland", "Croatia", "Greece", + "Ukraine", + }; +} diff --git a/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/MainActivity.java b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/MainActivity.java new file mode 100755 index 000000000..5d4802a05 --- /dev/null +++ b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/MainActivity.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012 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.animationsdemo; + +import android.app.Activity; +import android.app.ListActivity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +/** + * The launchpad activity for this sample project. This activity launches other activities that + * demonstrate implementations of common animations. + */ +public class MainActivity extends ListActivity { + /** + * This class describes an individual sample (the sample title, and the activity class that + * demonstrates this sample). + */ + private class Sample { + private CharSequence title; + private Class activityClass; + + public Sample(int titleResId, Class activityClass) { + this.activityClass = activityClass; + this.title = getResources().getString(titleResId); + } + + @Override + public String toString() { + return title.toString(); + } + } + + /** + * The collection of all samples in the app. This gets instantiated in {@link + * #onCreate(android.os.Bundle)} because the {@link Sample} constructor needs access to {@link + * android.content.res.Resources}. + */ + private static Sample[] mSamples; + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // Instantiate the list of samples. + mSamples = new Sample[]{ + new Sample(R.string.title_crossfade, CrossfadeActivity.class), + new Sample(R.string.title_card_flip, CardFlipActivity.class), + new Sample(R.string.title_screen_slide, ScreenSlideActivity.class), + new Sample(R.string.title_zoom, ZoomActivity.class), + new Sample(R.string.title_layout_changes, LayoutChangesActivity.class), + }; + + setListAdapter(new ArrayAdapter(this, + android.R.layout.simple_list_item_1, + android.R.id.text1, + mSamples)); + } + + @Override + protected void onListItemClick(ListView listView, View view, int position, long id) { + // Launch the sample associated with this list position. + startActivity(new Intent(MainActivity.this, mSamples[position].activityClass)); + } +} diff --git a/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/ScreenSlideActivity.java b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/ScreenSlideActivity.java new file mode 100644 index 000000000..4652a0ed8 --- /dev/null +++ b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/ScreenSlideActivity.java @@ -0,0 +1,141 @@ +/* + * Copyright 2012 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.animationsdemo; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.Intent; +import android.os.Bundle; +import android.support.v13.app.FragmentStatePagerAdapter; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.NavUtils; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.Menu; +import android.view.MenuItem; + +/** + * Demonstrates a "screen-slide" animation using a {@link ViewPager}. Because {@link ViewPager} + * automatically plays such an animation when calling {@link ViewPager#setCurrentItem(int)}, there + * isn't any animation-specific code in this sample. + * + *

This sample shows a "next" button that advances the user to the next step in a wizard, + * animating the current screen out (to the left) and the next screen in (from the right). The + * reverse animation is played when the user presses the "previous" button.

+ * + * @see ScreenSlidePageFragment + */ +public class ScreenSlideActivity 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); + + // Instantiate a ViewPager and a PagerAdapter. + mPager = (ViewPager) findViewById(R.id.pager); + mPagerAdapter = new ScreenSlidePagerAdapter(getFragmentManager()); + mPager.setAdapter(mPagerAdapter); + mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + // When changing pages, reset the action bar actions since they are dependent + // on which page is currently active. An alternative approach is to have each + // fragment expose actions itself (rather than the activity exposing actions), + // but for simplicity, the activity provides the actions in this sample. + invalidateOptionsMenu(); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.activity_screen_slide, menu); + + menu.findItem(R.id.action_previous).setEnabled(mPager.getCurrentItem() > 0); + + // Add either a "next" or "finish" button to the action bar, depending on which page + // is currently selected. + MenuItem item = menu.add(Menu.NONE, R.id.action_next, Menu.NONE, + (mPager.getCurrentItem() == mPagerAdapter.getCount() - 1) + ? R.string.action_finish + : R.string.action_next); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + // Navigate "up" the demo structure to the launchpad activity. + // See http://developer.android.com/design/patterns/navigation.html for more. + NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class)); + return true; + + case R.id.action_previous: + // Go to the previous step in the wizard. If there is no previous step, + // setCurrentItem will do nothing. + mPager.setCurrentItem(mPager.getCurrentItem() - 1); + return true; + + case R.id.action_next: + // Advance to the next step in the wizard. If there is no next step, setCurrentItem + // will do nothing. + mPager.setCurrentItem(mPager.getCurrentItem() + 1); + return true; + } + + return super.onOptionsItemSelected(item); + } + + /** + * A simple pager adapter that represents 5 {@link ScreenSlidePageFragment} objects, in + * sequence. + */ + private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter { + public ScreenSlidePagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + return ScreenSlidePageFragment.create(position); + } + + @Override + public int getCount() { + return NUM_PAGES; + } + } +} diff --git a/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/ScreenSlidePageFragment.java b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/ScreenSlidePageFragment.java new file mode 100644 index 000000000..887c926bd --- /dev/null +++ b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/ScreenSlidePageFragment.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012 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.animationsdemo; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +/** + * A fragment representing a single step in a wizard. The fragment shows a dummy title indicating + * the page number, along with some dummy text. + * + *

This class is used by the {@link CardFlipActivity} and {@link + * ScreenSlideActivity} samples.

+ */ +public class ScreenSlidePageFragment extends Fragment { + /** + * The argument key for the page number this fragment represents. + */ + public static final String ARG_PAGE = "page"; + + /** + * The fragment's page number, which is set to the argument value for {@link #ARG_PAGE}. + */ + private int mPageNumber; + + /** + * Factory method for this fragment class. Constructs a new fragment for the given page number. + */ + public static ScreenSlidePageFragment create(int pageNumber) { + ScreenSlidePageFragment fragment = new ScreenSlidePageFragment(); + Bundle args = new Bundle(); + args.putInt(ARG_PAGE, pageNumber); + fragment.setArguments(args); + return fragment; + } + + public ScreenSlidePageFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mPageNumber = getArguments().getInt(ARG_PAGE); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout containing a title and body text. + ViewGroup rootView = (ViewGroup) inflater + .inflate(R.layout.fragment_screen_slide_page, container, false); + + // Set the title view to show the page number. + ((TextView) rootView.findViewById(android.R.id.text1)).setText( + getString(R.string.title_template_step, mPageNumber + 1)); + + return rootView; + } + + /** + * Returns the page number represented by this fragment object. + */ + public int getPageNumber() { + return mPageNumber; + } +} diff --git a/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/TouchHighlightImageButton.java b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/TouchHighlightImageButton.java new file mode 100644 index 000000000..8fc3e0193 --- /dev/null +++ b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/TouchHighlightImageButton.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012 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.animationsdemo; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.ImageButton; + +/** + * An image button that uses a blue highlight (@link android.R.attr.selectableItemBackground} to + * indicate pressed and focused states. + */ +public class TouchHighlightImageButton extends ImageButton { + /** + * The highlight drawable. This generally a {@link android.graphics.drawable.StateListDrawable} + * that's transparent in the default state, and contains a semi-transparent overlay + * for the focused and pressed states. + */ + private Drawable mForegroundDrawable; + + /** + * The cached bounds of the view. + */ + private Rect mCachedBounds = new Rect(); + + public TouchHighlightImageButton(Context context) { + super(context); + init(); + } + + public TouchHighlightImageButton(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public TouchHighlightImageButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * General view initialization used common to all constructors of the view. + */ + private void init() { + // Reset default ImageButton background and padding. + setBackgroundColor(0); + setPadding(0, 0, 0, 0); + + // Retrieve the drawable resource assigned to the android.R.attr.selectableItemBackground + // theme attribute from the current theme. + TypedArray a = getContext() + .obtainStyledAttributes(new int[]{android.R.attr.selectableItemBackground}); + mForegroundDrawable = a.getDrawable(0); + mForegroundDrawable.setCallback(this); + a.recycle(); + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + // Update the state of the highlight drawable to match + // the state of the button. + if (mForegroundDrawable.isStateful()) { + mForegroundDrawable.setState(getDrawableState()); + } + + // Trigger a redraw. + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + // First draw the image. + super.onDraw(canvas); + + // Then draw the highlight on top of it. If the button is neither focused + // nor pressed, the drawable will be transparent, so just the image + // will be drawn. + mForegroundDrawable.setBounds(mCachedBounds); + mForegroundDrawable.draw(canvas); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + // Cache the view bounds. + mCachedBounds.set(0, 0, w, h); + } +} diff --git a/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/ZoomActivity.java b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/ZoomActivity.java new file mode 100644 index 000000000..70dcfcbd5 --- /dev/null +++ b/samples/training/AnimationsDemo/src/com/example/android/animationsdemo/ZoomActivity.java @@ -0,0 +1,233 @@ +/* + * Copyright 2012 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.animationsdemo; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Intent; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.NavUtils; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; + +/** + * A sample showing how to zoom an image thumbnail to full-screen, by animating the bounds of the + * zoomed image from the thumbnail bounds to the screen bounds. + * + *

In this sample, the user can touch one of two images. Touching an image zooms it in, covering + * the entire activity content area. Touching the zoomed-in image hides it.

+ */ +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); + } + }); + + final View thumb2View = findViewById(R.id.thumb_button_2); + thumb2View.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + zoomImageFromThumb(thumb2View, R.drawable.image2); + } + }); + + // Retrieve and cache the system's default "short" animation time. + mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + // Navigate "up" the demo structure to the launchpad activity. + // See http://developer.android.com/design/patterns/navigation.html for more. + NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class)); + return true; + } + + return super.onOptionsItemSelected(item); + } + + /** + * "Zooms" in a thumbnail view by assigning the high resolution image to a hidden "zoomed-in" + * image view and animating its bounds to fit the entire activity content area. More + * specifically: + * + *
    + *
  1. Assign the high-res image to the hidden "zoomed-in" (expanded) image view.
  2. + *
  3. Calculate the starting and ending bounds for the expanded view.
  4. + *
  5. Animate each of four positioning/sizing properties (X, Y, SCALE_X, SCALE_Y) + * simultaneously, from the starting bounds to the ending bounds.
  6. + *
  7. Zoom back out by running the reverse animation on click.
  8. + *
+ * + * @param thumbView The thumbnail view to zoom in. + * @param imageResId The high-resolution version of the image represented by the thumbnail. + */ + 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; + } + }); + } +}