Last active
          July 6, 2024 22:34 
        
      - 
      
 - 
        
Save akexorcist/7814a32b4bed09216a8fc3a54d7c663f to your computer and use it in GitHub Desktop.  
    Beak Shape for Jetpack Compose or Compose Multiplatform
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | import androidx.annotation.FloatRange | |
| import androidx.compose.ui.geometry.Size | |
| import androidx.compose.ui.graphics.Outline | |
| import androidx.compose.ui.graphics.Path | |
| import androidx.compose.ui.graphics.Shape | |
| import androidx.compose.ui.unit.Density | |
| import androidx.compose.ui.unit.LayoutDirection | |
| import kotlin.math.atan2 | |
| import kotlin.math.cos | |
| import kotlin.math.pow | |
| import kotlin.math.sin | |
| import kotlin.math.sqrt | |
| enum class BeakDirection { | |
| Top, Bottom, Left, Right; | |
| } | |
| class BeakShape( | |
| @FloatRange(from = .0, to = 1.0) private val ratio: Float, | |
| private val direction: BeakDirection = BeakDirection.Top, | |
| ) : Shape { | |
| override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline { | |
| val width = size.width.toDouble() | |
| val height = size.height.toDouble() | |
| val centerX = width / 2 | |
| val centerY = height / 2 | |
| val path = createBeakPath(width.toFloat(), height.toFloat(), centerX.toFloat(), centerY.toFloat(), direction) | |
| return Outline.Generic(path) | |
| } | |
| private fun createBeakPath( | |
| width: Float, | |
| height: Float, | |
| centerX: Float, | |
| centerY: Float, | |
| direction: BeakDirection | |
| ): Path { | |
| val diagonal = when (direction) { | |
| BeakDirection.Left, | |
| BeakDirection.Right -> sqrt(width.pow(2) + centerY.pow(2)) | |
| BeakDirection.Top, | |
| BeakDirection.Bottom -> sqrt(centerX.pow(2) + height.pow(2)) | |
| } | |
| val targetDiagonal = diagonal * (1 - ratio) | |
| val radian = atan2( | |
| when (direction) { | |
| BeakDirection.Left, | |
| BeakDirection.Right -> width | |
| BeakDirection.Top, | |
| BeakDirection.Bottom -> centerX | |
| }, | |
| when (direction) { | |
| BeakDirection.Left, | |
| BeakDirection.Right -> centerY | |
| BeakDirection.Top, | |
| BeakDirection.Bottom -> height | |
| } | |
| ) | |
| val curvePointX = sin(radian) * targetDiagonal | |
| val curvePointY = cos(radian) * targetDiagonal | |
| return Path().apply { | |
| when (direction) { | |
| BeakDirection.Top -> { | |
| moveTo(0f, height) | |
| cubicTo(centerX, 0f, centerX, 0f, (centerX - curvePointX), curvePointY) | |
| cubicTo(centerX, 0f, centerX, 0f, (centerX + curvePointX), curvePointY) | |
| lineTo(width, height) | |
| } | |
| BeakDirection.Bottom -> { | |
| moveTo(0f, 0f) | |
| cubicTo(centerX, height, centerX, height, (centerX - curvePointX), height - curvePointY) | |
| cubicTo(centerX, height, centerX, height, (centerX + curvePointX), height - curvePointY) | |
| lineTo(width, 0f) | |
| } | |
| BeakDirection.Left -> { | |
| moveTo(width, 0f) | |
| cubicTo(0f, centerY, 0f, centerY, curvePointX, (centerY - curvePointY)) | |
| cubicTo(0f, centerY, 0f, centerY, curvePointX, (centerY + curvePointY)) | |
| lineTo(width, height) | |
| } | |
| BeakDirection.Right -> { | |
| moveTo(0f, 0f) | |
| cubicTo(width, centerY, width, centerY, width - curvePointX, (centerY - curvePointY)) | |
| cubicTo(width, centerY, width, centerY, width - curvePointX, (centerY + curvePointY)) | |
| lineTo(0f, height) | |
| } | |
| } | |
| close() | |
| } | |
| } | |
| } | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | @Preview(widthDp = 400, heightDp = 2000) | |
| @Composable | |
| private fun AnyCustomShapePreview() { | |
| val directions = listOf( | |
| BeakDirection.Top, | |
| BeakDirection.Bottom, | |
| BeakDirection.Left, | |
| BeakDirection.Right, | |
| ) | |
| val sizes = listOf( | |
| 70.dp to 100.dp, | |
| 100.dp to 100.dp, | |
| 150.dp to 100.dp, | |
| ) | |
| val ratios = listOf( | |
| 1.0f, | |
| 0.75f, | |
| 0.5f, | |
| 0.25f, | |
| 0.0f, | |
| ) | |
| MaterialTheme { | |
| Column( | |
| modifier = Modifier.background(Color(0xFF111111)), | |
| horizontalAlignment = Alignment.CenterHorizontally, | |
| verticalArrangement = Arrangement.Center, | |
| ) { | |
| directions.forEachIndexed { index, direction -> | |
| Text(text = "$direction", color = Color.White, modifier = Modifier | |
| .align(Alignment.Start) | |
| .offset(x = 92.dp), fontWeight = FontWeight.Bold) | |
| ratios.forEach { ratio -> | |
| Row { | |
| sizes.forEachIndexed { index, (width, height) -> | |
| AnyCustomShapeContainer( | |
| modifier = Modifier | |
| .width(width) | |
| .height(height), | |
| ratio = ratio, | |
| direction = direction, | |
| ) | |
| if (index != sizes.lastIndex) | |
| Spacer(modifier = Modifier.size(16.dp)) | |
| } | |
| } | |
| } | |
| if (index != directions.lastIndex) | |
| Spacer(modifier = Modifier.size(48.dp)) | |
| } | |
| } | |
| } | |
| } | |
| @Composable | |
| fun AnyCustomShapeContainer( | |
| modifier: Modifier, | |
| ratio: Float, | |
| direction: BeakDirection, | |
| ) { | |
| Column { | |
| Spacer(modifier = Modifier.height(8.dp)) | |
| Text( | |
| text = "Ratio = $ratio", | |
| fontSize = 10.sp, | |
| color = Color.White | |
| ) | |
| Spacer(modifier = Modifier.height(4.dp)) | |
| AnyCustomShape(modifier = modifier, ratio = ratio, direction = direction) | |
| Spacer(modifier = Modifier.height(8.dp)) | |
| } | |
| } | |
| @Composable | |
| fun AnyCustomShape( | |
| modifier: Modifier, | |
| ratio: Float, | |
| direction: BeakDirection, | |
| ) { | |
| Spacer( | |
| modifier = modifier | |
| .background( | |
| color = Color.White.copy(alpha = 0.1f) | |
| ) | |
| .background( | |
| color = Color.White, | |
| shape = BeakShape(ratio = ratio, direction = direction), | |
| ) | |
| ) | |
| } | 
      
      
  Author
  
  
      
          
      
      
            akexorcist
  
      
      
      commented 
        Jun 5, 2024 
      
    
  




  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment