AnimationsDemo sample code for Animations training class.

Change-Id: I0fbf3c2f66ee4321adcc0c0bf3fe7f4b4e67584e
This commit is contained in:
Roman Nurik
2012-10-15 13:09:16 -07:00
parent 6a705429d4
commit 01be9fd4c0
52 changed files with 2020 additions and 0 deletions

View File

@@ -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)}).
*
* <p>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.</p>
*/
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);
}
}
}

View File

@@ -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.
*
* <p>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.</p>
*/
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);
}
});
}
}

View File

@@ -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}.
*
* <p>In this sample, the user can add rows to and remove rows from a vertical
* {@link android.widget.LinearLayout}.</p>
*/
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",
};
}

View File

@@ -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<? extends Activity> activityClass;
public Sample(int titleResId, Class<? extends Activity> 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<Sample>(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));
}
}

View File

@@ -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.
*
* <p>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.</p>
*
* @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;
}
}
}

View File

@@ -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.
*
* <p>This class is used by the {@link CardFlipActivity} and {@link
* ScreenSlideActivity} samples.</p>
*/
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;
}
}

View File

@@ -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);
}
}

View File

@@ -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.
*
* <p>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.</p>
*/
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:
*
* <ol>
* <li>Assign the high-res image to the hidden "zoomed-in" (expanded) image view.</li>
* <li>Calculate the starting and ending bounds for the expanded view.</li>
* <li>Animate each of four positioning/sizing properties (X, Y, SCALE_X, SCALE_Y)
* simultaneously, from the starting bounds to the ending bounds.</li>
* <li>Zoom back out by running the reverse animation on click.</li>
* </ol>
*
* @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;
}
});
}
}