{"record":{"id":"027c28557f963aab","repo":"material-components/material-components-android","slug":"keylines-being-linearly-interpolated-must-have-the","errorCode":null,"errorMessage":"Keylines being linearly interpolated must have the same item size.","messagePattern":"Keylines being linearly interpolated must have the same item size\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"lib/java/com/google/android/material/carousel/KeylineState.java","lineNumber":187,"sourceCode":"    return keylines.size() - anchorKeylines;\n  }\n\n  /** Returns the size of the carousel used to build this keyline state. */\n  int getCarouselSize() {\n    return carouselSize;\n  }\n\n  /**\n   * Linearly interpolate between two {@link KeylineState}s.\n   *\n   * @param from the start keyline state\n   * @param to the end keyline state\n   * @param progress the interpolation between from and to. When progress is 0, from will be\n   *     returned. When progress is 1, to will be returned.\n   */\n  static KeylineState lerp(KeylineState from, KeylineState to, float progress) {\n    if (from.getItemSize() != to.getItemSize()) {\n      throw new IllegalArgumentException(\n          \"Keylines being linearly interpolated must have the same item size.\");\n    }\n    List<Keyline> fromKeylines = from.getKeylines();\n    List<Keyline> toKeylines = to.getKeylines();\n    if (fromKeylines.size() != toKeylines.size()) {\n      throw new IllegalArgumentException(\n          \"Keylines being linearly interpolated must have the same number of keylines.\");\n    }\n\n    List<Keyline> keylines = new ArrayList<>();\n    for (int i = 0; i < from.getKeylines().size(); i++) {\n      keylines.add(Keyline.lerp(fromKeylines.get(i), toKeylines.get(i), progress));\n    }\n\n    int focalKeylineFirstIndex =\n        AnimationUtils.lerp(\n            from.getFirstFocalKeylineIndex(), to.getFirstFocalKeylineIndex(), progress);\n    int focalKeylineLastIndex =","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/material-components/material-components-android/blob/ac7e18efeefb331850c561faf9ab8bf81d27ba68/lib/java/com/google/android/material/carousel/KeylineState.java#L169-L205","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["In custom CarouselStrategy subclasses, always rebuild KeylineStates from the current Carousel object in onChild initView — never cache and return states across size changes.","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.","Update to the latest patch release if the mismatch originates from library-internal state refresh bugs rather than your strategy."],"exampleFix":"// before (custom strategy)\nprivate KeylineState cached;\n@Override\nprotected KeylineState onCalculate(Carousel carousel) {\n  if (cached != null) return cached; // stale after resize -> lerp mismatch\n  ...\n}\n\n// after\n@Override\nprotected KeylineState onCalculate(Carousel carousel) {\n  return create(self, carousel, /* item size from current carousel */ carousel.getCarouselLayoutManager() ...);\n}","handlingStrategy":"validation","validationCode":"if (from.getItemSize() == to.getItemSize()) {\n  KeylineState.lerp(from, to, progress);\n} else {\n  to = rebuildStateWithItemSize(to, from.getItemSize());\n}","typeGuard":null,"tryCatchPattern":"try { KeylineState.lerp(from, to, progress); } catch (IllegalArgumentException e) { Log.w(TAG, \"item size mismatch; jumping to target state\"); return to; }","preventionTips":["Never cache KeylineStates across container size changes in custom strategies.","Derive item size from the live Carousel on every onCalculate.","Log from/to item sizes in debug builds to catch stale-state producers early."],"tags":["android","material-components","carousel","keylines","animation","custom-strategy"],"backgroundTag":null,"analyzedSha":"ac7e18efeefb331850c561faf9ab8bf81d27ba68","analyzedAt":"2026-08-14T15:35:35.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}