Conversation
요약Android 기반 그림자 효과 렌더링 구현을 per-layer 방식에서 drawWithCache 기반 네이티브 캔버스 렌더링으로 교체했습니다. 새로운 구현은 각 레이어에 대해 shadow layer를 설정하고 모서리가 둥근 사각형을 그린 후, 최종적으로 내용을 렌더링합니다. 변경 사항
예상 코드 리뷰 소요 시간🎯 2 (Simple) | ⏱️ ~10 minutes 관련 가능성 있는 PR
추천 검토자
시 🎨
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip CodeRabbit can use OpenGrep to find security vulnerabilities and bugs across 17+ programming languages.OpenGrep is compatible with Semgrep configurations. Add an |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
composeApp/src/androidMain/kotlin/com/konkuk/medicarecall/ui/theme/Effect.android.kt (1)
43-56: 불필요한 drawRoundRect 호출 같아요.
clearShadowLayer()호출 후color = 0(완전 투명)인 paint로 drawRoundRect를 그리는데, 이건 화면에 아무것도 표시되지 않아서 제거해도 될 것 같아요. 혹시 의도된 동작이라면 주석으로 이유를 남겨주시면 좋겠어요.💡 불필요한 코드 제거 제안
} paint.clearShadowLayer() - drawIntoCanvas { canvas -> - val nc = canvas.nativeCanvas - nc.drawRoundRect( - 0f, - 0f, - size.width, - size.height, - radiusPx, - radiusPx, - paint, - ) - } - drawContent() } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@composeApp/src/androidMain/kotlin/com/konkuk/medicarecall/ui/theme/Effect.android.kt` around lines 43 - 56, The drawn transparent rounded rect after paint.clearShadowLayer() is redundant: remove the drawIntoCanvas block that calls canvas.nativeCanvas.drawRoundRect(..., paint) when paint.color == 0 (fully transparent), or if the draw is intentional, add a comment above paint.clearShadowLayer()/drawIntoCanvas explaining the purpose; specifically update the section using paint.clearShadowLayer(), drawIntoCanvas and drawRoundRect to either delete the no-op draw call or document why it must remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@composeApp/src/androidMain/kotlin/com/konkuk/medicarecall/ui/theme/Effect.android.kt`:
- Around line 16-27: The use of Paint.setShadowLayer(...) inside
onDrawWithContent (where paint is created via Paint().asFrameworkPaint()) will
not reliably render under Compose's hardware-accelerated pipeline; replace this
shadow approach by either applying a Compose shadow API (e.g., Modifier.shadow
or Modifier.dropShadow) to the composable that draws group.layers, or force
software/offscreen compositing for just that drawing region by wrapping the
drawing with graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
before calling onDrawWithContent and removing the setShadowLayer call on paint;
update references to paint.setShadowLayer(...) and ensure layer
blur/offset/color values from group.layers are mapped to the chosen Modifier or
the offscreen rendering path.
---
Nitpick comments:
In
`@composeApp/src/androidMain/kotlin/com/konkuk/medicarecall/ui/theme/Effect.android.kt`:
- Around line 43-56: The drawn transparent rounded rect after
paint.clearShadowLayer() is redundant: remove the drawIntoCanvas block that
calls canvas.nativeCanvas.drawRoundRect(..., paint) when paint.color == 0 (fully
transparent), or if the draw is intentional, add a comment above
paint.clearShadowLayer()/drawIntoCanvas explaining the purpose; specifically
update the section using paint.clearShadowLayer(), drawIntoCanvas and
drawRoundRect to either delete the no-op draw call or document why it must
remain.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3a0a013a-6461-4001-9bc3-697c14fb5fc3
📒 Files selected for processing (1)
composeApp/src/androidMain/kotlin/com/konkuk/medicarecall/ui/theme/Effect.android.kt
| val paint = Paint().asFrameworkPaint().apply { | ||
| isAntiAlias = true | ||
| color = 0 | ||
| } | ||
|
|
||
| onDrawWithContent { | ||
| group.layers.forEach { layer -> | ||
| paint.setShadowLayer( | ||
| layer.blurRadius.toPx(), | ||
| layer.offsetX.toPx(), | ||
| layer.offsetY.toPx(), | ||
| layer.color.toArgb(), |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Android setShadowLayer hardware acceleration Compose drawWithCache
💡 Result:
In traditional Android Views, Paint.setShadowLayer for non-text elements (e.g., shapes, paths) is not supported with hardware acceleration enabled (default since API 14). It requires setting the view to software rendering via view.setLayerType(View.LAYER_TYPE_SOFTWARE, null), which can impact performance. Jetpack Compose is hardware accelerated by default. Official docs recommend using built-in shadow APIs like Modifier.shadow(elevation), Modifier.dropShadow(Shadow), or Modifier.innerShadow for shadows, which handle hardware acceleration properly without needing setShadowLayer. However, custom shadow implementations in Compose often use drawBehind or drawWithCache with drawIntoCanvas { paint.asFrameworkPaint.setShadowLayer(...) } to access native Paint.setShadowLayer. This inherits the same limitation: setShadowLayer does not work reliably with hardware acceleration for non-text. - On API 28+ (Pie), some effects like BlurMaskFilter work with HW accel, but setShadowLayer for shapes remains unsupported (per official HW accel docs). - Pre-API 28, effects fail entirely. - Workarounds: Disable HW accel (not recommended, poor perf), use offscreen bitmaps/Pictures rendered in software, multi-layer gradients, or BlurMaskFilter (limited support). Example custom shadow using drawWithCache (from sources, but subject to HW limitations): kotlin fun Modifier.shadowWithClippingShadowLayer( elevation: Dp, shape: Shape = CircleShape, spotColor: Color = Color.Black.copy(alpha = 0.25f) ): Modifier = this.drawWithCache { if (elevation > 0.dp) { val blurRadiusPx = elevation.toPx val dxPx = 0f val dyPx = (elevation * 0.5f).toPx val outline = shape.createOutline(size, layoutDirection, this) val path = Path.apply { addOutline(outline) } val shadowPaint = Paint.apply { asFrameworkPaint.apply { isAntiAlias = false setShadowLayer(blurRadiusPx, dxPx, dyPx, spotColor.toArgb) } } onDrawWithContent { clipPath(path, ClipOp.Difference) { drawIntoCanvas { canvas -> canvas.drawPath(path, shadowPaint) } } drawContent } } else { onDrawWithContent { drawContent } } } Prefer official APIs for compatibility and performance. Use custom setShadowLayer only if necessary, testing across API levels, and consider alternatives like gradients for full HW support.
Citations:
- 1: https://developer.android.com/develop/ui/views/graphics/hardware-accel
- 2: https://stackoverflow.com/questions/17410195/setshadowlayer-android-api-differences
- 3: https://developer.android.com/develop/ui/compose/graphics/draw/shadows
- 4: https://developer.android.com/develop/ui/compose/graphics/draw/modifiers
- 5: https://medium.com/@yuriyskul/different-approaches-to-create-android-style-shadows-with-transparent-containers-in-jetpack-compose-e299a215557e
- 6: https://stackoverflow.com/questions/76907147/jetpack-compose-custom-shadow-using-drawbehind-not-working-as-expected-in-api-26
- 7: https://stackoverflow.com/questions/68996525/x-and-y-offset-blur-opacity-and-colour-of-shadow-in-jetpack-compose
- 8: https://kimmandoo.medium.com/practicing-shadows-in-jetpack-compose-elevation-drop-shadow-0999e4500acb
setShadowLayer()는 Compose의 하드웨어 가속 환경에서 제대로 동작하지 않아요.
Android 공식 문서에서 명시하고 있듯이, setShadowLayer()는 도형이나 경로 같은 텍스트가 아닌 요소에 대해서는 하드웨어 가속 활성 상태에서 지원되지 않습니다. Compose가 기본으로 하드웨어 가속을 사용하기 때문에, 실제 기기에서 그림자가 제대로 보이지 않을 가능성이 높습니다.
Modifier.shadow()나 Modifier.dropShadow() 같은 공식 API를 사용하는 걸 추천하거나, 꼭 필요하면 오프스크린 비트맵 기법을 고려해 봐도 좋아요. graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)을 적용해서 해당 레이어만 소프트웨어 렌더링으로 처리하는 방법도 있습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@composeApp/src/androidMain/kotlin/com/konkuk/medicarecall/ui/theme/Effect.android.kt`
around lines 16 - 27, The use of Paint.setShadowLayer(...) inside
onDrawWithContent (where paint is created via Paint().asFrameworkPaint()) will
not reliably render under Compose's hardware-accelerated pipeline; replace this
shadow approach by either applying a Compose shadow API (e.g., Modifier.shadow
or Modifier.dropShadow) to the composable that draws group.layers, or force
software/offscreen compositing for just that drawing region by wrapping the
drawing with graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
before calling onDrawWithContent and removing the setShadowLayer call on paint;
update references to paint.setShadowLayer(...) and ensure layer
blur/offset/color values from group.layers are mapped to the chosen Modifier or
the offscreen rendering path.
🔗 관련 이슈
📙 작업 설명
📸 스크린샷 또는 시연 영상 (선택)
💬 추가 설명 or 리뷰 포인트 (선택)
Summary by CodeRabbit
릴리스 노트