material-components/material-components-android · error · IllegalStateException

Scroll bar must contain a child to calculate interpolation.

Error message

Scroll bar must contain a child to calculate interpolation.

What it means

InterpolateOnScrollPositionChangeHelper.updateInterpolationForScreenPosition() throws IllegalStateException when the tracked ScrollView exists but has zero children. The helper computes corner 'healing/growing' interpolation from the offset between the ScrollView and its first child; with no child, childAt(0) would NPE, so the library throws a descriptive state exception instead.

Source

Thrown at lib/java/com/google/android/material/shape/InterpolateOnScrollPositionChangeHelper.java:107

   * @param viewTreeObserver {@link ViewTreeObserver belonging to the {@link View} being
   * interpolated.
   */
  public void stopListeningForScrollChanges(@NonNull ViewTreeObserver viewTreeObserver) {
    viewTreeObserver.removeOnScrollChangedListener(scrollChangedListener);
  }

  /**
   * Updates the {@link MaterialShapeDrawable}'s interpolation based on the {@link View}'s position
   * in the containing {@link ScrollView}.
   */
  public void updateInterpolationForScreenPosition() {
    if (containingScrollView == null) {
      // No scroll view, no healing/growing.
      return;
    }
    if (containingScrollView.getChildCount() == 0) {
      // No container inside scroll view, no healing/growing.
      throw new IllegalStateException(
          "Scroll bar must contain a child to calculate interpolation.");
    }

    containingScrollView.getLocationInWindow(scrollLocation);
    containingScrollView.getChildAt(0).getLocationInWindow(containerLocation);
    int y = shapedView.getTop() - scrollLocation[1] + containerLocation[1];
    int viewHeight = shapedView.getHeight();
    int windowHeight = containingScrollView.getHeight();

    // Off the top of the screen.
    if (y < 0) {
      materialShapeDrawable.setInterpolation(
          Math.max(0f, Math.min(1f, 1f + (float) y / (float) viewHeight)));
      shapedView.invalidate();
    } else if (y + viewHeight > windowHeight) {
      int distanceOffScreen = y + viewHeight - windowHeight;
      materialShapeDrawable.setInterpolation(
          Math.max(0f, Math.min(1f, 1f - (float) distanceOffScreen / (float) viewHeight)));

View on GitHub (pinned to ac7e18efee)

Solutions

  1. Ensure the ScrollView always keeps exactly one container child (a vertical LinearLayout) and swap that container's children instead of removing the container.
  2. Defer attaching the scroll-position listener until the ScrollView content is inflated.
  3. Remove the listener (or null the helper's scroll view reference) before clearing scroll content.

Example fix

<!-- before -->
<ScrollView android:id="@+id/scroll">
    <!-- children swapped dynamically, sometimes empty -->
</ScrollView>

<!-- after -->
<ScrollView android:id="@+id/scroll">
    <LinearLayout android:id="@+id/scrollContent">
        <!-- swap children inside this container; container never removed -->
    </LinearLayout>
</ScrollView>
Defensive patterns

Strategy: validation

Validate before calling

if (scrollView.getChildCount() > 0) {
  helper.updateInterpolationForScreenPosition();
}

Prevention

When it happens

Trigger: Calling setUpdateAccessibilityNotificationType... no — attaching MaterialShapeDrawable interpolation tracking via view.setOnScrollChangeListener / updateInterpolationForScreenPosition() against a ScrollView whose content was removed (e.g. during a swap or before content loads).

Common situations: Corner-treated cards inside a ScrollView whose child is replaced dynamically and briefly removed; calling the update before setContentView/inflation completes; viewpager fragment where the scroll content is cleared on destroy and a pending scroll callback still fires.

Related errors


AI-assisted analysis of material-components/material-components-android@ac7e18efee (2026-08-14). Data as JSON: /api/errors/0edf4ad557876f98. Report an issue: GitHub.