Створення логотипа Perplexity з матового скла у Jetpack Compose
Придумав я для себе цікаву задачку і вирішив поділитися коротко тим, як я її вирішив. Десь в інстаграмі побачив логотип компанії 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
3 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів