Skip to content
This repository was archived by the owner on Jul 21, 2025. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.adamglin.composeshadow

import android.os.Build
import android.view.View
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.viewinterop.AndroidView

@Composable
fun SoftLayerShadowContainer(content: @Composable () -> Unit) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
AndroidView(
factory = { context ->
ComposeView(context).apply {
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
setContent(content)
}
},
modifier = Modifier.fillMaxSize()
)
} else {
content()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.adamglin.composeshadow

import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.adamglin.composeshadow.utils.spreadScale


/**
* A [NativePaint.setShadowLayer]/[View.LAYER_TYPE_SOFTWARE] layer type based shadow [Modifier].
*
* You must use it with [SoftLayerShadowContainer].
*
* @param radius The shadow radius.
* @param color The shadow color.
* @param shape The shadow shape.
* @param spread The shadow spread.
* @param offset The shadow offset.
* @param isAlphaContentClip Indicates whether the alpha (transparent) content should be clipped to the [shape].
* @return The applied SoftLayerShadow [Modifier].
* @see SoftLayerShadowContainer
* @author GIGAMOLE
*/
fun Modifier.softLayerShadow(
shape: Shape,
radius: Dp = 4.dp,
color: Color = Color.Black.copy(0.25f),
spread: Dp = 4.dp,
offset: DpOffset = DpOffset(4.dp, 4.dp),
): Modifier = this.drawWithCache {
val radiusPx = radius.toPx()
val isRadiusValid = radiusPx > 0.0F
val paint = Paint().apply {
this.color = if (isRadiusValid) {
Color.Transparent
} else {
color
}

asFrameworkPaint().apply {
isDither = true
isAntiAlias = true

if (isRadiusValid) {
setShadowLayer(
radiusPx,
offset.x.toPx(),
offset.y.toPx(),
color.toArgb()
)
}
}
}
val shapeOutline = shape.createOutline(
size = size,
layoutDirection = LayoutDirection.Rtl,
density = this
)
val shapePath = Path().apply {
addOutline(outline = shapeOutline)
}

val drawShadowBlock: DrawScope.() -> Unit = {
drawIntoCanvas { canvas ->
canvas.withSave {
if (isRadiusValid.not()) {
canvas.translate(
dx = offset.x.toPx(),
dy = offset.y.toPx()
)
}

if (spread.value != 0.0F) {
canvas.scale(
sx = spreadScale(
spread = spread.toPx(),
size = size.width
),
sy = spreadScale(
spread = spread.toPx(),
size = size.height
),
pivotX = center.x,
pivotY = center.y
)
}

canvas.drawOutline(
outline = shapeOutline,
paint = paint
)
}
}
}

onDrawBehind {
drawShadowBlock()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.adamglin.composeshadow.utils

/**
* Calculates shadow spread scale.
*
* @param spread The raw spread.
* @param size The X or Y side.
* @return The shadow spread scale.
* @see com.gigamole.composeshadowsplus.rsblur.rsBlurShadow
* @see com.gigamole.composeshadowsplus.softlayer.softLayerShadow
* @author GIGAMOLE
*/
internal fun spreadScale(
spread: Float,
size: Float
): Float = 1.0F + ((spread / size) * 2.0F)
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
#android
androidCompileSdk = "35"
androidMinSdk = "28"
androidMinSdk = "21"
androidTargetSdk = "35"
androidGradlePlugin = "8.6.1"
#androidx
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,51 @@
package com.adamglin.composeshadow.app

import SampleApp
import android.graphics.drawable.shapes.Shape
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.adamglin.composeshadow.SoftLayerShadowContainer
import com.adamglin.composeshadow.softLayerShadow

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
SampleApp()
SoftLayerShadowContainer{
Column {

Box(
modifier = Modifier
.padding(40.dp)
.softLayerShadow(
shape = RoundedCornerShape(4.dp)
)
.requiredSize(100.dp)
.background(Color.White)
)
SampleApp()
}
}
}
}
}
}

Loading