material-components/material-components-android · error · IllegalArgumentException

Keylines being linearly interpolated must have the same item

Error message

Keylines being linearly interpolated must have the same item size.

What it means

KeylineState.lerp (KeylineState.java:187) interpolates between two carousel keyline states position-by-position; a shared item size is a precondition because masked sizes are lerped per keyline and the resulting state must remain internally consistent. If from.getItemSize() != to.getItemSize() it throws IllegalArgumentException. This runs during carousel transitions such as strategy changes or configuration-driven keyline refreshes, so the two states being blended must come from the same layout parameters.

Source

Thrown at lib/java/com/google/android/material/carousel/KeylineState.java:187

    return keylines.size() - anchorKeylines;
  }

  /** Returns the size of the carousel used to build this keyline state. */
  int getCarouselSize() {
    return carouselSize;
  }

  /**
   * Linearly interpolate between two {@link KeylineState}s.
   *
   * @param from the start keyline state
   * @param to the end keyline state
   * @param progress the interpolation between from and to. When progress is 0, from will be
   *     returned. When progress is 1, to will be returned.
   */
  static KeylineState lerp(KeylineState from, KeylineState to, float progress) {
    if (from.getItemSize() != to.getItemSize()) {
      throw new IllegalArgumentException(
          "Keylines being linearly interpolated must have the same item size.");
    }
    List<Keyline> fromKeylines = from.getKeylines();
    List<Keyline> toKeylines = to.getKeylines();
    if (fromKeylines.size() != toKeylines.size()) {
      throw new IllegalArgumentException(
          "Keylines being linearly interpolated must have the same number of keylines.");
    }

    List<Keyline> keylines = new ArrayList<>();
    for (int i = 0; i < from.getKeylines().size(); i++) {
      keylines.add(Keyline.lerp(fromKeylines.get(i), toKeylines.get(i), progress));
    }

    int focalKeylineFirstIndex =
        AnimationUtils.lerp(
            from.getFirstFocalKeylineIndex(), to.getFirstFocalKeylineIndex(), progress);
    int focalKeylineLastIndex =

View on GitHub (pinned to ac7e18efee)

Solutions

  1. In custom CarouselStrategy subclasses, always rebuild KeylineStates from the current Carousel object in onChild initView — never cache and return states across size changes.
  2. Confirm both states passed to lerp derive from the same itemSize calculation; log from.getItemSize()/to.getItemSize() in a debug build to find the stale producer.
  3. Update to the latest patch release if the mismatch originates from library-internal state refresh bugs rather than your strategy.

Example fix

// before (custom strategy)
private KeylineState cached;
@Override
protected KeylineState onCalculate(Carousel carousel) {
  if (cached != null) return cached; // stale after resize -> lerp mismatch
  ...
}

// after
@Override
protected KeylineState onCalculate(Carousel carousel) {
  return create(self, carousel, /* item size from current carousel */ carousel.getCarouselLayoutManager() ...);
}
Defensive patterns

Strategy: validation

Validate before calling

if (from.getItemSize() == to.getItemSize()) {
  KeylineState.lerp(from, to, progress);
} else {
  to = rebuildStateWithItemSize(to, from.getItemSize());
}

Try / catch

try { KeylineState.lerp(from, to, progress); } catch (IllegalArgumentException e) { Log.w(TAG, "item size mismatch; jumping to target state"); return to; }

Prevention

When it happens

Trigger: Blending two KeylineStates built with different carousel item sizes — e.g. one from a CarouselStrategy configured for a small item size and another from a different strategy or a rebuilt state after container size/spacing changed mid-transition. Typically reached via library-internal animation code (KeylineStateList / CarouselLayoutManager) rather than direct app calls, since lerp is package-visible.

Common situations: Custom CarouselStrategy implementations that cache KeylineStates and return stale states built against an old item size after the RecyclerView's dimensions or itemSpacing changed (rotation, foldable posture change, window resize). Because the throw happens inside an animation frame, the crash surfaces far from the code that produced the mismatched states.

Related errors


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