content: /*
   * Copyright (C) 2014 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 android.transition;
  
  import android.animation.Animator;
  import android.animation.TimeInterpolator;
  import android.annotation.IntDef;
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.util.AttributeSet;
  import android.view.Gravity;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.animation.AccelerateInterpolator;
  import android.view.animation.DecelerateInterpolator;
  
  import com.android.internal.R;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  
  /**
   * This transition tracks changes to the visibility of target views in the
   * start and end scenes and moves views in or out from one of the edges of the
   * scene. Visibility is determined by both the
   * {@link View#setVisibility(int)} state of the view as well as whether it
   * is parented in the current view hierarchy. Disappearing Views are
   * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
   * TransitionValues, int, TransitionValues, int)}.
   */
  public class Slide extends Visibility {
      private static final String TAG = "Slide";
      private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
      private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
      private static final String PROPNAME_SCREEN_POSITION = "android:slide:screenPosition";
      private CalculateSlide mSlideCalculator = sCalculateBottom;
      private @GravityFlag int mSlideEdge = Gravity.BOTTOM;
      private float mSlideFraction = 1;
  
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef({Gravity.LEFT, Gravity.TOP, Gravity.RIGHT, Gravity.BOTTOM, Gravity.START, Gravity.END})
      public @interface GravityFlag {}
  
      private interface CalculateSlide {
  
          /** Returns the translation value for view when it goes out of the scene */
          float getGoneX(ViewGroup sceneRoot, View view, float fraction);
  
          /** Returns the translation value for view when it goes out of the scene */
          float getGoneY(ViewGroup sceneRoot, View view, float fraction);
      }
  
      private static abstract class CalculateSlideHorizontal implements CalculateSlide {
  
          @Override
          public float getGoneY(ViewGroup sceneRoot, View view, float fraction) {
              return view.getTranslationY();
          }
      }
  
      private static abstract class CalculateSlideVertical implements CalculateSlide {
  
          @Override
          public float getGoneX(ViewGroup sceneRoot, View view, float fraction) {
              return view.getTranslationX();
          }
      }
  
      private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
          @Override
          public float getGoneX(ViewGroup sceneRoot, View view, float fraction) {
              return view.getTranslationX() - sceneRoot.getWidth() * fraction;
          }
      };
  
      private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() {
          @Override
          public float getGoneX(ViewGroup sceneRoot, View view, float fraction) {
              final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
              final float x;
              if (isRtl) {
                  x = view.getTranslationX() + sceneRoot.getWidth() * fraction;
              } else {
                  x = view.getTranslationX() - sceneRoot.getWidth() * fraction;
              }
              return x;
          }
      };
  
      private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
          @Override
          public float getGoneY(ViewGroup sceneRoot, View view, float fraction) {
              return view.getTranslationY() - sceneRoot.getHeight() * fraction;
          }
      };
  
      private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
          @Override
          public float getGoneX(ViewGroup sceneRoot, View view, float fraction) {
              return view.getTranslationX() + sceneRoot.getWidth() * fraction;
          }
      };
  
      private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() {
          @Override
          public float getGoneX(ViewGroup sceneRoot, View view, float fraction) {
              final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
              final float x;
              if (isRtl) {
                  x = view.getTranslationX() - sceneRoot.getWidth() * fraction;
              } else {
                  x = view.getTranslationX() + sceneRoot.getWidth() * fraction;
              }
              return x;
          }
      };
  
      private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
          @Override
          public float getGoneY(ViewGroup sceneRoot, View view, float fraction) {
              return view.getTranslationY() + sceneRoot.getHeight() * fraction;
          }
      };
  
      /**
       * Constructor using the default {@link Gravity#BOTTOM}
       * slide edge direction.
       */
      public Slide() {
          setSlideEdge(Gravity.BOTTOM);
      }
  
      /**
       * Constructor using the provided slide edge direction.
       */
      public Slide(@GravityFlag int slideEdge) {
          setSlideEdge(slideEdge);
      }
  
      public Slide(Context context, AttributeSet attrs) {
          super(context, attrs);
          TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Slide);
          int edge = a.getInt(R.styleable.Slide_slideEdge, Gravity.BOTTOM);
          a.recycle();
          setSlideEdge(edge);
      }
  
      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) {
          super.captureStartValues(transitionValues);
          captureValues(transitionValues);
      }
  
      @Override
      public void captureEndValues(TransitionValues transitionValues) {
          super.captureEndValues(transitionValues);
          captureValues(transitionValues);
      }
  
      /**
       * Change the edge that Views appear and disappear from.
       *
       * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of
       *                  {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
       *                  {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
       *                  {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
       * @attr ref android.R.styleable#Slide_slideEdge
       */
      public void setSlideEdge(@GravityFlag int slideEdge) {
          switch (slideEdge) {
              case Gravity.LEFT:
                  mSlideCalculator = sCalculateLeft;
                  break;
              case Gravity.TOP:
                  mSlideCalculator = sCalculateTop;
                  break;
              case Gravity.RIGHT:
                  mSlideCalculator = sCalculateRight;
                  break;
              case Gravity.BOTTOM:
                  mSlideCalculator = sCalculateBottom;
                  break;
              case Gravity.START:
                  mSlideCalculator = sCalculateStart;
                  break;
              case Gravity.END:
                  mSlideCalculator = sCalculateEnd;
                  break;
              default:
                  throw new IllegalArgumentException("Invalid slide direction");
          }
          mSlideEdge = slideEdge;
          SidePropagation propagation = new SidePropagation();
          propagation.setSide(slideEdge);
          setPropagation(propagation);
      }
  
      /**
       * Returns the edge that Views appear and disappear from.
       *
       * @return the edge of the scene to use for Views appearing and disappearing. One of
       *         {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
       *         {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
       *         {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
       * @attr ref android.R.styleable#Slide_slideEdge
       */
      @GravityFlag
      public int getSlideEdge() {
          return mSlideEdge;
      }
  
      @Override
      public Animator onAppear(ViewGroup sceneRoot, View view,
              TransitionValues startValues, TransitionValues endValues) {
          if (endValues == null) {
              return null;
          }
          int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);
          float endX = view.getTranslationX();
          float endY = view.getTranslationY();
          float startX = mSlideCalculator.getGoneX(sceneRoot, view, mSlideFraction);
          float startY = mSlideCalculator.getGoneY(sceneRoot, view, mSlideFraction);
          return TranslationAnimationCreator
                  .createAnimation(view, endValues, position[0], position[1],
                          startX, startY, endX, endY, sDecelerate, this);
      }
  
      @Override
      public Animator onDisappear(ViewGroup sceneRoot, View view,
              TransitionValues startValues, TransitionValues endValues) {
          if (startValues == null) {
              return null;
          }
          int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION);
          float startX = view.getTranslationX();
          float startY = view.getTranslationY();
          float endX = mSlideCalculator.getGoneX(sceneRoot, view, mSlideFraction);
          float endY = mSlideCalculator.getGoneY(sceneRoot, view, mSlideFraction);
          return TranslationAnimationCreator
                  .createAnimation(view, startValues, position[0], position[1],
                          startX, startY, endX, endY, sAccelerate, this);
      }
  
      /** @hide */
      public void setSlideFraction(float slideFraction) {
          mSlideFraction = slideFraction;
      }
  }
  