content: /*
  * Copyright 2013 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 com.example.android.batchstepsensor.cardstream;
  
  import android.animation.Animator;
  import android.animation.LayoutTransition;
  import android.animation.ObjectAnimator;
  import android.annotation.SuppressLint;
  import android.annotation.TargetApi;
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.graphics.Rect;
  import android.os.Build;
  import android.util.AttributeSet;
  import android.view.MotionEvent;
  import android.view.View;
  import android.view.ViewConfiguration;
  import android.view.ViewGroup;
  import android.view.ViewParent;
  import android.widget.LinearLayout;
  import android.widget.ScrollView;
  
  import com.example.android.common.logger.Log;
  import com.example.android.batchstepsensor.R;
  
  import java.util.ArrayList;
  
  /**
   * A Layout that contains a stream of card views.
   */
  public class CardStreamLinearLayout extends LinearLayout {
  
      public static final int ANIMATION_SPEED_SLOW = 1001;
      public static final int ANIMATION_SPEED_NORMAL = 1002;
      public static final int ANIMATION_SPEED_FAST = 1003;
  
      private static final String TAG = "CardStreamLinearLayout";
      private final ArrayList<View> mFixedViewList = new ArrayList<View>();
      private final Rect mChildRect = new Rect();
      private CardStreamAnimator mAnimators;
      private OnDissmissListener mDismissListener = null;
      private boolean mLayouted = false;
      private boolean mSwiping = false;
      private String mFirstVisibleCardTag = null;
      private boolean mShowInitialAnimation = false;
  
      /**
       * Handle touch events to fade/move dragged items as they are swiped out
       */
      private OnTouchListener mTouchListener = new OnTouchListener() {
  
          private float mDownX;
          private float mDownY;
  
          @Override
          public boolean onTouch(final View v, MotionEvent event) {
  
              switch (event.getAction()) {
                  case MotionEvent.ACTION_DOWN:
                      mDownX = event.getX();
                      mDownY = event.getY();
                      break;
                  case MotionEvent.ACTION_CANCEL:
                      resetAnimatedView(v);
                      mSwiping = false;
                      mDownX = 0.f;
                      mDownY = 0.f;
                      break;
                  case MotionEvent.ACTION_MOVE: {
  
                      float x = event.getX() + v.getTranslationX();
                      float y = event.getY() + v.getTranslationY();
  
                      mDownX = mDownX == 0.f ? x : mDownX;
                      mDownY = mDownY == 0.f ? x : mDownY;
  
                      float deltaX = x - mDownX;
                      float deltaY = y - mDownY;
  
                      if (!mSwiping && isSwiping(deltaX, deltaY)) {
                          mSwiping = true;
                          v.getParent().requestDisallowInterceptTouchEvent(true);
                      } else {
                          swipeView(v, deltaX, deltaY);
                      }
                  }
                  break;
                  case MotionEvent.ACTION_UP: {
                      // User let go - figure out whether to animate the view out, or back into place
                      if (mSwiping) {
                          float x = event.getX() + v.getTranslationX();
                          float y = event.getY() + v.getTranslationY();
  
                          float deltaX = x - mDownX;
                          float deltaY = y - mDownX;
                          float deltaXAbs = Math.abs(deltaX);
  
                          // User let go - figure out whether to animate the view out, or back into place
                          boolean remove = deltaXAbs > v.getWidth() / 4 && !isFixedView(v);
                          if( remove )
                              handleViewSwipingOut(v, deltaX, deltaY);
                          else
                              handleViewSwipingIn(v, deltaX, deltaY);
                      }
                      mDownX = 0.f;
                      mDownY = 0.f;
                      mSwiping = false;
                  }
                  break;
                  default:
                      return false;
              }
              return false;
          }
      };
      private int mSwipeSlop = -1;
      /**
       * Handle end-transition animation event of each child and launch a following animation.
       */
      private LayoutTransition.TransitionListener mTransitionListener
              = new LayoutTransition.TransitionListener() {
  
          @Override
          public void startTransition(LayoutTransition transition, ViewGroup container, View
                  view, int transitionType) {
              Log.d(TAG, "Start LayoutTransition animation:" + transitionType);
          }
  
          @Override
          public void endTransition(LayoutTransition transition, ViewGroup container,
                                    final View view, int transitionType) {
  
              Log.d(TAG, "End LayoutTransition animation:" + transitionType);
              if (transitionType == LayoutTransition.APPEARING) {
                  final View area = view.findViewById(R.id.card_actionarea);
                  if (area != null) {
                      runShowActionAreaAnimation(container, area);
                  }
              }
          }
      };
      /**
       * Handle a hierarchy change event
       * when a new child is added, scroll to bottom and hide action area..
       */
      private OnHierarchyChangeListener mOnHierarchyChangeListener
              = new OnHierarchyChangeListener() {
          @Override
          public void onChildViewAdded(final View parent, final View child) {
  
              Log.d(TAG, "child is added: " + child);
  
              ViewParent scrollView = parent.getParent();
              if (scrollView != null && scrollView instanceof ScrollView) {
                  ((ScrollView) scrollView).fullScroll(FOCUS_DOWN);
              }
  
              if (getLayoutTransition() != null) {
                  View view = child.findViewById(R.id.card_actionarea);
                  if (view != null)
                      view.setAlpha(0.f);
              }
          }
  
          @Override
          public void onChildViewRemoved(View parent, View child) {
              Log.d(TAG, "child is removed: " + child);
              mFixedViewList.remove(child);
          }
      };
      private int mLastDownX;
  
      public CardStreamLinearLayout(Context context) {
          super(context);
          initialize(null, 0);
      }
  
      public CardStreamLinearLayout(Context context, AttributeSet attrs) {
          super(context, attrs);
          initialize(attrs, 0);
      }
  
      @SuppressLint("NewApi")
      public CardStreamLinearLayout(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
          initialize(attrs, defStyle);
      }
  
      /**
       * add a card view w/ canDismiss flag.
       *
       * @param cardView   a card view
       * @param canDismiss flag to indicate this card is dismissible or not.
       */
      public void addCard(View cardView, boolean canDismiss) {
          if (cardView.getParent() == null) {
              initCard(cardView, canDismiss);
  
              ViewGroup.LayoutParams param = cardView.getLayoutParams();
              if(param == null)
                  param = generateDefaultLayoutParams();
  
              super.addView(cardView, -1, param);
          }
      }
  
      @Override
      public void addView(View child, int index, ViewGroup.LayoutParams params) {
          if (child.getParent() == null) {
              initCard(child, true);
              super.addView(child, index, params);
          }
      }
  
      @TargetApi(Build.VERSION_CODES.HONEYCOMB)
      @Override
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
          super.onLayout(changed, l, t, r, b);
          Log.d(TAG, "onLayout: " + changed);
  
          if( changed && !mLayouted ){
              mLayouted = true;
  
              ObjectAnimator animator;
              LayoutTransition layoutTransition = new LayoutTransition();
  
              animator = mAnimators.getDisappearingAnimator(getContext());
              layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animator);
  
              animator = mAnimators.getAppearingAnimator(getContext());
              layoutTransition.setAnimator(LayoutTransition.APPEARING, animator);
  
              layoutTransition.addTransitionListener(mTransitionListener);
  
              if( animator != null )
                  layoutTransition.setDuration(animator.getDuration());
  
              setLayoutTransition(layoutTransition);
  
              if( mShowInitialAnimation )
                  runInitialAnimations();
  
              if (mFirstVisibleCardTag != null) {
                  scrollToCard(mFirstVisibleCardTag);
                  mFirstVisibleCardTag = null;
              }
          }
      }
  
      /**
       * Check whether a user moved enough distance to start a swipe action or not.
       *
       * @param deltaX
       * @param deltaY
       * @return true if a user is swiping.
       */
      protected boolean isSwiping(float deltaX, float deltaY) {
  
          if (mSwipeSlop < 0) {
              //get swipping slop from ViewConfiguration;
              mSwipeSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
          }
  
          boolean swipping = false;
          float absDeltaX = Math.abs(deltaX);
  
          if( absDeltaX > mSwipeSlop )
              return true;
  
          return swipping;
      }
  
      /**
       * Swipe a view by moving distance
       *
       * @param child a target view
       * @param deltaX x moving distance by x-axis.
       * @param deltaY y moving distance by y-axis.
       */
      @TargetApi(Build.VERSION_CODES.HONEYCOMB)
      protected void swipeView(View child, float deltaX, float deltaY) {
          if (isFixedView(child)){
              deltaX = deltaX / 4;
          }
  
          float deltaXAbs = Math.abs(deltaX);
          float fractionCovered = deltaXAbs / (float) child.getWidth();
  
          child.setTranslationX(deltaX);
          child.setAlpha(1.f - fractionCovered);
  
          if (deltaX > 0)
              child.setRotationY(-15.f * fractionCovered);
          else
              child.setRotationY(15.f * fractionCovered);
      }
  
      protected void notifyOnDismissEvent( View child ){
          if( child == null || mDismissListener == null )
              return;
  
          mDismissListener.onDismiss((String) child.getTag());
      }
  
      /**
       * get the tag of the first visible child in this layout
       *
       * @return tag of the first visible child or null
       */
      public String getFirstVisibleCardTag() {
  
          final int count = getChildCount();
  
          if (count == 0)
              return null;
  
          for (int index = 0; index < count; ++index) {
              //check the position of each view.
              View child = getChildAt(index);
              if (child.getGlobalVisibleRect(mChildRect) == true)
                  return (String) child.getTag();
          }
  
          return null;
      }
  
      /**
       * Set the first visible card of this linear layout.
       *
       * @param tag tag of a card which should already added to this layout.
       */
      public void setFirstVisibleCard(String tag) {
          if (tag == null)
              return; //do nothing.
  
          if (mLayouted) {
              scrollToCard(tag);
          } else {
              //keep the tag for next use.
              mFirstVisibleCardTag = tag;
          }
      }
  
      /**
       * If this flag is set,
       * after finishing initial onLayout event, an initial animation which is defined in DefaultCardStreamAnimator is launched.
       */
      public void triggerShowInitialAnimation(){
          mShowInitialAnimation = true;
      }
  
      @TargetApi(Build.VERSION_CODES.HONEYCOMB)
      public void setCardStreamAnimator( CardStreamAnimator animators ){
  
          if( animators == null )
              mAnimators = new CardStreamAnimator.EmptyAnimator();
          else
              mAnimators = animators;
  
          LayoutTransition layoutTransition = getLayoutTransition();
  
          if( layoutTransition != null ){
              layoutTransition.setAnimator( LayoutTransition.APPEARING,
                      mAnimators.getAppearingAnimator(getContext()) );
              layoutTransition.setAnimator( LayoutTransition.DISAPPEARING,
                      mAnimators.getDisappearingAnimator(getContext()) );
          }
      }
  
      /**
       * set a OnDismissListener which called when user dismiss a card.
       *
       * @param listener
       */
      public void setOnDismissListener(OnDissmissListener listener) {
          mDismissListener = listener;
      }
  
      @TargetApi(Build.VERSION_CODES.HONEYCOMB)
      private void initialize(AttributeSet attrs, int defStyle) {
  
          float speedFactor = 1.f;
  
          if (attrs != null) {
              TypedArray a = getContext().obtainStyledAttributes(attrs,
                      R.styleable.CardStream, defStyle, 0);
  
              if( a != null ){
                  int speedType = a.getInt(R.styleable.CardStream_animationDuration, 1001);
                  switch (speedType){
                      case ANIMATION_SPEED_FAST:
                          speedFactor = 0.5f;
                          break;
                      case ANIMATION_SPEED_NORMAL:
                          speedFactor = 1.f;
                          break;
                      case ANIMATION_SPEED_SLOW:
                          speedFactor = 2.f;
                          break;
                  }
  
                  String animatorName = a.getString(R.styleable.CardStream_animators);
  
                  try {
                      if( animatorName != null )
                          mAnimators = (CardStreamAnimator) getClass().getClassLoader()
                                  .loadClass(animatorName).newInstance();
                  } catch (Exception e) {
                      Log.e(TAG, "Fail to load animator:" + animatorName, e);
                  } finally {
                      if(mAnimators == null)
                          mAnimators = new DefaultCardStreamAnimator();
                  }
                  a.recycle();
              }
          }
  
          mAnimators.setSpeedFactor(speedFactor);
          mSwipeSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
          setOnHierarchyChangeListener(mOnHierarchyChangeListener);
      }
  
      private void initCard(View cardView, boolean canDismiss) {
          resetAnimatedView(cardView);
          cardView.setOnTouchListener(mTouchListener);
          if (!canDismiss)
              mFixedViewList.add(cardView);
      }
  
      private boolean isFixedView(View v) {
          return mFixedViewList.contains(v);
      }
  
      private void resetAnimatedView(View child) {
          child.setAlpha(1.f);
          child.setTranslationX(0.f);
          child.setTranslationY(0.f);
          child.setRotation(0.f);
          child.setRotationY(0.f);
          child.setRotationX(0.f);
          child.setScaleX(1.f);
          child.setScaleY(1.f);
      }
  
      @TargetApi(Build.VERSION_CODES.HONEYCOMB)
      private void runInitialAnimations() {
          if( mAnimators == null )
              return;
  
          final int count = getChildCount();
  
          for (int index = 0; index < count; ++index) {
              final View child = getChildAt(index);
              ObjectAnimator animator =  mAnimators.getInitalAnimator(getContext());
              if( animator != null ){
                  animator.setTarget(child);
                  animator.start();
              }
          }
      }
  
      private void runShowActionAreaAnimation(View parent, View area) {
          area.setPivotY(0.f);
          area.setPivotX(parent.getWidth() / 2.f);
  
          area.setAlpha(0.5f);
          area.setRotationX(-90.f);
          area.animate().rotationX(0.f).alpha(1.f).setDuration(400);
      }
  
      private void handleViewSwipingOut(final View child, float deltaX, float deltaY) {
          ObjectAnimator animator = mAnimators.getSwipeOutAnimator(child, deltaX, deltaY);
          if( animator != null ){
              animator.addListener(new EndAnimationWrapper() {
                  @Override
                  public void onAnimationEnd(Animator animation) {
                      removeView(child);
                      notifyOnDismissEvent(child);
                  }
              });
          } else {
              removeView(child);
              notifyOnDismissEvent(child);
          }
  
          if( animator != null ){
              animator.setTarget(child);
              animator.start();
          }
      }
  
      private void handleViewSwipingIn(final View child, float deltaX, float deltaY) {
          ObjectAnimator animator = mAnimators.getSwipeInAnimator(child, deltaX, deltaY);
          if( animator != null ){
              animator.addListener(new EndAnimationWrapper() {
                  @Override
                  public void onAnimationEnd(Animator animation) {
                      child.setTranslationY(0.f);
                      child.setTranslationX(0.f);
                  }
              });
          } else {
              child.setTranslationY(0.f);
              child.setTranslationX(0.f);
          }
  
          if( animator != null ){
              animator.setTarget(child);
              animator.start();
          }
      }
  
      private void scrollToCard(String tag) {
  
  
          final int count = getChildCount();
          for (int index = 0; index < count; ++index) {
              View child = getChildAt(index);
  
              if (tag.equals(child.getTag())) {
  
                  ViewParent parent = getParent();
                  if( parent != null && parent instanceof ScrollView ){
                      ((ScrollView)parent).smoothScrollTo(
                              0, child.getTop() - getPaddingTop() - child.getPaddingTop());
                  }
                  return;
              }
          }
      }
  
      public interface OnDissmissListener {
          public void onDismiss(String tag);
      }
  
      /**
       * Empty default AnimationListener
       */
      private abstract class EndAnimationWrapper implements Animator.AnimatorListener {
  
          @Override
          public void onAnimationStart(Animator animation) {
          }
  
          @Override
          public void onAnimationCancel(Animator animation) {
          }
  
          @Override
          public void onAnimationRepeat(Animator animation) {
          }
      }//end of inner class
  }
  