agentsclimarketplace

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).

Install
npx -y skills add almasumdev/awesome-mobile-performance-agent-skills --skill overdraw-and-layout

Assembled 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 overdrawShow 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 = true on views whose background is fully opaque.
  • Android: remove android:background from 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 ConstraintLayout over nested LinearLayout/RelativeLayout.
  • Replace ScrollView > LinearLayout > N children with RecyclerView.
  • Use <merge> in reusable layouts to collapse a pointless root.

Compose:

  • Avoid nesting Box inside Box inside Box. Use Modifier.padding/background/clickable instead of wrapper Box.
  • Prefer LazyColumn/LazyRow over Column { items.forEach { ... } }.

SwiftUI:

  • LazyVStack instead of VStack for 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 single Padding + direct child.
  • Use Flex/Spacer/Expanded instead of multiple SizedBox.
  • Use RepaintBoundary around parts that repaint independently (e.g., a ticking clock above a static feed).

React Native:

  • Collapse wrapper <View> layers. Each native View adds a shadow node and a native view.
  • Use collapsable={true} hints and Fabric's view flattening.
  • flex: 1 everywhere 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 RecyclerView with DiffUtil or ListAdapter. Set setHasFixedSize(true) if bounds do not change with content.
  • Compose: LazyColumn with stable key = { it.id } and contentType when heterogeneous.
  • SwiftUI: List or LazyVStack(pinnedViews:). Provide id: explicitly; avoid UUID() per render.
  • Flutter: ListView.builder with itemExtent/prototypeItem for uniform rows.
  • RN: FlatList with keyExtractor, 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, derivedStateOf for derived values, stable key/contentType in lazy lists.
  • SwiftUI: no AnyView on hot paths; large leaves are equatable.
  • Flutter: const constructors; RepaintBoundary around independently repainting subtrees.
  • RN: memoized rows, stable callbacks, FlatList/FlashList configured with keyExtractor and getItemLayout.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.