Android Jetpack Compose сложная проблема с производительностью анимации
Я только что начал создавать анимацию, и теперь я запутался. У меня есть экран входа в систему, и я хочу использовать анимацию, но я заметил проблемы с производительностью в моем коде. Когда я щелкаю поле в первый раз, и я не хочу знать, что можно сделать для этого, есть ли другое решение или это просто так? Как я могу это решить?
enum class ViewState { WELCOME, SIGNING, SIGNUP }
@ExperimentalAnimationApi
@Composable
fun OnBoardView(deviceHeight: Dp, deviceWidth: Dp,activity: Activity) {
val baseState = remember { mutableStateOf(ViewState.WELCOME) }
val signUpTransition = updateTransition(targetState = baseState, "1")
val signInTransition = updateTransition(targetState = baseState, label = "2")
val focusManager = LocalFocusManager.current
val visible by remember { baseState }
val density = LocalDensity.current
val isRunning = !signUpTransition.isRunning || !signInTransition.isRunning
//SIGN UP
val signUpSize = sizeAnimate(
targetState = ViewState.SIGNUP,
deviceWidth = deviceWidth,
label = "3",
transition = signUpTransition,
)
val signUpOffset = offsetAnimate(
targetState = ViewState.SIGNUP,
label = "4",
transition = signUpTransition,
deviceWidth = deviceWidth
)
//SIGN IN
val signInSize = sizeAnimate(
targetState = ViewState.SIGNING,
deviceWidth = deviceWidth,
label = "5",
transition = signInTransition,
)
val signInOffset = offsetAnimate(
targetState = ViewState.SIGNING,
label = "6",
transition = signInTransition,
deviceWidth = deviceWidth
)
@Composable
fun TextView(label: String, targetState: ViewState, icon: ImageVector, color: Color) {
AnimatedVisibility(
visible = visible == targetState,
enter = slideInHorizontally(
animationSpec = tween(900),
initialOffsetX = {
with(density) {
when (targetState) {
ViewState.SIGNUP -> -deviceWidth.roundToPx()
ViewState.SIGNING -> deviceWidth.roundToPx()
else -> {
0
}
}
}
}
),
exit = slideOutHorizontally(
animationSpec = tween(900),
targetOffsetX = {
with(density) {
when (targetState) {
ViewState.SIGNUP -> -deviceWidth.roundToPx()
ViewState.SIGNING -> deviceWidth.roundToPx()
else -> {
0
}
}
}
}
)
) {
Column(
modifier = Modifier
.fillMaxHeight(0.5F)
.fillMaxWidth(),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally
) {
IconButton(
enabled = isRunning && !this@AnimatedVisibility.transition.isRunning,
onClick = {
baseState.value = ViewState.WELCOME
},
modifier = Modifier
.size(50.dp)
.clip(shape = CircleShape)
.background(color)
) {
Icon(
icon,
contentDescription = null,
tint = Color.White
)
}
Text(text = label, fontSize = 25.sp)
}
}
}
@Composable
fun MainBoxView(
offset: Offset,
deviceWidth: Dp,
backgroundColor: Color,
selectedState: ViewState,
content: @Composable () -> Unit
) {
Box(
modifier = Modifier
.size(
width = deviceWidth,
height = if (selectedState == ViewState.SIGNUP) deviceHeight / 1.5F else deviceHeight / 2.3F
)
.offset(offset.x.dp, offset.y.dp)
.clip(shapes.medium)
.background(backgroundColor)
.noRippleClickable(isEnabled = isRunning) {
baseState.value = selectedState
focusManager.clearFocus()
},
contentAlignment = Alignment.TopCenter
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
content()
}
}
}
@Composable
fun BoxColumnView(
targetState: ViewState,
content: @Composable () -> Unit,
) {
AnimatedVisibility(
visible = visible == targetState,
enter = slideInHorizontally(
animationSpec = tween(800),
initialOffsetX = { with(density) { if (targetState == ViewState.SIGNUP) (-deviceWidth / 2).roundToPx() else (deviceWidth / 2).roundToPx() } }
),
exit = slideOutHorizontally(
animationSpec = tween(800),
targetOffsetX = { with(density) { if (targetState == ViewState.SIGNUP) (-deviceWidth / 2).roundToPx() else (deviceWidth / 2).roundToPx() } }
)
) {
content()
}
}
@Composable
fun BoxPreTextView(label: String, targetState: ViewState) {
AnimatedVisibility(
visible = visible == ViewState.WELCOME,
enter = when (visible) {
ViewState.SIGNUP -> {
slideInHorizontally(
animationSpec = tween(800),
initialOffsetX = { with(density) { deviceWidth.roundToPx() } }
)
}
ViewState.WELCOME -> {
slideInHorizontally(
animationSpec = tween(800),
initialOffsetX = { with(density) { if (targetState == ViewState.SIGNUP) deviceWidth.roundToPx() else -deviceWidth.roundToPx() } }
)
}
else -> {
slideInHorizontally(
animationSpec = tween(800),
initialOffsetX = { with(density) { deviceWidth.roundToPx() } }
)
}
},
exit = when (visible) {
ViewState.SIGNUP -> {
slideOutHorizontally(
animationSpec = tween(800),
targetOffsetX = { with(density) { deviceWidth.roundToPx() } }
)
}
ViewState.WELCOME -> {
slideOutHorizontally(
animationSpec = tween(800),
targetOffsetX = { with(density) { if (targetState == ViewState.SIGNUP) -deviceWidth.roundToPx() else deviceWidth.roundToPx() } }
)
}
else -> {
slideOutHorizontally(
animationSpec = tween(800),
targetOffsetX = { with(density) { -deviceWidth.roundToPx() } }
)
}
}
) {
Text(
text = label,
color = Color.White,
)
}
}
@Composable
fun MainTextView(label: String, targetState: ViewState) {
AnimatedVisibility(
visible = visible == targetState || visible == ViewState.WELCOME,
enter = slideInHorizontally(
animationSpec = tween(800),
initialOffsetX = {
with(density) {
if (targetState == ViewState.SIGNUP) {
(-deviceWidth / 3).roundToPx()
} else {
(deviceWidth / 3).roundToPx()
}
}
}
),
exit = slideOutHorizontally(
animationSpec = tween(1200),
targetOffsetX = {
with(density) {
if (targetState == ViewState.SIGNUP) {
(-deviceWidth / 3).roundToPx()
} else {
(deviceWidth / 3).roundToPx()
}
}
})
) {
Text(
text = label,
color = Color.White,
fontSize = 24.sp,
)
}
}
Column(
modifier = Modifier
.fillMaxSize()
.noRippleClickable {
focusManager.clearFocus()
},
) {
Box(
modifier = Modifier
.weight(1.5F)
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
TextView(
icon = Icons.Default.ArrowForward,
label = "WELCOME!",
targetState = ViewState.SIGNUP,
color = DarkBlue
)
TextView(
icon = Icons.Default.ArrowBack,
label = "WELCOME BACK!",
targetState = ViewState.SIGNING,
color = LightBlue
)
}
Row(
modifier = Modifier
.weight(3F)
.fillMaxSize()
.noRippleClickable {
focusManager.clearFocus()
},
//horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top,
) {
//SIGN UP
MainBoxView(
offset = signUpOffset,
deviceWidth = signUpSize,
backgroundColor = DarkBlue,
selectedState = ViewState.SIGNUP
) {
Box(
modifier = Modifier.weight(1F),
contentAlignment = Alignment.Center
) {
MainTextView(
label = "SIGN UP",
targetState = ViewState.SIGNUP,
)
}
Box(
modifier = Modifier.weight(6F),
contentAlignment = Alignment.TopCenter
) {
/*
BoxPreTextView(
label = "New here? come on, what are you waiting for, sign up and open up to new worlds",
targetState = ViewState.SIGNUP
)
*/
BoxColumnView(
targetState = ViewState.SIGNUP,
) {
}
}
}
//SIGN IN
MainBoxView(
offset = signInOffset,
deviceWidth = signInSize,
backgroundColor = LightBlue,
selectedState = ViewState.SIGNING
) {
Box(
modifier = Modifier.weight(1F),
contentAlignment = Alignment.Center
) {
MainTextView(
label = "SIGN IN",
targetState = ViewState.SIGNING,
)
}
Box(
modifier = Modifier.weight(4F),
contentAlignment = Alignment.TopCenter
) {
/*
BoxPreTextView(
label = "Returning? Just Sign in to resume what you were doing",
targetState = ViewState.SIGNING
)
*/
BoxColumnView(
targetState = ViewState.SIGNING,
) {
LoginView(activity)
}
}
}
}
Box(
modifier = Modifier
.weight(1F)
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier.fillMaxSize(0.5F),
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
) {
IconButton(
onClick = { },
modifier = Modifier
.size(50.dp)
.clip(shape = CircleShape)
.background(Color.Blue)
) {
Icon(
painterResource(id = R.mipmap.facebook_foreground),
contentDescription = null,
tint = Color.White
)
}
IconButton(
onClick = { },
modifier = Modifier
.size(50.dp)
.clip(shape = CircleShape)
.background(GoogleRed)
) {
Icon(
painterResource(id = R.mipmap.google_foreground),
contentDescription = null,
tint = Color.White
)
}
IconButton(
onClick = { },
modifier = Modifier
.size(50.dp)
.clip(shape = CircleShape)
.background(LightBlue)
) {
Icon(
painterResource(id = R.mipmap.twitter_foreground),
contentDescription = null,
tint = Color.White
)
}
}
}
}
}
@Composable
fun offsetAnimate(
transition: Transition<MutableState<ViewState>>,
targetState: ViewState,
label: String,
deviceWidth: Dp
): Offset {
val offset by transition.animateOffset(
label = label,
transitionSpec = {
if (this.targetState.value == targetState) {
tween(900, 100)
} else {
tween(450)
}
},
) { animated ->
fun width(value: Double) = (deviceWidth.value * value).toFloat()
if (targetState == ViewState.SIGNUP) {
when (animated.value) {
ViewState.SIGNUP -> {
Offset(width(0.07), 0f)
}
ViewState.SIGNING -> {
Offset(-width(0.1), 0f)
}
else -> {
Offset(-width(0.06), 0f)
}
}
} else {
when (animated.value) {
ViewState.SIGNUP -> {
Offset(width(0.2), 0f)
}
ViewState.SIGNING -> {
Offset(width(0.00), 0f)
}
else -> {
Offset(width(0.04), 0f)
}
}
}
}
return offset
}
@Composable
fun sizeAnimate(
transition: Transition<MutableState<ViewState>>,
targetState: ViewState,
label: String,
deviceWidth: Dp
): Dp {
val sizeAnimate by transition.animateDp(
label = label,
transitionSpec = {
if (this.targetState.value == targetState)
tween(450) else tween(900)
},
) {
if (targetState == ViewState.SIGNUP) {
when (it.value) {
ViewState.WELCOME -> deviceWidth / 2F
ViewState.SIGNING -> deviceWidth / 16F
ViewState.SIGNUP -> deviceWidth / 1.15F
}
} else {
when (it.value) {
ViewState.WELCOME -> deviceWidth / 2F
ViewState.SIGNUP -> deviceWidth / 16F
ViewState.SIGNING -> deviceWidth / 1.15F
}
}
}
return sizeAnimate
}
1 ответ
У меня была аналогичная проблема с производительностью не так давно, и мне удалось ее исправить, переключив вариант сборки с отладки на выпуск.
Сначала в левом нижнем углу вашего проекта нажмите «Варианты сборки» и выберите «Выпуск» в качестве «Активного варианта сборки».
Теперь вам нужно установить конфигурацию подписи для варианта выпуска. Нажмите Ctrl+Alt+Shift+S, и этот ярлык должен открыть структуру проекта. Затем в «Модулях» выберите «Приложение» и нажмите «Конфигурация по умолчанию». Найдите «Signing Config» и выберите «$signingConfigs.debug».
Нажмите «Применить» и подождите, пока Gradle загрузит модели сборки. Затем нажмите OK и подождите, пока Gradle синхронизирует проект.
Перестройте проект, и проблема с производительностью должна исчезнуть.