Search code examples
androidanimationtouch-event

How to prevent touching the view until a view dismiss animation function ends?


I have a custom java class which extends FrameLayout that sits ontop of another custom parent view that extends FrameLayout

I have the following function which dismisses the view with animation -

private void dismissCard(final View view, int xPos) {
    view.animate()
        .x(xPos)
        .y(0)
        .setInterpolator(new AccelerateInterpolator())
        .setDuration(DURATION)
        .setListener(new Animator.AnimatorListener() {
          @Override
          public void onAnimationStart(Animator animator) {
            setOnTouchListener(null);
          }
          @Override
          public void onAnimationEnd(Animator animator) {
            ViewGroup viewGroup = (ViewGroup) view.getParent();
            if (viewGroup != null) {
              viewGroup.removeView(view);
            }
          }
          @Override
          public void onAnimationCancel(Animator animator) {
          }
          @Override
          public void onAnimationRepeat(Animator animator) {
          }
        });
  }

The issue is that I have overriden the onTouch() function for moving the view functionality, and for each touch I am getting the parent of the view, which is a custom view, getting all childs of the view and by that determining which view is on the top. So my issue is that while being animated, the animated view is still the one on the top so it can be moved another time until the animation is gone and viewGroup.removeView(view) is being called.

Here is my onTouch() implementation -

@Override
  public boolean onTouch(final View view, MotionEvent motionEvent) {
    TinderStackLayout tinderStackLayout = ((TinderStackLayout) view.getParent());
    TinderCardView topCard = (TinderCardView) tinderStackLayout.getChildAt(tinderStackLayout.getChildCount() - 1);
    if (topCard.equals(view)) {
      switch (motionEvent.getAction()) {
        case MotionEvent.ACTION_DOWN:
          oldX = motionEvent.getX();
          oldY = motionEvent.getY();
          // cancel any current animations
          view.clearAnimation();
          return true;
        case MotionEvent.ACTION_UP:
          if (isCardBeyondLeftBoundary(view)) {
            Log.d("top card dismissed - ", topCard.usernameTextView.getText().toString());
            onCardSwipedListener.send(new TopCardMovedEvent(-(screenWidth)));
            dismissCard(view, -(screenWidth * 2));
          } else if (isCardBeyondRightBoundary(view)) {
            showLawyerContactDetailsFragment(topCard);
            onCardSwipedListener.send(new TopCardMovedEvent(-(screenWidth)));
            resetCard(view);
          } else {
            onCardSwipedListener.send(new TopCardMovedEvent(-(screenWidth)));
            resetCard(view);
          }
          return true;
        case MotionEvent.ACTION_MOVE:
          newX = motionEvent.getX();
          newY = motionEvent.getY();
          dX = newX - oldX;
          dY = newY - oldY;
          float posX = view.getX() + dX;
          onCardSwipedListener.send(new TopCardMovedEvent(-(screenWidth)));
          // Set new position
          view.setX(view.getX() + dX);
          view.setY(view.getY() + dY);
          setCardRotation(view, view.getX());
          updateAlphaOfBadges(posX);
          return true;
        default:
          return super.onTouchEvent(motionEvent);
      }
    }
    return super.onTouchEvent(motionEvent);
  }

What I am missing is a way to block the touch ability while dismissCard() is animating. How can I implement such thing?


Solution

  • Declare a boolean variable(isAnimating) in the class you are animating and set it to true in onAnimationStart and false in onAnimationEnd.

    access this variable in your onTouch

    @Override
      public boolean onTouch(final View view, MotionEvent motionEvent) {
        TinderStackLayout tinderStackLayout = ((TinderStackLayout) view.getParent());
        TinderCardView topCard = (TinderCardView) tinderStackLayout.getChildAt(tinderStackLayout.getChildCount() - 1);
        if (topCard.equals(view)&& !isAnimating) { //access event's only if animation is ended.
          switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
              oldX = motionEvent.getX();
              oldY = motionEvent.getY();
              // cancel any current animations
              view.clearAnimation();
              return true;
            case MotionEvent.ACTION_UP:
              if (isCardBeyondLeftBoundary(view)) {
                Log.d("top card dismissed - ", topCard.usernameTextView.getText().toString());
                onCardSwipedListener.send(new TopCardMovedEvent(-(screenWidth)));
                dismissCard(view, -(screenWidth * 2));
              } else if (isCardBeyondRightBoundary(view)) {
                showLawyerContactDetailsFragment(topCard);
                onCardSwipedListener.send(new TopCardMovedEvent(-(screenWidth)));
                resetCard(view);
              } else {
                onCardSwipedListener.send(new TopCardMovedEvent(-(screenWidth)));
                resetCard(view);
              }
              return true;
            case MotionEvent.ACTION_MOVE:
              newX = motionEvent.getX();
              newY = motionEvent.getY();
              dX = newX - oldX;
              dY = newY - oldY;
              float posX = view.getX() + dX;
              onCardSwipedListener.send(new TopCardMovedEvent(-(screenWidth)));
              // Set new position
              view.setX(view.getX() + dX);
              view.setY(view.getY() + dY);
              setCardRotation(view, view.getX());
              updateAlphaOfBadges(posX);
              return true;
            default:
              return super.onTouchEvent(motionEvent);
          }
        }
        return super.onTouchEvent(motionEvent);
      }
    

    P.S- better solution is to return false when isAnimating is true.In my app i have overriden the dispatch touch in the parent class and don't let any event to reach the parent or any of it's child class.

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
     if (isAnimationOnGoing) {
            return false
     }
     return super.dispatchTouchEvent(ev)
    }