diff --git a/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/CardStackLayoutManager.java b/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/CardStackLayoutManager.java index 261ce797..daf06be0 100644 --- a/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/CardStackLayoutManager.java +++ b/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/CardStackLayoutManager.java @@ -1,17 +1,21 @@ package com.yuyakaido.android.cardstackview; +import android.animation.Animator; import android.content.Context; import android.graphics.PointF; import android.os.Handler; import android.view.View; import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; import android.view.animation.Interpolator; import androidx.annotation.FloatRange; import androidx.annotation.IntRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; +import com.yuyakaido.android.cardstackview.internal.CardStackAnimatorListener; import com.yuyakaido.android.cardstackview.internal.CardStackSetting; import com.yuyakaido.android.cardstackview.internal.CardStackSmoothScroller; import com.yuyakaido.android.cardstackview.internal.CardStackState; @@ -26,16 +30,18 @@ public class CardStackLayoutManager private final Context context; private CardStackListener listener = CardStackListener.DEFAULT; - private CardStackSetting setting = new CardStackSetting(); + private CardStackSetting setting; private CardStackState state = new CardStackState(); public CardStackLayoutManager(Context context) { this(context, CardStackListener.DEFAULT); + this.setting = new CardStackSetting(context.getResources()); } public CardStackLayoutManager(Context context, CardStackListener listener) { this.context = context; this.listener = listener; + this.setting = new CardStackSetting(context.getResources()); } @Override @@ -276,6 +282,7 @@ private void update(RecyclerView.Recycler recycler) { if (state.topPosition == state.targetPosition) { state.targetPosition = RecyclerView.NO_POSITION; } + state.isLastChildWasAnimated = false; /* Handlerを経由してイベント通知を行っているのは、以下のエラーを回避するため * @@ -361,45 +368,101 @@ private void updateTranslation(View view) { view.setTranslationY(state.dy); } - private void updateTranslation(View view, int index) { + private void updateTranslation(View view, final int index) { + boolean isLastVisibleElement = index == setting.visibleCount - 1; + if (state.isLastChildOnAnimation && isLastVisibleElement) { + return; + } + int nextIndex = index - 1; int translationPx = DisplayUtil.dpToPx(context, setting.translationInterval); float currentTranslation = index * translationPx; float nextTranslation = nextIndex * translationPx; float targetTranslation = currentTranslation - (currentTranslation - nextTranslation) * state.getRatio(); + boolean needToStartAnimation = needToStartAnimation(index); + Float translationX = null; + Float translationY = null; + switch (setting.stackFrom) { case None: // Do nothing break; case Top: - view.setTranslationY(-targetTranslation); + translationY = -targetTranslation; break; case TopAndLeft: - view.setTranslationY(-targetTranslation); - view.setTranslationX(-targetTranslation); + translationY = -targetTranslation; + translationX = -targetTranslation; break; case TopAndRight: - view.setTranslationY(-targetTranslation); - view.setTranslationX(targetTranslation); + translationY = -targetTranslation; + translationX = targetTranslation; break; case Bottom: - view.setTranslationY(targetTranslation); + translationY = targetTranslation; break; case BottomAndLeft: - view.setTranslationY(targetTranslation); - view.setTranslationX(-targetTranslation); + translationY = targetTranslation; + translationX = -targetTranslation; break; case BottomAndRight: - view.setTranslationY(targetTranslation); - view.setTranslationX(targetTranslation); + translationY = targetTranslation; + translationX = targetTranslation; break; case Left: - view.setTranslationX(-targetTranslation); + translationX = -targetTranslation; break; case Right: - view.setTranslationX(targetTranslation); + translationX = targetTranslation; break; } + + if (needToStartAnimation) { + startAnimation(view, translationX, translationY); + } else { + setTranslation(view, translationX, translationY); + } + } + + private void startAnimation(@NonNull View view, @Nullable Float translationX, @Nullable Float translationY) { + ViewPropertyAnimator animator = view.animate(); + + if (translationX != null) { + animator.translationX(translationX); + } + + if (translationY != null) { + animator.translationY(translationY); + } + + animator.setDuration(setting.lastItemAppearingAnimationDuration).setListener(new CardStackAnimatorListener() { + + @Override + public void onAnimationStart(Animator animation) { + state.isLastChildOnAnimation = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + state.isLastChildOnAnimation = false; + state.isLastChildWasAnimated = true; + } + }); + } + + private boolean needToStartAnimation(int index) { + boolean isLastElement = index == setting.visibleCount - 1; + return !state.isLastChildOnAnimation && isLastElement && !state.isLastChildWasAnimated; + } + + private void setTranslation(@NonNull View view, @Nullable Float translationX, @Nullable Float translationY) { + if (translationX != null) { + view.setTranslationX(translationX); + } + + if (translationY != null) { + view.setTranslationY(translationY); + } } private void resetTranslation(View view) { @@ -637,4 +700,7 @@ public void setOverlayInterpolator(@NonNull Interpolator overlayInterpolator) { setting.overlayInterpolator = overlayInterpolator; } + public void setLastItemAppearingAnimationDuration(@NonNull Integer duration) { + setting.lastItemAppearingAnimationDuration = duration; + } } diff --git a/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/internal/CardStackAnimatorListener.java b/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/internal/CardStackAnimatorListener.java new file mode 100644 index 00000000..69ebc411 --- /dev/null +++ b/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/internal/CardStackAnimatorListener.java @@ -0,0 +1,26 @@ +package com.yuyakaido.android.cardstackview.internal; + +import android.animation.Animator; + +public abstract class CardStackAnimatorListener implements Animator.AnimatorListener { + + @Override + public void onAnimationStart(Animator animation, boolean isReverse) { + // not need to implement + } + + @Override + public void onAnimationEnd(Animator animation, boolean isReverse) { + // not need to implement + } + + @Override + public void onAnimationCancel(Animator animation) { + // not need to implement + } + + @Override + public void onAnimationRepeat(Animator animation) { + // not need to implement + } +} \ No newline at end of file diff --git a/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/internal/CardStackSetting.java b/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/internal/CardStackSetting.java index 80aa20f1..1d3bcd7e 100644 --- a/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/internal/CardStackSetting.java +++ b/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/internal/CardStackSetting.java @@ -1,17 +1,21 @@ package com.yuyakaido.android.cardstackview.internal; +import android.content.res.Resources; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; - import com.yuyakaido.android.cardstackview.Direction; import com.yuyakaido.android.cardstackview.RewindAnimationSetting; import com.yuyakaido.android.cardstackview.StackFrom; import com.yuyakaido.android.cardstackview.SwipeAnimationSetting; import com.yuyakaido.android.cardstackview.SwipeableMethod; - import java.util.List; public class CardStackSetting { + + public CardStackSetting(Resources resources) { + lastItemAppearingAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime); + } + public StackFrom stackFrom = StackFrom.None; public int visibleCount = 3; public float translationInterval = 8.0f; @@ -25,4 +29,5 @@ public class CardStackSetting { public SwipeAnimationSetting swipeAnimationSetting = new SwipeAnimationSetting.Builder().build(); public RewindAnimationSetting rewindAnimationSetting = new RewindAnimationSetting.Builder().build(); public Interpolator overlayInterpolator = new LinearInterpolator(); + public Integer lastItemAppearingAnimationDuration; } diff --git a/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/internal/CardStackState.java b/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/internal/CardStackState.java index 59254982..53b4694d 100644 --- a/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/internal/CardStackState.java +++ b/cardstackview/src/main/java/com/yuyakaido/android/cardstackview/internal/CardStackState.java @@ -13,6 +13,8 @@ public class CardStackState { public int topPosition = 0; public int targetPosition = RecyclerView.NO_POSITION; public float proportion = 0.0f; + public Boolean isLastChildOnAnimation = false; + public Boolean isLastChildWasAnimated = false; public enum Status { Idle, @@ -105,5 +107,4 @@ public boolean canScrollToPosition(int position, int itemCount) { } return true; } - } diff --git a/sample/src/main/java/com/yuyakaido/android/cardstackview/sample/MainActivity.kt b/sample/src/main/java/com/yuyakaido/android/cardstackview/sample/MainActivity.kt index 6ea5ea48..dac80040 100644 --- a/sample/src/main/java/com/yuyakaido/android/cardstackview/sample/MainActivity.kt +++ b/sample/src/main/java/com/yuyakaido/android/cardstackview/sample/MainActivity.kt @@ -148,6 +148,8 @@ class MainActivity : AppCompatActivity(), CardStackListener { manager.setCanScrollVertical(true) manager.setSwipeableMethod(SwipeableMethod.AutomaticAndManual) manager.setOverlayInterpolator(LinearInterpolator()) + manager.setLastItemAppearingAnimationDuration(150) + manager.setStackFrom(StackFrom.Bottom) cardStackView.layoutManager = manager cardStackView.adapter = adapter cardStackView.itemAnimator.apply {