/* * Copyright (C) 2017 takahirom * * 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.github.takahirom.materialelement.animation.transition; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.TypeConverter; import android.animation.TypeEvaluator; import android.content.Context; import android.graphics.Path; import android.graphics.PointF; import android.graphics.Rect; import android.os.Build; import android.support.annotation.RequiresApi; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.transition.Transition; import android.transition.TransitionValues; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /** * From ChangeBoundsPort in AOSP */ public class AsymmetricTransform extends Transition { private static final String PROP_BOUNDS = "mdap:fabTransform:bounds"; private static final String[] transitionProperties = { PROP_BOUNDS }; private static final long DEFAULT_DURATION = 375L; public AsymmetricTransform() { super(); setDuration(DEFAULT_DURATION); } public AsymmetricTransform(Context context, AttributeSet attrs) { super(context, attrs); } @Override public String[] getTransitionProperties() { return transitionProperties; } @Override public void captureStartValues(TransitionValues transitionValues) { captureValues(transitionValues); } @Override public void captureEndValues(TransitionValues transitionValues) { captureValues(transitionValues); } private void captureValues(TransitionValues transitionValues) { final View view = transitionValues.view; if (view == null || view.getWidth() <= 0 || view.getHeight() <= 0) return; transitionValues.values.put(PROP_BOUNDS, new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues == null || endValues == null) return null; final Rect startBounds = (Rect) startValues.values.get(PROP_BOUNDS); final Rect endBounds = (Rect) endValues.values.get(PROP_BOUNDS); final boolean expand = startBounds.top > endBounds.top; final View view = endValues.view; int startLeft = startBounds.left; int endLeft = endBounds.left; int startTop = startBounds.top; int endTop = endBounds.top; int startRight = startBounds.right; int endRight = endBounds.right; int startBottom = startBounds.bottom; int endBottom = endBounds.bottom; int startWidth = startRight - startLeft; int startHeight = startBottom - startTop; int endWidth = endRight - endLeft; int endHeight = endBottom - endTop; int numChanges = 0; if (startWidth != 0 && startHeight != 0 && endWidth != 0 && endHeight != 0) { if (startLeft != endLeft) { ++numChanges; } if (startRight != endRight) { ++numChanges; } } PropertyValuesHolder pvhWidth[] = new PropertyValuesHolder[numChanges]; int pvhIndex = 0; if (startLeft != endLeft) { view.setLeft(startLeft); } if (startTop != endTop) { view.setTop(startTop); } if (startRight != endRight) { view.setRight(startRight); } if (startBottom != endBottom) { view.setBottom(startBottom); } if (startLeft != endLeft) { Path topLeftPath = getPathMotion().getPath(startLeft, startTop, endLeft, endTop); pvhWidth[pvhIndex++] = PropertyValuesHolder.ofObject("left", new TypeConverter<PointF, Integer>(PointF.class, Integer.class) { @Override public Integer convert(PointF value) { return (int) value.x; } }, topLeftPath); } if (startRight != endRight) { Path topRightPath = getPathMotion().getPath(startRight, startTop, endRight, endTop); pvhWidth[pvhIndex] = PropertyValuesHolder.ofObject("right", new TypeConverter<PointF, Integer>(PointF.class, Integer.class) { @Override public Integer convert(PointF value) { return (int) value.x; } }, topRightPath); } ObjectAnimator widthAnim = ObjectAnimator.ofPropertyValuesHolder(view, pvhWidth); pvhIndex = 0; numChanges = 0; if (startWidth != 0 && startHeight != 0 && endWidth != 0 && endHeight != 0) { if (startTop != endTop) { ++numChanges; } if (startBottom != endBottom) { ++numChanges; } } PropertyValuesHolder pvhHeight[] = new PropertyValuesHolder[numChanges]; if (startTop != endTop) { Path topRightPath = getPathMotion().getPath(startRight, startTop, endRight, endTop); pvhHeight[pvhIndex++] = PropertyValuesHolder.ofObject("top", new TypeConverter<PointF, Integer>(PointF.class, Integer.class) { @Override public Integer convert(PointF value) { return (int) value.y; } }, topRightPath); } if (startBottom != endBottom) { Path topRightPath = getPathMotion().getPath(startRight, startBottom, endRight, endBottom); pvhHeight[pvhIndex] = PropertyValuesHolder.ofObject("bottom", new TypeConverter<PointF, Integer>(PointF.class, Integer.class) { @Override public Integer convert(PointF value) { return (int) value.y; } }, topRightPath); } ObjectAnimator heightAnim = ObjectAnimator.ofPropertyValuesHolder(view, pvhHeight); AnimatorSet animatorSet = new AnimatorSet(); if (expand) { widthAnim.setDuration(275); heightAnim.setStartDelay(30); heightAnim.setDuration(345); animatorSet.playTogether(widthAnim, heightAnim); } else { widthAnim.setDuration(325); widthAnim.setStartDelay(50); heightAnim.setDuration(325); animatorSet.playTogether(heightAnim, widthAnim); } animatorSet.setInterpolator(new FastOutSlowInInterpolator()); if (view.getParent() instanceof ViewGroup) { final ViewGroup parent = (ViewGroup) view.getParent(); // parent.suppressLayout(true); Transition.TransitionListener transitionListener = new Transition.TransitionListener() { boolean mCanceled = false; @Override public void onTransitionCancel(Transition transition) { // parent.suppressLayout(false); mCanceled = true; } @Override public void onTransitionStart(Transition transition) { } @Override public void onTransitionEnd(Transition transition) { if (!mCanceled) { // parent.suppressLayout(false); } } @Override public void onTransitionPause(Transition transition) { // parent.suppressLayout(false); } @Override public void onTransitionResume(Transition transition) { // parent.suppressLayout(true); } }; addListener(transitionListener); } return animatorSet; } }