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

Keylines that are marked as focal must all have the same mas

Error message

Keylines that are marked as focal must all have the same masked item size.

What it means

Every focal keyline in a KeylineState must share the same masked item size (KeylineState.java:409): when a focal keyline's maskedItemSize differs from the first focal keyline's (tmpFirstFocalKeyline.maskedItemSize), the builder throws. Focal items are the unmasked, full-size items; a focal run with mixed sizes would make 'focal' meaningless for scroll-snap and focal-range calculations.

Source

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

        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) {
          throw new IllegalArgumentException(
              "Keylines marked as focal must be placed next to each other. There cannot be"
                  + " non-focal keylines between focal keylines.");
        }
        if (maskedItemSize != tmpFirstFocalKeyline.maskedItemSize) {
          throw new IllegalArgumentException(
              "Keylines that are marked as focal must all have the same masked item size.");
        }
        tmpLastFocalKeyline = tmpKeyline;
        lastFocalKeylineIndex = tmpKeylines.size();
      } else {
        if (tmpFirstFocalKeyline == null && tmpKeyline.maskedItemSize < lastKeylineMaskedSize) {
          throw new IllegalArgumentException(
              "Keylines before the first focal keyline must be ordered by incrementing masked item"
                  + " size.");
        } else if (tmpLastFocalKeyline != null
            && tmpKeyline.maskedItemSize > lastKeylineMaskedSize) {
          throw new IllegalArgumentException(
              "Keylines after the last focal keyline must be ordered by decreasing masked item"
                  + " size.");
        }
      }
      lastKeylineMaskedSize = tmpKeyline.maskedItemSize;
      tmpKeylines.add(tmpKeyline);

View on GitHub (pinned to ac7e18efee)

Solutions

  1. Flag exactly one masked size as focal; keylines with other sizes must be non-focal.
  2. If sizes are computed, derive them from the same itemSize constant so float equality holds (maskedItemSize for focal keylines should equal the unmasked item size).
  3. Check a hero-style strategy (HeroCarouselStrategy) for the correct pattern when you need varied center sizes.

Example fix

// before
builder.addKeyline(o1, 0f, itemSize, true, false);
builder.addKeyline(o2, 0f, itemSize * 0.8f, true, false); // throws: focal sizes differ

// after
builder.addKeyline(o1, 0f, itemSize, true, false);
builder.addKeyline(o2, 0.2f, itemSize * 0.8f, false, false); // non-focal, different size OK
Defensive patterns

Strategy: validation

Validate before calling

boolean focal = Math.abs(maskedItemSize - itemSize) < 0.01f; // single focal size only
builder.addKeyline(offset, mask, maskedItemSize, focal, anchor);

Prevention

When it happens

Trigger: Custom CarouselStrategy marking keylines with different masked sizes (e.g. a large 300dp item and a medium 240dp item) as isFocal=true in the same builder. Any per-index size variation combined with isFocal=true triggers it as soon as the second, differently-sized focal keyline is added.

Common situations: Strategies that vary item size across the center of the carousel (hero-style layouts) but forget that only one size may be focal — in HeroCarouselStrategy-like layouts the large item is the focal and other sizes must not be flagged focal. Arithmetic bugs producing slightly different masked sizes (float rounding on different offsets) can also cause inequality.

Related errors


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