Overdraw and layout
Skill almasumdev/awesome-mobile-performance-agent-skills/.github/skills/rendering/overdraw-and-layout
Agent skills for profiling and optimizing mobile app performance (startup, memory, frame-rate, network).
npx -y skills add almasumdev/awesome-mobile-performance-agent-skills --skill overdraw-and-layoutAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Reduce overdraw, layout depth, and unnecessary recomposition/re-render in native Android, iOS, Compose, SwiftUI, Flutter, and React Native UIs.
SKILL.md
5.8 KB, as published. Nobody here has run it
Overdraw and Layout
Instructions
Overdraw means the GPU painted the same pixel multiple times. Deep layout hierarchies mean the CPU measured and positioned views the user never sees. Both waste the frame budget. This skill covers how to see the problem and how to fix it.
1. Visualize Overdraw
-
Android: Developer Options → Debug GPU overdraw → Show overdraw areas. Blue = 1x (fine), green = 2x, pink = 3x, red = 4x+. Red areas are the target.
-
iOS: Xcode → Debug → View Debugging → Rendering → Color Blended Layers. Green layers are opaque (good), red are blended (expensive). Also Color Misaligned Images and Color Off-screen Rendered.
-
Flutter: in
main:debugPaintLayerBordersEnabled = true; // shows layer boundaries debugRepaintRainbowEnabled = true; // rainbow cycles on every repaint -
React Native: with Flipper → Layout Inspector, enable Show Overdraw on Android device.
2. Fix Overdraw
- Set a single root background color. Remove nested background drawables that repaint the same area.
- Use opaque backgrounds for full-width rows. Blended (translucent) backgrounds force compositing.
- Replace shadow-under-image patterns with a pre-baked PNG or a CAGradientLayer clipped to the corners.
- iOS: set
isOpaque = trueon views whose background is fully opaque. - Android: remove
android:backgroundfrom intermediate ViewGroups; let the window background show through. - Flutter: avoid stacked
Container(decoration: BoxDecoration(color: ...))for no reason.
3. Shrink Layout Depth
A deep hierarchy costs measure + layout passes on every frame.
Android View system:
- Prefer
ConstraintLayoutover nestedLinearLayout/RelativeLayout. - Replace
ScrollView > LinearLayout > N childrenwithRecyclerView. - Use
<merge>in reusable layouts to collapse a pointless root.
Compose:
- Avoid nesting
BoxinsideBoxinsideBox. UseModifier.padding/background/clickableinstead of wrapperBox. - Prefer
LazyColumn/LazyRowoverColumn { items.forEach { ... } }.
SwiftUI:
LazyVStackinstead ofVStackfor large lists.- Flatten
Group { Group { ... } }wrappers; they do not add value. - Avoid
AnyView— it erases types and blocks SwiftUI's diffing.
Flutter:
- Replace
Column(children: [Padding(Padding(Padding(...)))])with a singlePadding+ direct child. - Use
Flex/Spacer/Expandedinstead of multipleSizedBox. - Use
RepaintBoundaryaround parts that repaint independently (e.g., a ticking clock above a static feed).
React Native:
- Collapse wrapper
<View>layers. Each nativeViewadds a shadow node and a native view. - Use
collapsable={true}hints and Fabric's view flattening. flex: 1everywhere can force extra layout passes; set explicit dimensions when known.
4. Minimize Recomposition / Re-render
Compose — only recompose what changed:
@Composable
fun Counter(count: Int) {
// Do NOT compute derived values with Modifier.drawBehind using the raw state
val label = remember(count) { "Count: $count" }
Text(label)
}
@Composable
fun Screen(state: ScreenState) {
// Use derivedStateOf to avoid recomposing on unrelated state fields
val canSubmit by remember(state) { derivedStateOf { state.name.isNotBlank() } }
Button(enabled = canSubmit, onClick = state.onSubmit) { Text("Submit") }
}
Mark classes @Stable / @Immutable when their equality is value-based so Compose can skip recomposition.
SwiftUI — extract subviews so @State changes do not invalidate siblings. Use EquatableView for heavy leaf views.
React (RN) — React.memo rows; keep onPress identity stable with useCallback:
const Row = React.memo(function Row({ item, onPress }: Props) {
return <Pressable onPress={() => onPress(item.id)}><Text>{item.title}</Text></Pressable>;
});
Flutter — prefer const constructors everywhere possible. Use Selector (provider) or select (Riverpod) to subscribe to a slice of state instead of the whole object.
5. Lists at Scale
- Android: use
RecyclerViewwithDiffUtilorListAdapter. SetsetHasFixedSize(true)if bounds do not change with content. - Compose:
LazyColumnwith stablekey = { it.id }andcontentTypewhen heterogeneous. - SwiftUI:
ListorLazyVStack(pinnedViews:). Provideid:explicitly; avoidUUID()per render. - Flutter:
ListView.builderwithitemExtent/prototypeItemfor uniform rows. - RN:
FlatListwithkeyExtractor,getItemLayout,removeClippedSubviews={true}, or migrate to FlashList.
6. Verify with Layout Profilers
- Android Studio → Layout Inspector → shows actual view hierarchy depth and recomposition counts in Compose.
- Xcode → Debug View Hierarchy → see layer counts per screen.
- Flutter DevTools → Widget Inspector → check rebuild counts per widget.
- React DevTools Profiler → commit-by-commit render durations.
Checklist
- Debug overdraw is blue or green on the hot screens; no red zones.
- Layout depth on hot screens ≤ 10 levels (native) or no redundant wrappers.
- Opaque backgrounds marked as such; no stacked redundant backgrounds.
- Compose: stable/immutable models,
derivedStateOffor derived values, stablekey/contentTypein lazy lists. - SwiftUI: no
AnyViewon hot paths; large leaves are equatable. - Flutter:
constconstructors;RepaintBoundaryaround independently repainting subtrees. - RN: memoized rows, stable callbacks,
FlatList/FlashList configured withkeyExtractorandgetItemLayout.