Search code examples
androidobjectanimator

Scale from start rect to final position


I'm trying to scale view from start rectangle (e.g. defined by another view) to it's final position.

I tried to use the following code to setup animations which looks straight forward:

float scaleX = 0f;
float scaleY = 0f;
Rect startRect = new Rect(10, 10, 100, 100); // taken from real view position with getLocationOnScreen
final Collection<Animator> animators = new ArrayList<>();

if (animatedView.getMeasuredHeight() != 0) {
    scaleX = (float)startRect.width() / animatedView.getMeasuredWidth();
}

if (animatedView.getMeasuredHeight() != 0) {
    scaleY = (float)startRect.height() / animatedView.getMeasuredHeight();
}

animatedView.getLocationInWindow(location);
animatedView.setPivotX(startRect.left);
animatedView.setPivotY(startRect.top);
animatedView.setScaleX(scaleX);
animatedView.setScaleY(scaleY);

animators.add(ObjectAnimator.ofFloat(animatedView, View.SCALE_X, 1.0f).setDuration(1000));
animators.add(ObjectAnimator.ofFloat(animatedView, View.SCALE_Y, 1.0f).setDuration(1000));

The animatedView is child of RelativeLayout (layout parameters set to below some title view of layout) and measured width and height and location are valid values at the moment of animation setup.

Depending on startRect I observe different animations - sometimes animated view get displayed below or above startRect.

Seems RectEvaluator is one of possible solutions, but it's available only from API 18.

What is the proper way to animate view from start rectangle position to final (not modified one)?


Solution

  • As per comments on the question, it's possible to copy RectEvaluator code from Android source, and then apply the following logic:

    RectViewAnimator mRectAnimator;
    
    /**
     * Creates animator which can be played. From some start position
     * to final (real position).
     * From final position to start position can be achieved using reverse interpolation.
     */
    private Collection<Animator> createMoveAnimators(View targetView, Rect startRect) {
        final Collection<Animator> animators = new ArrayList<>();
        final int[] location = new int[2];
    
        targetView.getLocationOnScreen(location);
    
        final Rect finalRect = new Rect(location[0], location[1],
                location[0] + targetView.getMeasuredWidth(),
                location[1] + targetView.getMeasuredHeight());
    
        // Must keep this reference during animations, since Animator keeps only WeakReference to it's targets.
        mRectAnimator = appendRectEvaluatorAnimation(animators, targetView, 500, startRect, finalRect);
    
        return animators;
    }
    
    private RectViewAnimator appendRectEvaluatorAnimation(final Collection<Animator> animators, final View view, final int duration,
                                                          final Rect startRect, final Rect finalRect) {
        final float scaleX = (float) startRect.width() / finalRect.width();
        final float scaleY = (float) startRect.height() / finalRect.height();
    
        view.setTranslationY(startRect.top - (finalRect.top + (1 - scaleY) * finalRect.height() / 2));
        view.setTranslationX(startRect.left - (finalRect.left + (1 - scaleX) * finalRect.width() / 2));
        view.setScaleX(scaleX);
        view.setScaleY(scaleY);
    
        final RectViewAnimator rectViewAnimator = new RectViewAnimator(view, finalRect);
        final Animator animator = ObjectAnimator.ofObject(rectViewAnimator, RectViewAnimator.RECT,
                new RectEvaluator(), startRect, finalRect);
    
        animators.add(animator);
        return rectViewAnimator;
    }
    
    private static class RectViewAnimator {
        static final String RECT = "rect";
    
        private final View target;
        private final Rect finalRect;
    
        RectViewAnimator(final View target, final Rect finalRect) {
            this.target = target;
            this.finalRect = finalRect;
        }
    
        @Keep
        public void setRect(final Rect r) {
            final float scaleX = (float)r.width() / finalRect.width();
            final float scaleY = (float)r.height() / finalRect.height();
    
            target.setScaleX(scaleX);
            target.setScaleY(scaleY);
            target.setTranslationX(r.left - (finalRect.left + (1 - scaleX) * finalRect.width() / 2));
            target.setTranslationY(r.top - (finalRect.top + (1 - scaleY) * finalRect.height() / 2));
        }
    }