blob: 2f3c7ad49211d80505007fd87451e6c24993eafe [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 androidx.leanback.transition;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.transition.Fade;
import android.transition.Transition;
import android.transition.TransitionValues;
import android.transition.Visibility;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.leanback.R;
/**
* Execute horizontal slide of 1/4 width and fade (to workaround bug 23718734)
* @hide
*/
@RequiresApi(21)
@RestrictTo(LIBRARY_GROUP_PREFIX)
public class FadeAndShortSlide extends Visibility {
private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
// private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
private static final String PROPNAME_SCREEN_POSITION =
"android:fadeAndShortSlideTransition:screenPosition";
private CalculateSlide mSlideCalculator;
private Visibility mFade = new Fade();
private float mDistance = -1;
private static abstract class CalculateSlide {
CalculateSlide() {
}
/** Returns the translation X value for view when it goes out of the scene */
float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
return view.getTranslationX();
}
/** Returns the translation Y value for view when it goes out of the scene */
float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
return view.getTranslationY();
}
}
float getHorizontalDistance(ViewGroup sceneRoot) {
return mDistance >= 0 ? mDistance : (sceneRoot.getWidth() / 4);
}
float getVerticalDistance(ViewGroup sceneRoot) {
return mDistance >= 0 ? mDistance : (sceneRoot.getHeight() / 4);
}
final static CalculateSlide sCalculateStart = new CalculateSlide() {
@Override
public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
final float x;
if (isRtl) {
x = view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
} else {
x = view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
}
return x;
}
};
final static CalculateSlide sCalculateEnd = new CalculateSlide() {
@Override
public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
final float x;
if (isRtl) {
x = view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
} else {
x = view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
}
return x;
}
};
final static CalculateSlide sCalculateStartEnd = new CalculateSlide() {
@Override
public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
final int viewCenter = position[0] + view.getWidth() / 2;
sceneRoot.getLocationOnScreen(position);
Rect center = t.getEpicenter();
final int sceneRootCenter = center == null ? (position[0] + sceneRoot.getWidth() / 2)
: center.centerX();
if (viewCenter < sceneRootCenter) {
return view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
} else {
return view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
}
}
};
final static CalculateSlide sCalculateBottom = new CalculateSlide() {
@Override
public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
return view.getTranslationY() + t.getVerticalDistance(sceneRoot);
}
};
final static CalculateSlide sCalculateTop = new CalculateSlide() {
@Override
public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
return view.getTranslationY() - t.getVerticalDistance(sceneRoot);
}
};
final CalculateSlide sCalculateTopBottom = new CalculateSlide() {
@Override
public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
final int viewCenter = position[1] + view.getHeight() / 2;
sceneRoot.getLocationOnScreen(position);
Rect center = getEpicenter();
final int sceneRootCenter = center == null ? (position[1] + sceneRoot.getHeight() / 2)
: center.centerY();
if (viewCenter < sceneRootCenter) {
return view.getTranslationY() - t.getVerticalDistance(sceneRoot);
} else {
return view.getTranslationY() + t.getVerticalDistance(sceneRoot);
}
}
};
public FadeAndShortSlide() {
this(Gravity.START);
}
public FadeAndShortSlide(int slideEdge) {
setSlideEdge(slideEdge);
}
public FadeAndShortSlide(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSlide);
int edge = a.getInt(R.styleable.lbSlide_lb_slideEdge, Gravity.START);
setSlideEdge(edge);
a.recycle();
}
@Override
public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
mFade.setEpicenterCallback(epicenterCallback);
super.setEpicenterCallback(epicenterCallback);
}
private void captureValues(TransitionValues transitionValues) {
View view = transitionValues.view;
int[] position = new int[2];
view.getLocationOnScreen(position);
transitionValues.values.put(PROPNAME_SCREEN_POSITION, position);
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
mFade.captureStartValues(transitionValues);
super.captureStartValues(transitionValues);
captureValues(transitionValues);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
mFade.captureEndValues(transitionValues);
super.captureEndValues(transitionValues);
captureValues(transitionValues);
}
public void setSlideEdge(int slideEdge) {
switch (slideEdge) {
case Gravity.START:
mSlideCalculator = sCalculateStart;
break;
case Gravity.END:
mSlideCalculator = sCalculateEnd;
break;
case Gravity.START | Gravity.END:
mSlideCalculator = sCalculateStartEnd;
break;
case Gravity.TOP:
mSlideCalculator = sCalculateTop;
break;
case Gravity.BOTTOM:
mSlideCalculator = sCalculateBottom;
break;
case Gravity.TOP | Gravity.BOTTOM:
mSlideCalculator = sCalculateTopBottom;
break;
default:
throw new IllegalArgumentException("Invalid slide direction");
}
}
@Override
public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
TransitionValues endValues) {
if (endValues == null) {
return null;
}
if (sceneRoot == view) {
// workaround b/25375640, avoid run animation on sceneRoot
return null;
}
int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);
int left = position[0];
int top = position[1];
float endX = view.getTranslationX();
float startX = mSlideCalculator.getGoneX(this, sceneRoot, view, position);
float endY = view.getTranslationY();
float startY = mSlideCalculator.getGoneY(this, sceneRoot, view, position);
final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view, endValues,
left, top, startX, startY, endX, endY, sDecelerate, this);
final Animator fadeAnimator = mFade.onAppear(sceneRoot, view, startValues, endValues);
if (slideAnimator == null) {
return fadeAnimator;
} else if (fadeAnimator == null) {
return slideAnimator;
}
final AnimatorSet set = new AnimatorSet();
set.play(slideAnimator).with(fadeAnimator);
return set;
}
@Override
public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
TransitionValues endValues) {
if (startValues == null) {
return null;
}
if (sceneRoot == view) {
// workaround b/25375640, avoid run animation on sceneRoot
return null;
}
int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION);
int left = position[0];
int top = position[1];
float startX = view.getTranslationX();
float endX = mSlideCalculator.getGoneX(this, sceneRoot, view, position);
float startY = view.getTranslationY();
float endY = mSlideCalculator.getGoneY(this, sceneRoot, view, position);
final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view,
startValues, left, top, startX, startY, endX, endY, sDecelerate /* sAccelerate */,
this);
final Animator fadeAnimator = mFade.onDisappear(sceneRoot, view, startValues, endValues);
if (slideAnimator == null) {
return fadeAnimator;
} else if (fadeAnimator == null) {
return slideAnimator;
}
final AnimatorSet set = new AnimatorSet();
set.play(slideAnimator).with(fadeAnimator);
return set;
}
@Override
public Transition addListener(TransitionListener listener) {
mFade.addListener(listener);
return super.addListener(listener);
}
@Override
public Transition removeListener(TransitionListener listener) {
mFade.removeListener(listener);
return super.removeListener(listener);
}
/**
* Returns distance to slide. When negative value is returned, it will use 1/4 of
* sceneRoot dimension.
*/
public float getDistance() {
return mDistance;
}
/**
* Set distance to slide, default value is -1. when negative value is set, it will use 1/4 of
* sceneRoot dimension.
* @param distance Pixels to slide.
*/
public void setDistance(float distance) {
mDistance = distance;
}
@Override
public Transition clone() {
FadeAndShortSlide clone = (FadeAndShortSlide) super.clone();
clone.mFade = (Visibility) mFade.clone();
return clone;
}
}