{"record":{"id":"7275948fe3933195","repo":"material-components/material-components-android","slug":"all-children-of-a-recyclerview-using-carousellayou","errorCode":null,"errorMessage":"All children of a RecyclerView using CarouselLayoutManager must use MaskableFrameLayout as their root ViewGroup.","messagePattern":"All children of a RecyclerView using CarouselLayoutManager must use MaskableFrameLayout as their root ViewGroup\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"lib/java/com/google/android/material/carousel/CarouselLayoutManager.java","lineNumber":1028,"sourceCode":"        new RectF(getParentLeft(), getParentTop(), getParentRight(), getParentBottom());\n    // If the carousel is a CONTAINED carousel, ensure the mask collapses against the side of the\n    // container instead of bleeding and being clipped by the RecyclerView's bounds.\n    // Only do this if there is only one side of the mask that is out of bounds; if\n    // both sides are out of bounds on the same side, then the whole mask is out of view.\n    if (carouselStrategy.getStrategyType() == StrategyType.CONTAINED) {\n      orientationHelper.containMaskWithinBounds(maskRect, offsetMaskRect, parentBoundsRect);\n    }\n\n    // 'Push out' any masks that are on the parent edge by rounding up/down and adding or\n    // subtracting a pixel. Otherwise, the mask on the 'edge' looks like it has a width of 1 pixel.\n    orientationHelper.moveMaskOnEdgeOutsideBounds(maskRect, offsetMaskRect, parentBoundsRect);\n    ((Maskable) child).setMaskRectF(maskRect);\n  }\n\n  @Override\n  public void measureChildWithMargins(@NonNull View child, int widthUsed, int heightUsed) {\n    if (!(child instanceof Maskable)) {\n      throw new IllegalStateException(\n          \"All children of a RecyclerView using CarouselLayoutManager must use MaskableFrameLayout\"\n              + \" as their root ViewGroup.\");\n    }\n\n    LayoutParams lp = (LayoutParams) child.getLayoutParams();\n\n    Rect insets = new Rect();\n    calculateItemDecorationsForChild(child, insets);\n    widthUsed += insets.left + insets.right;\n    heightUsed += insets.top + insets.bottom;\n\n    // If the strategy's keyline set is available, use the item size from the keyline set.\n    // Otherwise, measure the item to what it would like to be so the strategy will be given an\n    // opportunity to use this desired size in making it's sizing decision.\n    final float childWidthDimension =\n        keylineStateList != null && orientationHelper.orientation == HORIZONTAL\n            ? keylineStateList.getDefaultState().getItemSize()\n            : lp.width;","sourceCodeStart":1010,"sourceCodeEnd":1046,"githubUrl":"https://github.com/material-components/material-components-android/blob/ac7e18efeefb331850c561faf9ab8bf81d27ba68/lib/java/com/google/android/material/carousel/CarouselLayoutManager.java#L1010-L1046","documentation":"CarouselLayoutManager implements its item masking by casting each RecyclerView child to the Maskable interface (implemented by MaskableFrameLayout) inside measureChildWithMargins (CarouselLayoutManager.java:1028). Any child that is not Maskable makes per-child masking impossible, so the layout manager throws IllegalStateException — the carousel contract requires MaskableFrameLayout as the item root.","triggerScenarios":"Supplying a RecyclerView.Adapter whose item layout root is not MaskableFrameLayout (e.g. a ConstraintLayout, MaterialCardView or FrameLayout root, or a fragment-created view) to a RecyclerView using CarouselLayoutManager. The throw happens during measurement of the first child, i.e. at first layout pass.","commonSituations":"Building a carousel with an existing list item layout instead of the catalog sample layout. Copying the demo adapter but replacing the item XML root with ConstraintLayout 'for convenience'. Also hitting it after refactoring item views into custom views that no longer extend MaskableFrameLayout.","solutions":["Make the item layout root <com.google.android.material.carousel.MaskableFrameLayout> and put the real item content in a child of it.","If a custom root view class is needed, extend MaskableFrameLayout so the Maskable cast still succeeds.","Verify every view type in the adapter (headers/footers included) uses a Maskable root — a single non-maskable view type crashes the whole layout pass."],"exampleFix":"<!-- before -->\n<androidx.constraintlayout.widget.ConstraintLayout ... />\n\n<!-- after -->\n<com.google.android.material.carousel.MaskableFrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n  <androidx.constraintlayout.widget.ConstraintLayout ...>...</androidx.constraintlayout.widget.ConstraintLayout>\n</com.google.android.material.carousel.MaskableFrameLayout>","handlingStrategy":"type-guard","validationCode":"if (view instanceof Maskable) {\n  recyclerView.setLayoutManager(new CarouselLayoutManager());\n} // and ensure every item view root is MaskableFrameLayout before binding","typeGuard":"static boolean isCarouselItemValid(View itemView) {\n  return itemView instanceof Maskable;\n}","tryCatchPattern":null,"preventionTips":["Standardize item layouts on <com.google.android.material.carousel.MaskableFrameLayout> roots.","If a custom root is needed, extend MaskableFrameLayout.","Audit every adapter view type, including headers/footers."],"tags":["android","material-components","carousel","recyclerview","layout"],"backgroundTag":null,"analyzedSha":"ac7e18efeefb331850c561faf9ab8bf81d27ba68","analyzedAt":"2026-08-14T15:35:35.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}