package com.orgzly.android.ui.views; import android.support.v4.view.ViewCompat; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.Transformation; import android.widget.ViewFlipper; import com.orgzly.BuildConfig; import com.orgzly.R; import com.orgzly.android.ui.util.ListViewUtils; import com.orgzly.android.util.LogUtils; import java.util.HashMap; class GesturedListViewItemMenu { private static final String TAG = GesturedListViewItemMenu.class.getName(); private final static Interpolator MENU_OPEN_INTERPOLATOR = new DecelerateInterpolator(); private final static Interpolator MENU_CLOSE_INTERPOLATOR = new DecelerateInterpolator(); private final static int FLING_RIGHT_OUT_ANIMATION = R.anim.slide_out_to_right; private final static int FLING_RIGHT_IN_ANIMATION = R.anim.slide_in_from_left; private final static int FLING_LEFT_OUT_ANIMATION = R.anim.slide_out_to_left; private final static int FLING_LEFT_IN_ANIMATION = R.anim.slide_in_from_right; // private final static int FLING_RIGHT_OUT_ANIMATION = R.anim.fade_out; // private final static int FLING_RIGHT_IN_ANIMATION = R.anim.fade_in; // private final static int FLING_LEFT_OUT_ANIMATION = R.anim.fade_out; // private final static int FLING_LEFT_IN_ANIMATION = R.anim.fade_in; private final int animationDuration; private final float containerHeight; private final long itemId; private final GesturedListView gesturedListView; private ViewGroup containerView; private ViewFlipper flipperView; private HashMap<GesturedListView.Gesture, Integer> gestureMenuMap; private GesturedListViewItemMenuState containerState; private int flipperDisplayedChild; public GesturedListViewItemMenu(long itemId, GesturedListView listView, ViewGroup container, ViewFlipper flipper, HashMap<GesturedListView.Gesture, Integer> gestureMenuMap) { this.animationDuration = listView.getResources().getInteger(R.integer.item_menu_animation_duration); this.containerHeight = listView.getResources().getDimension(R.dimen.item_menu_height); this.itemId = itemId; this.gesturedListView = listView; this.containerView = container; this.flipperView = flipper; this.gestureMenuMap = gestureMenuMap; } public void open(GesturedListView.Gesture gesture) { setFlipperAnimation(gesture); setFlipperDisplayedChild(gesture); if (isContainerViewGone()) { startOpening(); } } /** * Menu that should be opened for this gesture is already opened. */ public boolean isOpenedForGesture(GesturedListView.Gesture gesture) { if (containerView.getVisibility() == View.GONE) { return false; } Integer targetChild = gestureMenuMap.get(gesture); if (targetChild != null && targetChild == flipperView.getDisplayedChild()) { return true; } return false; } private void setFlipperAnimation(GesturedListView.Gesture gesture) { /* * If container is closed, we don't want flipper animation. * Appearance of the container itself will be animated. */ if (isContainerViewGone()) { flipperView.setInAnimation(null); flipperView.setOutAnimation(null); } else { if (gesture == GesturedListView.Gesture.FLING_RIGHT) { flipperView.setOutAnimation(gesturedListView.getContext(), FLING_RIGHT_OUT_ANIMATION); flipperView.setInAnimation(gesturedListView.getContext(), FLING_RIGHT_IN_ANIMATION); } else if (gesture == GesturedListView.Gesture.FLING_LEFT) { flipperView.setOutAnimation(gesturedListView.getContext(), FLING_LEFT_OUT_ANIMATION); flipperView.setInAnimation(gesturedListView.getContext(), FLING_LEFT_IN_ANIMATION); } else { flipperView.setInAnimation(null); flipperView.setOutAnimation(null); } } } private void setFlipperDisplayedChild(GesturedListView.Gesture gesture) { flipperDisplayedChild = gestureMenuMap.get(gesture); flipIfNeeded(flipperDisplayedChild); } private void flipIfNeeded(int child) { if (flipperView.getDisplayedChild() != child) { flipperView.setDisplayedChild(child); } } public boolean isContainerViewGone() { return containerView.getVisibility() == View.GONE && containerView.getAnimation() == null; } public boolean isClosed() { return containerState == GesturedListViewItemMenuState.CLOSED; } private void startOpening() { containerState = GesturedListViewItemMenuState.OPENING; ViewCompat.setHasTransientState(containerView, true); Animation animation; AnimationSet animations = new AnimationSet(false); animations.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { containerState = GesturedListViewItemMenuState.OPENED; ViewCompat.setHasTransientState(containerView, false); } @Override public void onAnimationRepeat(Animation animation) { } }); /* Fade in animation. */ animation = AnimationUtils.loadAnimation(gesturedListView.getContext(), R.anim.fade_in); animation.setInterpolator(MENU_OPEN_INTERPOLATOR); animation.setDuration(animationDuration); animations.addAnimation(animation); /* Height change animation. */ final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) containerView.getLayoutParams(); /* Initial state. */ params.height = 0; containerView.setVisibility(View.VISIBLE); animation = new Animation() { @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); /* New margin. */ // params.bottomMargin = - params.height + (int) ((params.height) * interpolatedTime); params.height = (int) (containerHeight * interpolatedTime); /* Invalidating the layout, to see the made changes. */ containerView.requestLayout(); /* * Make sure entire item is visible while opening the menu. * Does not work well when flinging the list, * as the item keeps getting scrolled to. */ // gesturedListView.smoothScrollToPosition(itemPosition); } }; animation.setInterpolator(MENU_OPEN_INTERPOLATOR); animation.setDuration(animationDuration); animations.addAnimation(animation); containerView.startAnimation(animations); } public void startClosing() { /* Already closed or closing. */ if (containerState == GesturedListViewItemMenuState.CLOSING || containerState == GesturedListViewItemMenuState.CLOSED) { if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "Menu already in CLOSED or CLOSING state"); return; } containerState = GesturedListViewItemMenuState.CLOSING; ViewCompat.setHasTransientState(containerView, true); final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) containerView.getLayoutParams(); Animation animation; AnimationSet animations = new AnimationSet(false); animations.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { closeContainer(params); if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "Menu closed - animation ended"); } @Override public void onAnimationRepeat(Animation animation) { } }); /* Fade out animation. */ animation = AnimationUtils.loadAnimation(gesturedListView.getContext(), R.anim.fade_out); animation.setInterpolator(MENU_CLOSE_INTERPOLATOR); animation.setDuration(animationDuration); animations.addAnimation(animation); /* Margin change animation. */ animation = new Animation() { @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); /* New margin. */ // params.bottomMargin = - (int) ((params.height) * interpolatedTime); params.height = (int) (containerHeight - containerHeight * interpolatedTime); /* Invalidating the layout, to see the made changes. */ containerView.requestLayout(); /* Make sure entire item is visible while opening the menu. */ // ListView.this.smoothScrollToPosition(itemPosition); if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "Closing menu (" + params.height + ")"); } }; animation.setInterpolator(MENU_CLOSE_INTERPOLATOR); animation.setDuration(animationDuration); animations.addAnimation(animation); if (ListViewUtils.isIdVisible(gesturedListView, itemId)) { if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "Menu container visible, starting close animation"); containerView.startAnimation(animations); } else { if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "Menu container NOT visible, closing immediately"); closeContainer(params); } } private void closeContainer(ViewGroup.MarginLayoutParams params) { containerState = GesturedListViewItemMenuState.CLOSED; containerView.setVisibility(View.GONE); params.height = 0; containerView.requestLayout(); ViewCompat.setHasTransientState(containerView, false); } public void updateView(ViewGroup menuContainer, ViewFlipper menuFlipper) { /* Update current menu views for the item. */ containerView = menuContainer; flipperView = menuFlipper; /* Update container. */ switch (containerState) { case OPENED: /* Update flipper. */ // menu.flipperView.setInAnimation(null); // menu.flipperView.setOutAnimation(null); flipIfNeeded(flipperDisplayedChild); containerView.setVisibility(View.VISIBLE); break; case OPENING: break; case CLOSED: containerView.setVisibility(View.GONE); break; case CLOSING: break; } } private enum GesturedListViewItemMenuState { CLOSED, OPENING, OPENED, CLOSING } }