Створення логотипа Perplexity з матового скла у Jetpack Compose

💡 Усі статті, обговорення, новини про Mobile — в одному місці. Приєднуйтесь до Mobile спільноти!

Придумав я для себе цікаву задачку і вирішив поділитися коротко тим, як я її вирішив. Десь в інстаграмі побачив логотип компанії Perplexity зроблений зі скла, який прикольненько так крутився. У цьому туторіалі ми створимо анімований логотип Perplexity, що має ефект матового скла. Ми використаємо бібліотеку haze від Chris Banes для імітації скла та graphicsLayer для 3D-анімації

Налаштування проєкту

Спочатку необхідно додати залежність для haze у build.gradle:

repositories {
    mavenCentral()
}

dependencies {
    implementation("dev.chrisbanes.haze:haze:<version>")
}

Основний компонент

PerplexityLogo містить два блоки (RoundedBoxLeft і RoundedBoxRight), які створюють ефект матового скла.

💡 Таке рішення було зроблено через те, що після обертання елемента на 180° зображення відзеркалювалося і не давало такий ефект, як я хотів. На зображенні знизу видно, що жовтий є як зліва, так і зправа

@Composable
fun PerplexityLogo(
    hazeStateLeft: HazeState,
    hazeStateRight: HazeState,
    modifier: Modifier
) {
    val containerColor = MaterialTheme.colorScheme.surface
    val hazeStyle = HazeStyle(
        backgroundColor = containerColor,
        tints = listOf(
            HazeTint(containerColor.copy(alpha = if (containerColor.luminance() >= 0.5) 0.3f else 0.1f))
        ),
        blurRadius = 10.dp,
        noiseFactor = 0.3f
    )
    val infiniteTransition = rememberInfiniteTransition()
    val animationProgress by infiniteTransition.animateFloat(
        initialValue = 1f,
        targetValue = 0f,
        animationSpec = infiniteRepeatable(
            animation = tween(5_000),
            repeatMode = RepeatMode.Restart
        )
    )
    Box(modifier = modifier) {
        val pages = 8
        MutableList(pages) { index ->
            val rotationAngle = (animationProgress * 360 + (index * 360 / pages)) % 360
            if (rotationAngle in 0f..180f) {
                RoundedBoxLeft(rotationAngle - 90f, hazeStateLeft, hazeStyle, Modifier)
            } else {
                RoundedBoxRight(rotationAngle + 90f, hazeStateRight, hazeStyle, Modifier)
            }
        }
    }
}

1/ Я створив свій HazeStyle, бо стандартні не дуже підходили, тай хотілося погратися з різним blurRadius та noiseFactor

    val hazeStyle = HazeStyle(
        backgroundColor = containerColor,
        tints = listOf(
            HazeTint(
                containerColor.copy(alpha = if (containerColor.luminance() >= 0.5) lightAlpha else darkAlpha),
            )
        ),
        blurRadius = 10.dp,
        noiseFactor = 0.3f,
        fallbackTint = HazeTint.Unspecified,
    )

2/ Створюємо 8 сторінок та вираховуємо для кожної з них кут. Залежно від кута вони будуть або злівої чи правої сторони. Ми віднімаємо 90 чи додаємо 90 градусів, щоб у нас сторінки малювалися з однієї точки, залежно від того, звідки ми починаємо малювати

RoundedBox

@Composable
fun RoundedBoxRight(
    rotationAngle: Float,
    hazeState: HazeState,
    hazeStyle: HazeStyle,
    modifier: Modifier
) {
    Page(
        hazeState = hazeState,
        hazeStyle = hazeStyle,
        borderColor = Color(0xFF24F4FE),
        modifier = modifier
            .graphicsLayer {
                rotationY = rotationAngle
                rotationX = -45f
                cameraDistance = 100f
                transformOrigin = TransformOrigin(
                    pivotFractionX = 0f,
                    pivotFractionY = 0.0f,
                )
            }
    )
}

Основною різницею між лівою та правою частиною є transformOrigin = TransformOrigin(1f, 0f) та TransformOrigin(1f, 0f). Ці блоки використовують graphicsLayer для створення ефекту перспективи

Page

@Composable
fun Page(
    hazeState: HazeState,
    hazeStyle: HazeStyle,
    borderColor: Color,
    modifier: Modifier
) {
    Box(
        modifier = modifier
            .hazeEffect(hazeState, style = hazeStyle)
            .border(8.dp, borderColor)
    )
}

Тут все просто. Box з обводкою та нашим ефектом скла. При необхідності можна задати заливки чи паралакс ефект

zIndex

У нашому прикладі є дуже важливим zIndex. Для лівої сторони ми його визначаємо як zIndex(rotationAngle) для правої zIndex(360 - rotationAngle).

Ось так виглядатиме наше лого, якщо не задати правильний порядок відмалювання

hazeSource

Для отримання ефекту розмиття спочатку потрібно отримати інформацію з картинки позаду. Тому ми hazeState витягнули з PerplexityLogo ззовні, де є наш бекграунд і за допомогою hazeSource отримуємо його. Обов’язково потрібно вказати zIndex = 0f

          Box(
                contentAlignment = Alignment.Center
            ) {
                val hazeStateLeft = remember { HazeState() }
                val hazeStateRight = remember { HazeState() }
                Box(
                    modifier = Modifier
                        .offset(y = (-30).dp)
                        .size(300.dp)
                        .hazeSource(hazeStateRight, zIndex = 0f)
                        .hazeSource(hazeStateLeft, zIndex = 0f)
                        .graphicsLayer {
                            rotationZ = animationProgress * 1080
                        }
                        .clip(CircleShape)
                        .paint(
                            painter = painterResource(id = R.drawable.orb_bg),
                            contentScale = ContentScale.Crop
                        )
                )
                PerplexityLogo(
                    hazeStateLeft,
                    hazeStateRight,
                    modifier = Modifier.size(200.dp)
                )
            }

А в PerplexityLogo ми вже вказуємо zIndex залежно від оберту: .hazeSource(hazeStateLeft, rotationAngle + 1f) — за тим самим принципом, що й zIndex, але з +1 (оскільки попередній шар був 0)

Результат

Цей код створює анімований логотип Perplexity з ефектом матового скла. Використовуючи HazeEffect та graphicsLayer, ми досягаємо красивого візуального ефекту з плавною анімацією

(В репозиторії є приклад з гіфкою, який краще демонструє як воно виглядає)

🔗 Посилання на репозиторій з кодом: github.com/...​/perplexity_ui_experiment
🔗 Посилання на мій канал по Android в тг t.me/android_fragment

Надіюся, що вам було цікаво цікаво прочитати і познайомитися з можливостями бібліотеки Haze

👍ПодобаєтьсяСподобалось2
До обраногоВ обраному1
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Анімація на DOU!Вуху! В статтю не можна прикріпляти файли у форматах GIF, WebM або MP4?

Може це я туплю, але не знайшов можливості. Відос і гіфку не дозволяло просто додати, можливо треба було кудись закинути і додати як Embed

Шо курять ці дезайнери?

Підписатись на коментарі