yuliskov/SmartTube · error · IllegalStateException

Cannot settleCapturedViewAt outside of a call to Callback#on

Error message

Cannot settleCapturedViewAt outside of a call to Callback#onViewReleased

What it means

settleCapturedViewAt() animates the currently captured view to its resting position using the release velocity tracked by the helper. ViewDragHelper sets mReleaseInProgress only while dispatching Callback#onViewReleased; calling settle outside that window means there is no captured view/velocity to settle, so it throws IllegalStateException.

Source

Thrown at slidableactivity/src/main/java/com/r0adkll/slidr/util/ViewDragHelper.java:526

            mCapturedView = null;
        }
        return continueSliding;
    }

    /**
     * Settle the captured view at the given (left, top) position.
     * The appropriate velocity from prior motion will be taken into account.
     * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
     * on each subsequent frame to continue the motion until it returns false. If this method
     * returns false there is no further work to do to complete the movement.
     *
     * @param finalLeft Settled left edge position for the captured view
     * @param finalTop  Settled top edge position for the captured view
     * @return true if animation should continue through {@link #continueSettling(boolean)} calls
     */
    public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
        if (!mReleaseInProgress) {
            throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " +
                    "Callback#onViewReleased");
        }
        return forceSettleCapturedViewAt(finalLeft, finalTop,
                (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
                (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId));
    }

    /**
     * Settle the captured view at the given (left, top) position.
     *
     * @param finalLeft Target left position for the captured view
     * @param finalTop  Target top position for the captured view
     * @param xvel      Horizontal velocity
     * @param yvel      Vertical velocity
     * @return true if animation should continue through {@link #continueSettling(boolean)} calls
     */
    private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
        final int startLeft = mCapturedView.getLeft();

View on GitHub (pinned to 3de8d90593)

Solutions

  1. Move the settleCapturedViewAt call inside Callback#onViewReleased — that is the only sanctioned window.
  2. For programmatic moves outside a release, use smoothSlideViewTo(child, finalLeft, finalTop) (no such gate) and drive it with continueSettling(true) each frame.
  3. Keep a reference to the draggable child so smoothSlideViewTo can target it when no view is captured.

Example fix

// before — outside any release
 closeButton.setOnClickListener(v ->
     mDragHelper.settleCapturedViewAt(getWidth(), getTop())); // throws IllegalStateException

// after — programmatic move uses smoothSlideViewTo
 closeButton.setOnClickListener(v -> {
     mDragHelper.smoothSlideViewTo(mDraggableChild, getWidth(), mDraggableChild.getTop());
     ViewCompat.postInvalidateOnAnimation(mParentView);
 });

 // settle stays inside the release callback only
 @Override public void onViewReleased(View child, float xvel, float yvel) {
     mDragHelper.settleCapturedViewAt(targetLeft(child, xvel), child.getTop());
     ViewCompat.postInvalidateOnAnimation(mParentView);
 }
Defensive patterns

Strategy: validation

Validate before calling

// legal window only: inside Callback#onViewReleased
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
    if (mDragHelper.getCapturedView() != null) {
        mDragHelper.settleCapturedViewAt(targetLeft(releasedChild, xvel), releasedChild.getTop());
        ViewCompat.postInvalidateOnAnimation(mParentView);
    }
}

// everywhere else, use the ungated API:
boolean ok = mDragHelper.smoothSlideViewTo(child, finalLeft, finalTop);

Prevention

When it happens

Trigger: Invoking settleCapturedViewAt(...) from a click handler, menu item, animation tick, or any code path other than inside your ViewDragHelper.Callback's onViewReleased(releasedChild, xvel, yvel) override.

Common situations: Adding a 'close' button that tries to snap the sliding panel shut; porting Scroller-based snap logic and reusing the same call; sharing one method between user-release and programmatic resets.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of yuliskov/SmartTube@3de8d90593 (2026-08-22). Data as JSON: /api/errors/7a40a0c2da094dcb. Report an issue: GitHub.