Reputation: 30611
The official Zooming a View tutorial uses an AnimatorSet
to zoom into a View
. It creates the illusion of downward movement as the view expands. Later, the AnimatorSet
is simply replayed backwards to create the illusion of zoom-out.
What I need to implement is the exact reverse of this. I need to start with an expanded view and shrink it into a smaller view with an upward movement:
It doesn't seem that I can use the reversal code in the example. That example assumes that you first zoom into the view and expand it, and then shrink it back into the original thumbnail icon.
Here's what I've tried so far. My XML layout is
<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="match_parent"
android:orientation="vertical"
android:background="#1999da">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal"
android:layout_gravity="center"
android:gravity="center">
<!-- The final shrunk image -->
<ImageView
android:id="@+id/thumb_button_1"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginRight="1dp"
android:visibility="invisible"/>
</LinearLayout>
</LinearLayout>
<!-- The initial expanded image that needs to be shrunk -->
<ImageView
android:id="@+id/expanded_image"
android:layout_width="wrap_content"
android:layout_height="125dp"
android:layout_gravity="center"
android:src="@drawable/title_logo_expanded"
android:scaleType="centerCrop"/>
</FrameLayout>
And here is the method that performs the zoom-out operation. I've basically tried to reverse the procedure in the tutorial:
private void zoomImageFromThumbReverse(final View expandedImageView, int imageResId, final int duration) {
// If there's an animation in progress, cancel it immediately and proceed with this one.
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
}
// Load the low-resolution "zoomed-out" image.
final ImageView thumbView = (ImageView) findViewById(R.id.thumb_button_1);
thumbView.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 container view (i.e. the FrameLayout), and the
// final bounds are the global visible rectangle of the thumbnail. 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).
findViewById(R.id.container).getGlobalVisibleRect(startBounds, globalOffset);
thumbView.getGlobalVisibleRect(finalBounds);
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 expanded-image and show the zoomed-out, thumbnail view. When the animation begins,
// it will position the zoomed-in view in the place of the thumbnail.
expandedImageView.setAlpha(0f);
thumbView.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).
thumbView.setPivotX(0f);
thumbView.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(thumbView, View.X, startBounds.left,
finalBounds.left))
.with(ObjectAnimator.ofFloat(thumbView, View.Y, startBounds.top,
finalBounds.top))
.with(ObjectAnimator.ofFloat(thumbView, View.SCALE_X, startScale, 1f))
.with(ObjectAnimator.ofFloat(thumbView, View.SCALE_Y, startScale, 1f));
//set.setDuration(mShortAnimationDuration);
set.setDuration(duration);
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-out image, it should zoom back down to the original bounds
// and show the thumbnail instead of the expanded image.
final float startScaleFinal = startScale;
thumbView.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(thumbView, View.X, startBounds.left))
.with(ObjectAnimator.ofFloat(thumbView, View.Y, startBounds.top))
.with(ObjectAnimator
.ofFloat(thumbView, View.SCALE_X, startScaleFinal))
.with(ObjectAnimator
.ofFloat(thumbView, View.SCALE_Y, startScaleFinal));
//set.setDuration(mShortAnimationDuration);
set.setDuration(duration);
set.setInterpolator(new DecelerateInterpolator());
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
expandedImageView.setAlpha(1f);
thumbView.setVisibility(View.GONE);
mCurrentAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
expandedImageView.setAlpha(1f);
thumbView.setVisibility(View.GONE);
mCurrentAnimator = null;
}
});
set.start();
mCurrentAnimator = set;
}
});
}
I am invoking this method in onCreate()
as follows:
final View expandedImageView = findViewById(R.id.expanded_image);
new Handler().postDelayed(new Runnable(){
public void run() {
zoomImageFromThumbReverse(expandedImageView, R.drawable.title_logo_min, 1000);
}}, 1000);
Well, that's it, folks. It isn't working. I am at a loss as to why. The demo example works perfectly, so why doesn't this work ? Take a gander and tell me if I'm crazy.
Can anyone identify the error ? Or point me in the right direction ? All help will be greatly appreciated.
Upvotes: 17
Views: 4302
Reputation: 30611
This is the solution I have ultimately used:
private void applyAnimation(final View startView, final View finishView, long duration) {
float scalingFactor = ((float)finishView.getHeight())/((float)startView.getHeight());
ScaleAnimation scaleAnimation = new ScaleAnimation(1f, scalingFactor,
1f, scalingFactor,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(duration);
scaleAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
Display display = getWindowManager().getDefaultDisplay();
int H;
if(Build.VERSION.SDK_INT >= 13){
Point size = new Point();
display.getSize(size);
H = size.y;
}
else{
H = display.getHeight();
}
float h = ((float)finishView.getHeight());
float verticalDisplacement = (-(H/2)+(3*h/4));
TranslateAnimation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, verticalDisplacement);
translateAnimation.setDuration(duration);
translateAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
AnimationSet animationSet = new AnimationSet(false);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(translateAnimation);
animationSet.setFillAfter(false);
startView.startAnimation(animationSet);
}
The key factor here is the value of toYDelta
in the TranslateAnimation
parameter:
toYDelta = (-(H/2)+(3*h/4));
Understanding why this works is the main thing. The rest is mostly simple.
Upvotes: 6
Reputation: 438
Ok, I think you want a zoom out with upward movement from your images and description. I cant understand your code, it seems too much complex to me (I am a noob). Now I have done what you want using the following code. At first I declare a relative layout with an image view, this relative layout will be the container. I set initial width height, but we will change it later from code.
<RelativeLayout
android:id="@+id/container"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="400dp"
android:layout_height="200dp"
android:layout_centerHorizontal="true"
android:scaleType="fitXY"
android:src="@drawable/my_image"/>
</RelativeLayout>
Now In Activity I set a listener for layout change so that I can get the actual size of the container. Then set the the layout for ImageView.
public class MainActivity extends Activity {
ImageView im;
RelativeLayout container;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Logger.init().hideThreadInfo().setMethodCount(0);
setContentView(R.layout.activity_main);
im = (ImageView) findViewById(R.id.imageView);
container = (RelativeLayout) findViewById(R.id.container);
container.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
setInitialPos();
container.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
int width;
int height;
int topMargin;
private void setInitialPos() {
Logger.e("container: " + container.getWidth() + " x " + container.getHeight());
width = container.getWidth();
height = 400;
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) im.getLayoutParams();
layoutParams.width = width;
layoutParams.height = height;
topMargin = (container.getHeight() - height) / 2;
layoutParams.topMargin = topMargin;
im.setLayoutParams(layoutParams);
startAnimation();
}
We have to animate three things here, width, height and topMargin (for positioning). So, I declare three variable for initial position of animator and I calculate them on initial layoutsetup. Now we need to animate these three variables simultaneously which is easy.
private void startAnimation() {
AnimatorSet animator = new AnimatorSet();
Animator widthAnimator = ObjectAnimator.ofInt(this, "width", width, 200);
widthAnimator.setInterpolator(new LinearInterpolator());
Animator heightAnimator = ObjectAnimator.ofInt(this, "height", height, 100);
heightAnimator.setInterpolator(new LinearInterpolator());
Animator marginAnimator = ObjectAnimator.ofInt(this, "topMargin", topMargin, 0);
marginAnimator.setInterpolator(new LinearInterpolator());
animator.playTogether(widthAnimator, heightAnimator, marginAnimator);
animator.setDuration(3000);
animator.start();
}
public void setWidth(int w) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) im.getLayoutParams();
layoutParams.width = w;
im.setLayoutParams(layoutParams);
}
public void setHeight(int h) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) im.getLayoutParams();
layoutParams.height = h;
im.setLayoutParams(layoutParams);
}
public void setTopMargin(int m) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) im.getLayoutParams();
layoutParams.topMargin = m;
im.setLayoutParams(layoutParams);
}
}
Upvotes: 2