ChangeBounds.java revision b7573c2dbaff442a0f9f814bdc05aaa685574870
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/*
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Copyright (C) 2013 The Android Open Source Project
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Licensed under the Apache License, Version 2.0 (the "License");
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * you may not use this file except in compliance with the License.
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * You may obtain a copy of the License at
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
89ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch *      http://www.apache.org/licenses/LICENSE-2.0
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) * Unless required by applicable law or agreed to in writing, software
112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * distributed under the License is distributed on an "AS IS" BASIS,
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * See the License for the specific language governing permissions and
14a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * limitations under the License.
15a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) */
16a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
1768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)package android.transition;
182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import android.animation.AnimatorSet;
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.Context;
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.res.TypedArray;
2268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)import android.graphics.PointF;
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.Animator;
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.AnimatorListenerAdapter;
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.ObjectAnimator;
2768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)import android.animation.PropertyValuesHolder;
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.RectEvaluator;
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.Bitmap;
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.Canvas;
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.Path;
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.Rect;
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.drawable.BitmapDrawable;
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.drawable.Drawable;
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.util.AttributeSet;
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.util.Property;
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import android.view.View;
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import android.view.ViewGroup;
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import com.android.internal.R;
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import java.util.Map;
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/**
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * This transition captures the layout bounds of target views before and after
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * the scene change and animates those changes during the transition.
472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *
482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * <p>A ChangeBounds transition can be described in a resource file by using the
492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * tag <code>changeBounds</code>, using its attributes of
50c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) * {@link android.R.styleable#ChangeBounds} along with the other standard
51c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) * attributes of {@link android.R.styleable#Transition}.</p>
52c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) */
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)public class ChangeBounds extends Transition {
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds";
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private static final String PROPNAME_CLIP = "android:changeBounds:clip";
572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private static final String PROPNAME_PARENT = "android:changeBounds:parent";
582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX";
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY";
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private static final String[] sTransitionProperties = {
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            PROPNAME_BOUNDS,
622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            PROPNAME_CLIP,
632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            PROPNAME_PARENT,
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            PROPNAME_WINDOW_X,
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            PROPNAME_WINDOW_Y
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    };
672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private static final Property<Drawable, PointF> DRAWABLE_ORIGIN_PROPERTY =
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            new Property<Drawable, PointF>(PointF.class, "boundsOrigin") {
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                private Rect mBounds = new Rect();
712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                @Override
732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                public void set(Drawable object, PointF value) {
742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    object.copyBounds(mBounds);
752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    mBounds.offsetTo(Math.round(value.x), Math.round(value.y));
762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    object.setBounds(mBounds);
772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                }
782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                @Override
802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                public PointF get(Drawable object) {
812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    object.copyBounds(mBounds);
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    return new PointF(mBounds.left, mBounds.top);
832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                }
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    };
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final Property<ViewBounds, PointF> TOP_LEFT_PROPERTY =
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            new Property<ViewBounds, PointF>(PointF.class, "topLeft") {
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                @Override
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                public void set(ViewBounds viewBounds, PointF topLeft) {
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    viewBounds.setTopLeft(topLeft);
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                }
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                @Override
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                public PointF get(ViewBounds viewBounds) {
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    return null;
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                }
97b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)            };
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final Property<ViewBounds, PointF> BOTTOM_RIGHT_PROPERTY =
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            new Property<ViewBounds, PointF>(PointF.class, "bottomRight") {
1018bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                @Override
1028bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                public void set(ViewBounds viewBounds, PointF bottomRight) {
1038bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                    viewBounds.setBottomRight(bottomRight);
1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                }
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                @Override
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                public PointF get(ViewBounds viewBounds) {
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    return null;
1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                }
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            };
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
112c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    private static final Property<View, PointF> BOTTOM_RIGHT_ONLY_PROPERTY =
113b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)            new Property<View, PointF>(PointF.class, "bottomRight") {
114c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                @Override
115c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                public void set(View view, PointF bottomRight) {
116c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                    int left = view.getLeft();
117c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                    int top = view.getTop();
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    int right = Math.round(bottomRight.x);
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    int bottom = Math.round(bottomRight.y);
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    view.setLeftTopRightBottom(left, top, right, bottom);
1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                }
1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                @Override
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                public PointF get(View view) {
1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    return null;
126b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)                }
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            };
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final Property<View, PointF> TOP_LEFT_ONLY_PROPERTY =
13090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)            new Property<View, PointF>(PointF.class, "topLeft") {
13190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                @Override
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                public void set(View view, PointF topLeft) {
133a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                    int left = Math.round(topLeft.x);
134a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                    int top = Math.round(topLeft.y);
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    int right = view.getRight();
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    int bottom = view.getBottom();
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    view.setLeftTopRightBottom(left, top, right, bottom);
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                }
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                @Override
14190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                public PointF get(View view) {
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    return null;
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                }
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            };
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    private static final Property<View, PointF> POSITION_PROPERTY =
1474e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)            new Property<View, PointF>(PointF.class, "position") {
148a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                @Override
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                public void set(View view, PointF topLeft) {
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    int left = Math.round(topLeft.x);
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    int top = Math.round(topLeft.y);
1525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    int right = left + view.getWidth();
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    int bottom = top + view.getHeight();
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    view.setLeftTopRightBottom(left, top, right, bottom);
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                }
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                @Override
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                public PointF get(View view) {
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    return null;
1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                }
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            };
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    int[] tempLocation = new int[2];
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    boolean mResizeClip = false;
165b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    boolean mReparent = false;
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final String LOG_TAG = "ChangeBounds";
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static RectEvaluator sRectEvaluator = new RectEvaluator();
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public ChangeBounds() {}
171c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public ChangeBounds(Context context, AttributeSet attrs) {
1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        super(context, attrs);
1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ChangeBounds);
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        boolean resizeClip = a.getBoolean(R.styleable.ChangeBounds_resizeClip, false);
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        a.recycle();
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        setResizeClip(resizeClip);
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @Override
182c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    public String[] getTransitionProperties() {
183c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        return sTransitionProperties;
184c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    }
185c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
186c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    /**
187b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)     * When <code>resizeClip</code> is true, ChangeBounds resizes the view using the clipBounds
188c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * instead of changing the dimensions of the view during the animation. When
189c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * <code>resizeClip</code> is false, ChangeBounds resizes the View by changing its dimensions.
190c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     *
19190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)     * <p>When resizeClip is set to true, the clip bounds is modified by ChangeBounds. Therefore,
19290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)     * {@link android.transition.ChangeClipBounds} is not compatible with ChangeBounds
193c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * in this mode.</p>
194a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)     *
195a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)     * @param resizeClip Used to indicate whether the view bounds should be modified or the
196c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     *                   clip bounds should be modified by ChangeBounds.
197c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * @see android.view.View#setClipBounds(android.graphics.Rect)
198c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * @attr ref android.R.styleable#ChangeBounds_resizeClip
199c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     */
200c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    public void setResizeClip(boolean resizeClip) {
201c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        mResizeClip = resizeClip;
202c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    }
203c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
20490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    /**
205c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * Returns true when the ChangeBounds will resize by changing the clip bounds during the
206c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * view animation or false when bounds are changed. The default value is false.
207c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     *
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @return true when the ChangeBounds will resize by changing the clip bounds during the
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * view animation or false when bounds are changed. The default value is false.
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @attr ref android.R.styleable#ChangeBounds_resizeClip
2112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     */
212b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    public boolean getResizeClip() {
21390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)        return mResizeClip;
21490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    }
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
216a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    /**
217a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)     * Setting this flag tells ChangeBounds to track the before/after parent
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * of every view using this transition. The flag is not enabled by
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * default because it requires the parent instances to be the same
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * in the two scenes or else all parents must use ids to allow
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * the transition to determine which parents are the same.
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     *
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @param reparent true if the transition should track the parent
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * container of target views and animate parent changes.
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @deprecated Use {@link android.transition.ChangeTransform} to handle
22690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)     * transitions between different parents.
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public void setReparent(boolean reparent) {
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mReparent = reparent;
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private void captureValues(TransitionValues values) {
233b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)        View view = values.view;
23490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
23590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)        if (view.isLaidOut() || view.getWidth() != 0 || view.getHeight() != 0) {
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            values.values.put(PROPNAME_BOUNDS, new Rect(view.getLeft(), view.getTop(),
237a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                    view.getRight(), view.getBottom()));
238a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            values.values.put(PROPNAME_PARENT, values.view.getParent());
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if (mReparent) {
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                values.view.getLocationInWindow(tempLocation);
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                values.values.put(PROPNAME_WINDOW_X, tempLocation[0]);
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]);
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            }
24490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)            if (mResizeClip) {
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                values.values.put(PROPNAME_CLIP, view.getClipBounds());
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            }
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
2482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
249b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
25090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    @Override
25190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    public void captureStartValues(TransitionValues transitionValues) {
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        captureValues(transitionValues);
253a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    }
254a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @Override
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public void captureEndValues(TransitionValues transitionValues) {
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        captureValues(transitionValues);
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    private boolean parentMatches(View startParent, View endParent) {
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        boolean parentMatches = true;
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (mReparent) {
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            TransitionValues endValues = getMatchedTransitionValues(startParent, true);
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if (endValues == null) {
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                parentMatches = startParent == endParent;
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            } else {
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                parentMatches = endParent == endValues.view;
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            }
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
270        return parentMatches;
271    }
272
273    @Override
274    public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
275            TransitionValues endValues) {
276        if (startValues == null || endValues == null) {
277            return null;
278        }
279        Map<String, Object> startParentVals = startValues.values;
280        Map<String, Object> endParentVals = endValues.values;
281        ViewGroup startParent = (ViewGroup) startParentVals.get(PROPNAME_PARENT);
282        ViewGroup endParent = (ViewGroup) endParentVals.get(PROPNAME_PARENT);
283        if (startParent == null || endParent == null) {
284            return null;
285        }
286        final View view = endValues.view;
287        if (parentMatches(startParent, endParent)) {
288            Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
289            Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
290            final int startLeft = startBounds.left;
291            final int endLeft = endBounds.left;
292            final int startTop = startBounds.top;
293            final int endTop = endBounds.top;
294            final int startRight = startBounds.right;
295            final int endRight = endBounds.right;
296            final int startBottom = startBounds.bottom;
297            final int endBottom = endBounds.bottom;
298            final int startWidth = startRight - startLeft;
299            final int startHeight = startBottom - startTop;
300            final int endWidth = endRight - endLeft;
301            final int endHeight = endBottom - endTop;
302            Rect startClip = (Rect) startValues.values.get(PROPNAME_CLIP);
303            Rect endClip = (Rect) endValues.values.get(PROPNAME_CLIP);
304            int numChanges = 0;
305            if ((startWidth != 0 && startHeight != 0) || (endWidth != 0 && endHeight != 0)) {
306                if (startLeft != endLeft || startTop != endTop) ++numChanges;
307                if (startRight != endRight || startBottom != endBottom) ++numChanges;
308            }
309            if ((startClip != null && !startClip.equals(endClip)) ||
310                    (startClip == null && endClip != null)) {
311                ++numChanges;
312            }
313            if (numChanges > 0) {
314                Animator anim;
315                if (!mResizeClip) {
316                    view.setLeftTopRightBottom(startLeft, startTop, startRight, startBottom);
317                    if (numChanges == 2) {
318                        if (startWidth == endWidth && startHeight == endHeight) {
319                            Path topLeftPath = getPathMotion().getPath(startLeft, startTop, endLeft,
320                                    endTop);
321                            anim = ObjectAnimator.ofObject(view, POSITION_PROPERTY, null,
322                                    topLeftPath);
323                        } else {
324                            final ViewBounds viewBounds = new ViewBounds(view);
325                            Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
326                                    endLeft, endTop);
327                            ObjectAnimator topLeftAnimator = ObjectAnimator
328                                    .ofObject(viewBounds, TOP_LEFT_PROPERTY, null, topLeftPath);
329
330                            Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
331                                    endRight, endBottom);
332                            ObjectAnimator bottomRightAnimator = ObjectAnimator.ofObject(viewBounds,
333                                    BOTTOM_RIGHT_PROPERTY, null, bottomRightPath);
334                            AnimatorSet set = new AnimatorSet();
335                            set.playTogether(topLeftAnimator, bottomRightAnimator);
336                            anim = set;
337                            set.addListener(new AnimatorListenerAdapter() {
338                                // We need a strong reference to viewBounds until the
339                                // animator ends.
340                                private ViewBounds mViewBounds = viewBounds;
341                            });
342                        }
343                    } else if (startLeft != endLeft || startTop != endTop) {
344                        Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
345                                endLeft, endTop);
346                        anim = ObjectAnimator.ofObject(view, TOP_LEFT_ONLY_PROPERTY, null,
347                                topLeftPath);
348                    } else {
349                        Path bottomRight = getPathMotion().getPath(startRight, startBottom,
350                                endRight, endBottom);
351                        anim = ObjectAnimator.ofObject(view, BOTTOM_RIGHT_ONLY_PROPERTY, null,
352                                bottomRight);
353                    }
354                } else {
355                    int maxWidth = Math.max(startWidth, endWidth);
356                    int maxHeight = Math.max(startHeight, endHeight);
357
358                    view.setLeftTopRightBottom(startLeft, startTop, startLeft + maxWidth,
359                            startTop + maxHeight);
360
361                    ObjectAnimator positionAnimator = null;
362                    if (startLeft != endLeft || startTop != endTop) {
363                        Path topLeftPath = getPathMotion().getPath(startLeft, startTop, endLeft,
364                                endTop);
365                        positionAnimator = ObjectAnimator.ofObject(view, POSITION_PROPERTY, null,
366                                topLeftPath);
367                    }
368                    final Rect finalClip = endClip;
369                    if (startClip == null) {
370                        startClip = new Rect(0, 0, startWidth, startHeight);
371                    }
372                    if (endClip == null) {
373                        endClip = new Rect(0, 0, endWidth, endHeight);
374                    }
375                    ObjectAnimator clipAnimator = null;
376                    if (!startClip.equals(endClip)) {
377                        view.setClipBounds(startClip);
378                        clipAnimator = ObjectAnimator.ofObject(view, "clipBounds", sRectEvaluator,
379                                startClip, endClip);
380                        clipAnimator.addListener(new AnimatorListenerAdapter() {
381                            private boolean mIsCanceled;
382
383                            @Override
384                            public void onAnimationCancel(Animator animation) {
385                                mIsCanceled = true;
386                            }
387
388                            @Override
389                            public void onAnimationEnd(Animator animation) {
390                                if (!mIsCanceled) {
391                                    view.setClipBounds(finalClip);
392                                    view.setLeftTopRightBottom(endLeft, endTop, endRight,
393                                            endBottom);
394                                }
395                            }
396                        });
397                    }
398                    anim = TransitionUtils.mergeAnimators(positionAnimator,
399                            clipAnimator);
400                }
401                if (view.getParent() instanceof ViewGroup) {
402                    final ViewGroup parent = (ViewGroup) view.getParent();
403                    parent.suppressLayout(true);
404                    TransitionListener transitionListener = new TransitionListenerAdapter() {
405                        boolean mCanceled = false;
406
407                        @Override
408                        public void onTransitionCancel(Transition transition) {
409                            parent.suppressLayout(false);
410                            mCanceled = true;
411                        }
412
413                        @Override
414                        public void onTransitionEnd(Transition transition) {
415                            if (!mCanceled) {
416                                parent.suppressLayout(false);
417                            }
418                        }
419
420                        @Override
421                        public void onTransitionPause(Transition transition) {
422                            parent.suppressLayout(false);
423                        }
424
425                        @Override
426                        public void onTransitionResume(Transition transition) {
427                            parent.suppressLayout(true);
428                        }
429                    };
430                    addListener(transitionListener);
431                }
432                return anim;
433            }
434        } else {
435            int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X);
436            int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y);
437            int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X);
438            int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y);
439            // TODO: also handle size changes: check bounds and animate size changes
440            if (startX != endX || startY != endY) {
441                sceneRoot.getLocationInWindow(tempLocation);
442                Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
443                        Bitmap.Config.ARGB_8888);
444                Canvas canvas = new Canvas(bitmap);
445                view.draw(canvas);
446                final BitmapDrawable drawable = new BitmapDrawable(bitmap);
447                final float transitionAlpha = view.getTransitionAlpha();
448                view.setTransitionAlpha(0);
449                sceneRoot.getOverlay().add(drawable);
450                Path topLeftPath = getPathMotion().getPath(startX - tempLocation[0],
451                        startY - tempLocation[1], endX - tempLocation[0], endY - tempLocation[1]);
452                PropertyValuesHolder origin = PropertyValuesHolder.ofObject(
453                        DRAWABLE_ORIGIN_PROPERTY, null, topLeftPath);
454                ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, origin);
455                anim.addListener(new AnimatorListenerAdapter() {
456                    @Override
457                    public void onAnimationEnd(Animator animation) {
458                        sceneRoot.getOverlay().remove(drawable);
459                        view.setTransitionAlpha(transitionAlpha);
460                    }
461                });
462                return anim;
463            }
464        }
465        return null;
466    }
467
468    private static class ViewBounds {
469        private int mLeft;
470        private int mTop;
471        private int mRight;
472        private int mBottom;
473        private boolean mIsTopLeftSet;
474        private boolean mIsBottomRightSet;
475        private View mView;
476
477        public ViewBounds(View view) {
478            mView = view;
479        }
480
481        public void setTopLeft(PointF topLeft) {
482            mLeft = Math.round(topLeft.x);
483            mTop = Math.round(topLeft.y);
484            mIsTopLeftSet = true;
485            if (mIsBottomRightSet) {
486                setLeftTopRightBottom();
487            }
488        }
489
490        public void setBottomRight(PointF bottomRight) {
491            mRight = Math.round(bottomRight.x);
492            mBottom = Math.round(bottomRight.y);
493            mIsBottomRightSet = true;
494            if (mIsTopLeftSet) {
495                setLeftTopRightBottom();
496            }
497        }
498
499        private void setLeftTopRightBottom() {
500            mView.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
501            mIsTopLeftSet = false;
502            mIsBottomRightSet = false;
503        }
504    }
505}
506