diff --git a/build.gradle b/build.gradle index a20f3e1..49235e5 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:8.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/starview/build.gradle b/starview/build.gradle index 041e14c..39de3d7 100644 --- a/starview/build.gradle +++ b/starview/build.gradle @@ -8,7 +8,7 @@ android { defaultConfig { minSdk 21 - targetSdk 33 + targetSdk 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -48,6 +48,8 @@ dependencies { implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:1.6.0-alpha04" + implementation "androidx.compose.ui:ui-tooling-preview:1.6.0-alpha04" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/starview/src/main/java/com/developerpaul123/starview/StarComposable.kt b/starview/src/main/java/com/developerpaul123/starview/StarComposable.kt new file mode 100644 index 0000000..5338ea6 --- /dev/null +++ b/starview/src/main/java/com/developerpaul123/starview/StarComposable.kt @@ -0,0 +1,189 @@ +package com.developerpaul123.starview + +import android.graphics.CornerPathEffect +import android.graphics.RectF +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.PaintingStyle +import androidx.compose.ui.graphics.asComposePath +import androidx.compose.ui.graphics.drawscope.clipRect +import androidx.compose.ui.graphics.toComposePathEffect +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.developerpaul123.starview.common.FillDirection +import com.developerpaul123.starview.engine.StarEngine + +/** + * Copyright Paul 2023 + * Part of the StarView project + */ +@Composable +fun Star( + points: Int, + outlineColor: Color, + fillColor: Color, + backgroundColor: Color, + fillPercent: Float, + cornerRadius: Float, + outlineThickness: Float, + fillDirection: FillDirection +) { + val effect = CornerPathEffect(cornerRadius).toComposePathEffect() + val fillPaint = remember { + Paint().apply { + pathEffect = effect + color = fillColor + style = PaintingStyle.Fill + } + } + + val outlinePaint = remember { + Paint().apply { + pathEffect = effect + color = outlineColor + style = PaintingStyle.Stroke + strokeWidth = outlineThickness + } + } + + val backgroundPaint = remember { + Paint().apply { + pathEffect = effect + color = backgroundColor + style = PaintingStyle.Fill + } + } + + Box(modifier = Modifier.fillMaxSize()) { + Canvas( + modifier = Modifier + .fillMaxSize() + .defaultMinSize(100.dp, 100.dp), + onDraw = { + val width = size.width + val height = size.height + // get the path + val graphicsPath = + StarEngine.computeStarPath( + points, + width.toInt(), + height.toInt() + ) + val bounds = RectF() + graphicsPath.computeBounds(bounds, true) + + val path = graphicsPath.asComposePath() + + with(drawContext.canvas) { + // draw background + drawPath( + path, + backgroundPaint + ) + + // determine clipping based on fill direction + val left: Float + val right: Float + val top: Float + val bottom: Float + when (fillDirection) { + FillDirection.LeftToRight -> { + left = 0f + right = bounds.width() * fillPercent + top = 0f + bottom = bounds.height() + } + + FillDirection.RightToLeft -> { + left = width * (1 - fillPercent) + right = width + top = 0f + bottom = height + } + + FillDirection.TopToBottom -> { + left = 0f + right = width + top = 0f + bottom = height * fillPercent + } + + FillDirection.BottomToTop -> { + left = 0f + right = bounds.width() + top = height * (1 - fillPercent) + bottom = height + } + + } + clipRect(left, top, right, bottom) { + drawPath( + path, + fillPaint + ) + } + + // draw the outline + drawPath(path, outlinePaint) + } + } + ) + } +} + +@Composable +@Preview +private fun StarPreview() { + LazyVerticalGrid( + columns = GridCells.Adaptive(100.dp), + modifier = Modifier.fillMaxSize(), + // content padding + contentPadding = PaddingValues( + start = 8.dp, + top = 16.dp, + end = 8.dp, + bottom = 8.dp + ), + content = { + items(8) { point -> + Star( + point + 3, + outlineColor = Color(252, 212, 52), + fillColor = Color(252, 182, 52), + backgroundColor = Color.Transparent, + 0.5f, + 80f, + 10f, + fillDirection = FillDirection.LeftToRight + ) + } + } + ) +} + +@Composable +@Preview +private fun SimpleStar() { + Star( + 5, + outlineColor = Color(252, 212, 52), + fillColor = Color(252, 182, 52), + backgroundColor = Color.Transparent, + 0.5f, + 80f, + 20f, + fillDirection = FillDirection.BottomToTop + ) +} \ No newline at end of file diff --git a/starview/src/main/java/com/developerpaul123/starview/StarView.kt b/starview/src/main/java/com/developerpaul123/starview/StarView.kt index edb72a9..697e760 100644 --- a/starview/src/main/java/com/developerpaul123/starview/StarView.kt +++ b/starview/src/main/java/com/developerpaul123/starview/StarView.kt @@ -5,10 +5,11 @@ import android.graphics.* import android.util.AttributeSet import android.view.View import androidx.core.math.MathUtils +import com.developerpaul123.starview.common.FillDirection import com.developerpaul123.starview.engine.StarEngine /** - * Copyright Paul 2021 + * Copyright Paul 2021-23 * Part of the StarView project */ class StarView @JvmOverloads constructor( @@ -17,13 +18,6 @@ class StarView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : View(context, attributeSet, defStyleAttr) { - enum class FillDirection { - LeftToRight, - RightToLeft, - TopToBottom, - BottomToTop - } - /** * @brief Fill color of the star */ diff --git a/starview/src/main/java/com/developerpaul123/starview/common/FillDirection.kt b/starview/src/main/java/com/developerpaul123/starview/common/FillDirection.kt new file mode 100644 index 0000000..b54ed0b --- /dev/null +++ b/starview/src/main/java/com/developerpaul123/starview/common/FillDirection.kt @@ -0,0 +1,12 @@ +package com.developerpaul123.starview.common + +/** + * Copyright Paul 2023 + * Part of the StarView project + */ +enum class FillDirection { + LeftToRight, + RightToLeft, + TopToBottom, + BottomToTop +} diff --git a/starview/src/main/java/com/developerpaul123/starview/engine/StarEngine.kt b/starview/src/main/java/com/developerpaul123/starview/engine/StarEngine.kt index 23e53ad..64c2bfb 100644 --- a/starview/src/main/java/com/developerpaul123/starview/engine/StarEngine.kt +++ b/starview/src/main/java/com/developerpaul123/starview/engine/StarEngine.kt @@ -1,31 +1,35 @@ package com.developerpaul123.starview.engine import android.graphics.Path +import android.graphics.PointF import kotlin.math.PI import kotlin.math.cos import kotlin.math.sin /** - * Copyright Paul 2021 + * Copyright Paul 2021-23 * Part of the StarView project + * + * This class computes the paths that are drawn as stars in the views and composable views. */ class StarEngine { companion object { - fun computeStarPath( + private fun computeStarPoints( numberOfPoints: Int, width: Int, height: Int, - innerToOuterRadiusRatio: Float = 1 / 3f - ): Path { + innerToOuterRadiusRatio: Float + ): List { - val radius = (width / 2) * 0.95 - val path = Path() + val dim = if (width > 0 && height > 0) minOf(width, height) else width + val radius = (dim / 2) * 0.95 val halfPi = PI / 2 val centerX = width / 2f val centerY = height / 2f val numberOfVertices = 2 * numberOfPoints val radianSpacing = (2f * PI) / numberOfVertices + val points = mutableListOf() for (i in 0 until numberOfVertices + 1) { val degreesInRad = i * radianSpacing var radiusModifier = 1f @@ -35,13 +39,30 @@ class StarEngine { val x = centerX + (radius * radiusModifier) * cos(degreesInRad + 3 * halfPi) val y = centerY + (radius * radiusModifier) * sin(degreesInRad + 3 * halfPi) - if (i == 0) { - path.moveTo(x.toFloat(), y.toFloat()) - continue - } - path.lineTo(x.toFloat(), y.toFloat()) + points.add(PointF(x.toFloat(), y.toFloat())) + } + return points + } + fun computeStarPath( + numberOfPoints: Int, + width: Int, + height: Int, + innerToOuterRadiusRatio: Float = 1 / 3f + ): Path { + + val path = Path() + val points = computeStarPoints(numberOfPoints, width, height, innerToOuterRadiusRatio) + var isFirst = true + points.forEach { point -> + if (isFirst) { + path.moveTo(point.x, point.y) + isFirst = false + } else { + path.lineTo(point.x, point.y) + } + } path.close() return path }