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

Anchor keylines cannot be focal.

Error message

Anchor keylines cannot be focal.

What it means

In KeylineState.Builder's keyline-add path (KeylineState.java:385), a keyline flagged isAnchor=true must not also be isFocal=true, so the builder throws IllegalArgumentException. Anchor keylines are the fixed reference points (typically the fully-revealed edge items) used when interpolating/rebuilding keyline states; focal keylines are the full-size center items. An anchor that is also focal would make the anchor's position depend on scroll focus, defeating its purpose as a stable reference.

Source

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

     * @param cutoff How much the keyline item is out the bounds of the available space.
     */
    @NonNull
    @CanIgnoreReturnValue
    public Builder addKeyline(
        float offsetLoc,
        @FloatRange(from = 0.0F, to = 1.0F) float mask,
        float maskedItemSize,
        boolean isFocal,
        boolean isAnchor,
        float cutoff,
        float leftOrTopPaddingShift,
        float rightOrBottomPaddingShift) {
      if (maskedItemSize <= 0F) {
        return this;
      }
      if (isAnchor) {
        if (isFocal) {
          throw new IllegalArgumentException("Anchor keylines cannot be focal.");
        }
        if (latestAnchorKeylineIndex != NO_INDEX && latestAnchorKeylineIndex != 0) {
          throw new IllegalArgumentException(
              "Anchor keylines must be either the first or last keyline.");
        }
        latestAnchorKeylineIndex = tmpKeylines.size();
      }

      Keyline tmpKeyline =
          new Keyline(UNKNOWN_LOC, offsetLoc, mask, maskedItemSize, isAnchor, cutoff,
              leftOrTopPaddingShift, rightOrBottomPaddingShift);
      if (isFocal) {
        if (tmpFirstFocalKeyline == null) {
          tmpFirstFocalKeyline = tmpKeyline;
          firstFocalKeylineIndex = tmpKeylines.size();
        }

        if (lastFocalKeylineIndex != NO_INDEX && tmpKeylines.size() - lastFocalKeylineIndex > 1) {

View on GitHub (pinned to ac7e18efee)

Solutions

  1. Set isAnchor=true only on the small edge keylines and isFocal=true only on the center full-size keylines — never both on the same keyline.
  2. Model your builder calls on MultiBrowseCarouselStrategy's source, which shows the intended anchor/focal layout.
  3. If copying flag arrays between strategies, re-check each entry: anchors are typically the first and last keylines only.

Example fix

// before
builder.addKeyline(offset, mask, maskedSize, /* isFocal */ true, /* isAnchor */ true);

// after
builder.addKeyline(offset, mask, smallMaskedSize, /* isFocal */ false, /* isAnchor */ true); // edge anchor
builder.addKeyline(offset2, 0f, itemSize, /* isFocal */ true, /* isAnchor */ false); // center focal
Defensive patterns

Strategy: validation

Validate before calling

boolean anchor = isEdgePosition(i);
boolean focal = !anchor && isFullSize(maskedItemSize);
builder.addKeyline(offset, mask, maskedItemSize, focal, anchor);

Prevention

When it happens

Trigger: Calling KeylineState.Builder.addKeyline/addKeylineRange (from a custom CarouselStrategy) with isFocal=true and isAnchor=true for the same keyline — e.g. marking the large center keylines as anchors as well. Note the whole add is skipped (returns this) when maskedItemSize <= 0F, so the throw only occurs for positive-size keylines.

Common situations: Writing a custom CarouselStrategy and passing an isAnchor flag array or hardcoded booleans copied from another strategy without aligning which keylines are anchors vs focal. Unit-testing keyline construction with all-true flag arrays also trips it.

Related errors


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