Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to animate ImageView from center-crop to fill the screen and vice versa (facebook style)?

Background

Facebook app has a nice transition animation between a small image on a post, and an enlarged mode of it that the user can also zoom to it.

As I see it, the animation not only enlarges and moves the imageView according to its previous location and size, but also reveals content instead of stretching the content of the imageView.

This can be seen using the next sketch i've made:

enter image description here

The question

How did they do it? did they really have 2 views animating to reveal the content?

How did they make it so fluid as if it's a single view?

the only tutorial i've seen (link here) of an image that is enlarged to full screen doesn't show well when the thumbnail is set to be center-crop.

Not only that, but it works even on low API of Android.

does anybody know of a library that has a similar ability?


EDIT: I've found a way and posted an answer, but it's based on changing the layoutParams , and i think it's not efficient and recommended.

I've tried using the normal animations and other animation tricks, but for now that's the only thing that worked for me.

If anyone know what to do in order to make it work in a better way, please write it down.

like image 283
android developer Avatar asked Oct 22 '13 13:10

android developer


3 Answers

Ok, i've found a possible way to do it. i've made the layoutParams as variables that keep changing using the ObjectAnimator of the nineOldAndroids library. i think it's not the best way to achieve it since it causes a lot of onDraw and onLayout, but if the container has only a few views and doesn't change its size, maybe it's ok.

the assumption is that the imageView that i animate will take the exact needed size in the end, and that (currently) both the thumbnail and the animated imageView have the same container (but it should be easy to change it.

as i've tested, it is also possible to add zoom features by extending the TouchImageView class . you just set the scale type in the beginning to center-crop, and when the animation ends you set it back to matrix, and if you want, you can set the layoutParams to fill the entire container (and set the margin to 0,0).

i also wonder how come the AnimatorSet didn't work for me, so i will show here something that works, hoping someone could tell me what i should do.

here's the code:

MainActivity.java

public class MainActivity extends Activity {
    private static final int IMAGE_RES_ID = R.drawable.test_image_res_id;
    private static final int ANIM_DURATION = 5000;
    private final Handler mHandler = new Handler();
    private ImageView mThumbnailImageView;
    private CustomImageView mFullImageView;
    private Point mFitSizeBitmap;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mFullImageView = (CustomImageView) findViewById(R.id.fullImageView);
        mThumbnailImageView = (ImageView) findViewById(R.id.thumbnailImageView);
        mHandler.postDelayed(new Runnable() {

            @Override
            public void run() {
                prepareAndStartAnimation();
            }

        }, 2000);
    }

    private void prepareAndStartAnimation() {
        final int thumbX = mThumbnailImageView.getLeft(), thumbY = mThumbnailImageView.getTop();
        final int thumbWidth = mThumbnailImageView.getWidth(), thumbHeight = mThumbnailImageView.getHeight();
        final View container = (View) mFullImageView.getParent();
        final int containerWidth = container.getWidth(), containerHeight = container.getHeight();
        final Options bitmapOptions = getBitmapOptions(getResources(), IMAGE_RES_ID);
        mFitSizeBitmap = getFitSize(bitmapOptions.outWidth, bitmapOptions.outHeight, containerWidth, containerHeight);

        mThumbnailImageView.setVisibility(View.GONE);
        mFullImageView.setVisibility(View.VISIBLE);
        mFullImageView.setContentWidth(thumbWidth);
        mFullImageView.setContentHeight(thumbHeight);
        mFullImageView.setContentX(thumbX);
        mFullImageView.setContentY(thumbY);
        runEnterAnimation(containerWidth, containerHeight);
    }

    private Point getFitSize(final int width, final int height, final int containerWidth, final int containerHeight) {
        int resultHeight, resultWidth;
        resultHeight = height * containerWidth / width;
        if (resultHeight <= containerHeight) {
            resultWidth = containerWidth;
        } else {
            resultWidth = width * containerHeight / height;
            resultHeight = containerHeight;
        }
        return new Point(resultWidth, resultHeight);
    }

    public void runEnterAnimation(final int containerWidth, final int containerHeight) {
        final ObjectAnimator widthAnim = ObjectAnimator.ofInt(mFullImageView, "contentWidth", mFitSizeBitmap.x)
                .setDuration(ANIM_DURATION);
        final ObjectAnimator heightAnim = ObjectAnimator.ofInt(mFullImageView, "contentHeight", mFitSizeBitmap.y)
                .setDuration(ANIM_DURATION);
        final ObjectAnimator xAnim = ObjectAnimator.ofInt(mFullImageView, "contentX",
                (containerWidth - mFitSizeBitmap.x) / 2).setDuration(ANIM_DURATION);
        final ObjectAnimator yAnim = ObjectAnimator.ofInt(mFullImageView, "contentY",
                (containerHeight - mFitSizeBitmap.y) / 2).setDuration(ANIM_DURATION);
        widthAnim.start();
        heightAnim.start();
        xAnim.start();
        yAnim.start();
        // TODO check why using AnimatorSet doesn't work here:
        // final com.nineoldandroids.animation.AnimatorSet set = new AnimatorSet();
        // set.playTogether(widthAnim, heightAnim, xAnim, yAnim);
    }

    public static BitmapFactory.Options getBitmapOptions(final Resources res, final int resId) {
        final BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
        bitmapOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, bitmapOptions);
        return bitmapOptions;
    }

}

activity_main.xml

<RelativeLayout 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"
    tools:context=".MainActivity" >

    <com.example.facebookstylepictureanimationtest.CustomImageView
        android:id="@+id/fullImageView"
        android:layout_width="0px"
        android:layout_height="0px"
        android:background="#33ff0000"
        android:scaleType="centerCrop"
        android:src="@drawable/test_image_res_id"
        android:visibility="invisible" />

    <ImageView
        android:id="@+id/thumbnailImageView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:scaleType="centerCrop"
        android:src="@drawable/test_image_res_id" />

</RelativeLayout>

CustomImageView.java

public class CustomImageView extends ImageView {
    public CustomImageView(final Context context) {
        super(context);
    }

    public CustomImageView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomImageView(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setContentHeight(final int contentHeight) {
        final LayoutParams layoutParams = getLayoutParams();
        layoutParams.height = contentHeight;
        setLayoutParams(layoutParams);
    }

    public void setContentWidth(final int contentWidth) {
        final LayoutParams layoutParams = getLayoutParams();
        layoutParams.width = contentWidth;
        setLayoutParams(layoutParams);
    }

    public int getContentHeight() {
        return getLayoutParams().height;
    }

    public int getContentWidth() {
        return getLayoutParams().width;
    }

    public int getContentX() {
        return ((MarginLayoutParams) getLayoutParams()).leftMargin;
    }

    public void setContentX(final int contentX) {
        final MarginLayoutParams layoutParams = (MarginLayoutParams) getLayoutParams();
        layoutParams.leftMargin = contentX;
        setLayoutParams(layoutParams);
    }

    public int getContentY() {
        return ((MarginLayoutParams) getLayoutParams()).topMargin;
    }

    public void setContentY(final int contentY) {
        final MarginLayoutParams layoutParams = (MarginLayoutParams) getLayoutParams();
        layoutParams.topMargin = contentY;
        setLayoutParams(layoutParams);
    }

}
like image 173
android developer Avatar answered Oct 30 '22 19:10

android developer


Another solution, if you just want to make an animation of an image from small to large, you can try ActivityOptions.makeThumbnailScaleUpAnimation or makeScaleUpAnimationand see if they suit you.

http://developer.android.com/reference/android/app/ActivityOptions.html#makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int)

like image 38
Fernando Gallego Avatar answered Oct 30 '22 19:10

Fernando Gallego


You can achieve this through Transition Api, and this the resut gif:

Demo

essential code below:

private void zoomIn() {
    ViewGroup.LayoutParams layoutParams = mImage.getLayoutParams();
    int width = layoutParams.width;
    int height = layoutParams.height;
    layoutParams.width = (int) (width * 2);
    layoutParams.height = height * 2;
    mImage.setLayoutParams(layoutParams);
    mImage.setScaleType(ImageView.ScaleType.FIT_CENTER);

    TransitionSet transitionSet = new TransitionSet();
    Transition bound = new ChangeBounds();
    transitionSet.addTransition(bound);
    Transition changeImageTransform = new ChangeImageTransform();
    transitionSet.addTransition(changeImageTransform);
    transitionSet.setDuration(1000);
    TransitionManager.beginDelayedTransition(mRootView, transitionSet);
}

View demo on github

sdk version >= 21

like image 20
weigan Avatar answered Oct 30 '22 19:10

weigan