Skip to content

Instantly share code, notes, and snippets.

@Pooh3Mobi
Last active December 29, 2022 15:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pooh3Mobi/4c11145227f7ab54bb98b30bb95436de to your computer and use it in GitHub Desktop.
Save Pooh3Mobi/4c11145227f7ab54bb98b30bb95436de to your computer and use it in GitHub Desktop.
package com.example.composegraphicslayerplayground
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.example.composegraphicslayerplayground.ui.theme.ComposeGraphicsLayerPlayGroundTheme
import kotlin.math.cos
import kotlin.math.sin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeGraphicsLayerPlayGroundTheme {
var enabled by remember { mutableStateOf(false) }
Tree(
modifier = Modifier
.background(Color.Black)
.fillMaxSize()
.clickable {
enabled = !enabled
},
enabled = enabled,
layerNum = 10,
)
}
}
}
}
const val CameraDistance = 100f
// レイヤー間の距離のスケール
const val LayerDistanceScale = 8f
val circleColor = Color(0x66999999) // LightGray
val baubleRed = Color(0xCCfeab9a)
val baubleGreen = Color(0xCC9afeab)
@Composable
@NonRestartableComposable
fun Tree(
modifier: Modifier = Modifier,
enabled: Boolean,
layerNum: Int = 3,
// 各レイヤーの傾き
layerDegree: Float = 65f
) {
val zIndexScale by animateFloatAsState(
targetValue = if (enabled) LayerDistanceScale else 0f,
animationSpec = tween(
durationMillis = 300,
easing = CubicBezierEasing(0.4f, 0f, 0.2f, 1f)
)
)
val infiniteTransition = rememberInfiniteTransition()
val degree by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(4_000, easing = LinearEasing),
repeatMode = RepeatMode.Restart
)
)
Box(
modifier = modifier
.fillMaxWidth(0.6f),
contentAlignment = Alignment.Center,
) {
(0 .. layerNum).forEach {
val translationY = -15f * (layerNum - it) * zIndexScale
if (it == 0) {
val starSize = with(LocalDensity.current) { 20.dp.toPx() }
Star(
modifier = Modifier
.size(20.dp)
.graphicsLayer(
translationY = translationY - starSize
),
degree = degree,
starSize = starSize,
)
} else {
TreeRing(
layerDegree = layerDegree,
translationY = translationY,
radius = (10 * it).dp,
degree = -degree + (it * 30),
)
}
}
}
}
@Composable
@NonRestartableComposable
fun TreeRing(
modifier: Modifier = Modifier,
layerDegree: Float,
translationY: Float,
radius: Dp,
degree: Float,
) {
val baubleAdjTransY = with(LocalDensity.current) { 5.dp.toPx() }
Box(
modifier,
) {
Ring(
modifier = Modifier
.graphicsLayer(
cameraDistance = CameraDistance,
rotationX = layerDegree,
translationY = translationY ,
)
.size(radius * 2)
)
Baubles(
modifier = Modifier
.size(radius * 2)
.graphicsLayer(
cameraDistance = CameraDistance,
// translationY = translationY + baubleHalfSize,
translationY = translationY - baubleAdjTransY,
)
,
degree = degree,
layerDegree = layerDegree,
radius = radius
)
}
}
@Composable
fun Ring(
modifier: Modifier = Modifier,
) {
Canvas(modifier = modifier) {
drawCircle(
color = circleColor,
style = Stroke(5f),
radius = size.minDimension / 2,
)
}
}
@Composable
fun Baubles(
modifier: Modifier = Modifier,
degree: Float,
layerDegree: Float,
radius: Dp,
) {
val density = LocalDensity.current
val r by remember {
derivedStateOf {
with(density) { (radius + 10.dp).toPx() }
}
}
val radiusX by remember(r) {
mutableStateOf(r)
}
val radiusY by remember {
derivedStateOf {
val d = 90 - layerDegree
(r * sin(d * Math.PI / 180)).toFloat()
}
}
Box(
modifier = modifier,
contentAlignment = Alignment.Center,
) {
data class BaubleParams(
val x: Float,
val y: Float,
val degree: Float,
)
fun computeBaubleParams(
degree: Float,
): BaubleParams {
val x = (radiusX * sin(degree * Math.PI / 180)).toFloat()
val y = (radiusY * cos(degree * Math.PI / 180)).toFloat()
return BaubleParams(x, y, degree)
}
val bauble1 by remember(degree) {
derivedStateOf { computeBaubleParams(degree) }
}
val bauble2 by remember(degree) {
derivedStateOf { computeBaubleParams(degree + 180) }
}
val bauble3 by remember(degree) {
derivedStateOf { computeBaubleParams(degree + 90) }
}
val bauble4 by remember(degree) {
derivedStateOf { computeBaubleParams(degree + 270) }
}
Bauble(
modifier = Modifier
.graphicsLayer(
rotationY = bauble1.degree,
translationX = bauble1.x,
translationY = bauble1.y,
)
.size(16.dp),
type = BaubleType.Red,
)
Bauble(
modifier = Modifier
.graphicsLayer(
rotationY = bauble2.degree,
translationX = bauble2.x,
translationY = bauble2.y,
)
.size(16.dp),
type = BaubleType.Red,
)
Bauble(
modifier = Modifier
.graphicsLayer(
rotationY = bauble3.degree,
translationX = bauble3.x,
translationY = bauble3.y,
)
.size(16.dp),
type = BaubleType.Green,
)
Bauble(
modifier = Modifier
.graphicsLayer(
rotationY = bauble4.degree,
translationX = bauble4.x,
translationY = bauble4.y,
)
.size(16.dp),
type = BaubleType.Green,
)
}
}
@Composable
fun Bauble(
modifier: Modifier = Modifier,
type: BaubleType
) {
// circle bauble
// val color = when (type) {
// BaubleType.Red -> baubleRed
// BaubleType.Green -> baubleGreen
// }
// Canvas(
// modifier = modifier
// .shadow(
// ambientColor = color,
// spotColor = color,
// elevation = 10.5.dp,
// shape = CircleShape,
// clip = true,
// )
// ) {
// drawCircle(
// color = color,
// radius = size.minDimension / 2,
// )
// }
// droid icon
AndroidRobot(modifier = modifier, type = type)
}
enum class BaubleType {
Red, Green
}
@Composable
fun AndroidRobot(
modifier: Modifier = Modifier,
type: BaubleType,
) {
val vector = if (type == BaubleType.Green) {
ImageVector.vectorResource(id = R.drawable.robot_green)
} else {
ImageVector.vectorResource(id = R.drawable.robot_red)
}
val painter = rememberVectorPainter(image = vector)
// drop shadow
val color = when (type) {
BaubleType.Red -> baubleRed
BaubleType.Green -> baubleGreen
}
Canvas(
modifier = modifier
.size(15.dp)
.shadow(
ambientColor = color,
spotColor = color,
elevation = 20.dp,
shape = CircleShape,
clip = false,
)
) {
with(painter) {
draw(painter.intrinsicSize)
}
}
}
@Composable
fun Star(
modifier: Modifier = Modifier,
color: Color = Color.Yellow,
degree: Float,
starSize: Float,
) {
Canvas(
modifier = modifier
.graphicsLayer(
rotationY = -degree,
)
.shadow(
ambientColor = color,
spotColor = color,
elevation = 10.5.dp,
shape = object : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density
): Outline {
return Outline.Generic(
Path().starPath(starSize)
)
}
},
clip = true,
)
) {
val path = Path().starPath(starSize)
drawPath(
path = path,
color = color,
style = Stroke(5f),
)
}
}
fun Path.starPath(radius: Float): Path {
val innerRadius = radius * 0.5f
val outerRadius = radius * 0.95f
val angle: Float = (2.0 * Math.PI / 5f).toFloat()
val halfAngle: Float = angle / 2.0f
val centerX = radius / 2.0f
val centerY = radius / 2.0f
moveTo(
(centerX + cos(0.0f) * outerRadius),
(centerY + sin(0.0f) * outerRadius)
)
for (i in 0 until 5) {
lineTo(
(centerX + cos((i * angle + halfAngle)) * innerRadius),
(centerY + sin((i * angle + halfAngle)) * innerRadius)
)
lineTo(
(centerX + cos((i * angle + angle)) * outerRadius),
(centerY + sin((i * angle + angle)) * outerRadius)
)
}
close()
return this
}
@Preview(showBackground = true)
@Composable
fun TreePreview() {
ComposeGraphicsLayerPlayGroundTheme {
Tree(
modifier = Modifier
.background(Color.Black)
.padding(top = 200.dp)
.fillMaxSize(),
enabled = true,
layerNum = 10,
)
}
}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="800"
android:viewportHeight="742.35">
<path
android:pathData="M76.86,514.44c-5.18,17.1 4.48,35.15 21.57,40.31l40.31,12.23l-31.41,60.24c-8.24,15.81 -2.3,35.25 13.27,43.34l24.66,12.89c15.57,8.09 34.89,1.83 43.15,-14l39.35,-75.46l77.03,23.39l-22.31,73.5c-5.18,17.06 4.27,35.06 21.06,40.16l26.64,8.09c16.78,5.08 34.65,-4.65 39.83,-21.71l22.3,-73.47l27.05,8.21c17.1,5.2 35.15,-4.44 40.34,-21.54l77.46,-255.2l-382.81,-116.2L76.86,514.44zM107.46,245.67l21.75,-8.36c13.7,-5.28 19.65,-23.04 13.29,-39.69L76,24C69.6,7.36 53.29,-1.91 39.57,3.31l-21.72,8.37C4.14,16.92 -1.82,34.71 4.56,51.33L71.06,224.95C77.43,241.61 93.75,250.9 107.46,245.67zM544.96,116.8l-49.1,41.06c-20.37,-20.69 -45.42,-37.38 -74.57,-47.87c-29.88,-10.77 -60.61,-13.81 -90.16,-10.45l-12.33,-62.75c-0.78,-4.06 -4.7,-6.66 -8.69,-5.83c-4.01,0.77 -6.63,4.66 -5.85,8.66l12.23,62.17c-64.51,12.24 -121.42,55.76 -147.89,120.26l376.57,133.28c19.97,-66.69 3.18,-136.37 -39.31,-186.52l48.62,-40.65c3.12,-2.65 3.53,-7.33 0.92,-10.42C552.77,114.57 548.11,114.14 544.96,116.8zM311.06,179.85c-3.81,10.79 -15.67,16.43 -26.45,12.61c-10.78,-3.83 -16.47,-15.67 -12.64,-26.45c3.82,-10.81 15.69,-16.47 26.46,-12.64C309.22,157.19 314.89,169.06 311.06,179.85zM488.31,242.56c-3.82,10.8 -15.64,16.43 -26.46,12.63c-10.78,-3.83 -16.43,-15.67 -12.62,-26.45c3.82,-10.8 15.66,-16.45 26.45,-12.63C486.49,219.92 492.13,231.74 488.31,242.56zM793.66,281.73l-13.69,-18.82c-8.66,-11.88 -27.37,-12.99 -41.81,-2.49L587.9,369.85c-14.44,10.49 -19.15,28.66 -10.49,40.52l13.69,18.8c8.65,11.88 27.37,13.01 41.78,2.51l150.31,-109.4C797.61,311.77 802.32,293.6 793.66,281.73z"
android:fillColor="#CC9afeab"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="800"
android:viewportHeight="742.35">
<path
android:pathData="M76.86,514.44c-5.18,17.1 4.48,35.15 21.57,40.31l40.31,12.23l-31.41,60.24c-8.24,15.81 -2.3,35.25 13.27,43.34l24.66,12.89c15.57,8.09 34.89,1.83 43.15,-14l39.35,-75.46l77.03,23.39l-22.31,73.5c-5.18,17.06 4.27,35.06 21.06,40.16l26.64,8.09c16.78,5.08 34.65,-4.65 39.83,-21.71l22.3,-73.47l27.05,8.21c17.1,5.2 35.15,-4.44 40.34,-21.54l77.46,-255.2l-382.81,-116.2L76.86,514.44zM107.46,245.67l21.75,-8.36c13.7,-5.28 19.65,-23.04 13.29,-39.69L76,24C69.6,7.36 53.29,-1.91 39.57,3.31l-21.72,8.37C4.14,16.92 -1.82,34.71 4.56,51.33L71.06,224.95C77.43,241.61 93.75,250.9 107.46,245.67zM544.96,116.8l-49.1,41.06c-20.37,-20.69 -45.42,-37.38 -74.57,-47.87c-29.88,-10.77 -60.61,-13.81 -90.16,-10.45l-12.33,-62.75c-0.78,-4.06 -4.7,-6.66 -8.69,-5.83c-4.01,0.77 -6.63,4.66 -5.85,8.66l12.23,62.17c-64.51,12.24 -121.42,55.76 -147.89,120.26l376.57,133.28c19.97,-66.69 3.18,-136.37 -39.31,-186.52l48.62,-40.65c3.12,-2.65 3.53,-7.33 0.92,-10.42C552.77,114.57 548.11,114.14 544.96,116.8zM311.06,179.85c-3.81,10.79 -15.67,16.43 -26.45,12.61c-10.78,-3.83 -16.47,-15.67 -12.64,-26.45c3.82,-10.81 15.69,-16.47 26.46,-12.64C309.22,157.19 314.89,169.06 311.06,179.85zM488.31,242.56c-3.82,10.8 -15.64,16.43 -26.46,12.63c-10.78,-3.83 -16.43,-15.67 -12.62,-26.45c3.82,-10.8 15.66,-16.45 26.45,-12.63C486.49,219.92 492.13,231.74 488.31,242.56zM793.66,281.73l-13.69,-18.82c-8.66,-11.88 -27.37,-12.99 -41.81,-2.49L587.9,369.85c-14.44,10.49 -19.15,28.66 -10.49,40.52l13.69,18.8c8.65,11.88 27.37,13.01 41.78,2.51l150.31,-109.4C797.61,311.77 802.32,293.6 793.66,281.73z"
android:fillColor="#CCfeab9a"/>
</vector>
@Pooh3Mobi
Copy link
Author

no starting animation.

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