Last active
August 9, 2024 06:36
-
-
Save wotosts/fc703bced312490c406e7277ead94624 to your computer and use it in GitHub Desktop.
AnimatedLoader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import androidx.compose.runtime.Composable | |
| import androidx.compose.runtime.Stable | |
| import androidx.compose.runtime.getValue | |
| import androidx.compose.runtime.mutableStateOf | |
| import androidx.compose.runtime.remember | |
| import androidx.compose.runtime.rememberCoroutineScope | |
| import androidx.compose.runtime.setValue | |
| import androidx.compose.ui.Modifier | |
| import androidx.compose.ui.tooling.preview.Preview | |
| import com.airbnb.lottie.compose.LottieAnimation | |
| import com.airbnb.lottie.compose.LottieCancellationBehavior | |
| import com.airbnb.lottie.compose.LottieCompositionSpec | |
| import com.airbnb.lottie.compose.LottieConstants | |
| import com.airbnb.lottie.compose.animateLottieCompositionAsState | |
| import com.airbnb.lottie.compose.rememberLottieComposition | |
| import dev.wotosts.lottie.R | |
| /** | |
| * 애니메이션 중간에 로딩이 멈추는 경우 애니메이션을 끝까지 완료한 후 종료되어야 함. | |
| * | |
| * @property playOneTime 1회만 플레이 | |
| */ | |
| @Stable | |
| class AnimatedLoaderState( | |
| private val scope: CoroutineScope, | |
| private val playOneTime: Boolean = true, | |
| private val startDelay: Long = 100, | |
| private val onLastAnimatedLoopComplete: () -> Unit = {} | |
| ) { | |
| var show by mutableStateOf(false) | |
| var playing by mutableStateOf(false) | |
| private var delayJob: Job? = null | |
| fun startLoading() { | |
| if (show) return | |
| delayJob?.cancel() | |
| delayJob = scope.launch { | |
| delay(startDelay) | |
| playing = true | |
| show = true | |
| } | |
| } | |
| fun endLoading() { | |
| delayJob?.cancel() | |
| playing = false | |
| } | |
| // if !playOneTime -> after called endLoading and animation is completed | |
| // else -> animation is completed | |
| internal fun onAnimationComplete() { | |
| show = false | |
| onLastAnimatedLoopComplete() | |
| } | |
| val repeatCount: Int | |
| get() = if (playOneTime) 1 else LottieConstants.IterateForever | |
| } | |
| @Composable | |
| fun rememberAnimatedLoaderState( | |
| scope: CoroutineScope = rememberCoroutineScope(), | |
| startDelay: Long = 100, | |
| playOneTime: Boolean = false, | |
| onLastAnimatedLoopComplete: () -> Unit = {} | |
| ) = | |
| remember(scope, startDelay, playOneTime, onLastAnimatedLoopComplete) { | |
| AnimatedLoaderState( | |
| scope = scope, | |
| startDelay = startDelay, | |
| playOneTime = playOneTime, | |
| onLastAnimatedLoopComplete = onLastAnimatedLoopComplete | |
| ) | |
| } | |
| @Composable | |
| fun AnimatedLoader( | |
| modifier: Modifier = Modifier, | |
| state: AnimatedLoaderState = rememberAnimatedLoaderState(), | |
| onLastAnimationComplete: (() -> Unit)? = null | |
| ) { | |
| val composition by rememberLottieComposition(spec = LottieCompositionSpec.RawRes(R.raw.loading)) | |
| if (state.show) { | |
| val progress by animateLottieCompositionAsState( | |
| composition = composition, | |
| isPlaying = state.playing, | |
| iterations = state.repeatCount, | |
| cancellationBehavior = LottieCancellationBehavior.OnIterationFinish, | |
| restartOnPlay = true | |
| ) | |
| val isAnimationCompleted by remember { | |
| derivedStateOf { | |
| progress == 1.0f | |
| } | |
| } | |
| LaunchedEffect(isAnimationCompleted) { | |
| if(isAnimationCompleted) state.onAnimationComplete() | |
| } | |
| LottieAnimation( | |
| modifier = modifier, | |
| composition = composition, | |
| progress = { progress } | |
| ) | |
| } | |
| } | |
| @Preview | |
| @Composable | |
| private fun AnimatedLoaderPreview() { | |
| val state = rememberAnimatedLoaderState() | |
| AnimatedLoader(state = state) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import androidx.compose.foundation.layout.Box | |
| import androidx.compose.foundation.layout.fillMaxSize | |
| import androidx.compose.foundation.layout.size | |
| import androidx.compose.material3.Card | |
| import androidx.compose.material3.CardDefaults | |
| import androidx.compose.runtime.Composable | |
| import androidx.compose.ui.Alignment | |
| import androidx.compose.ui.ExperimentalComposeUiApi | |
| import androidx.compose.ui.Modifier | |
| import androidx.compose.ui.graphics.Color | |
| import androidx.compose.ui.platform.LocalView | |
| import androidx.compose.ui.unit.dp | |
| import androidx.compose.ui.window.Dialog | |
| import androidx.compose.ui.window.DialogProperties | |
| import androidx.compose.ui.window.DialogWindowProvider | |
| @OptIn(ExperimentalComposeUiApi::class) | |
| @Composable | |
| fun AnimatedLoaderDialog(state: AnimatedLoaderState = rememberAnimatedLoaderState()) { | |
| Dialog( | |
| onDismissRequest = { }, | |
| properties = DialogProperties( | |
| dismissOnBackPress = false, | |
| usePlatformDefaultWidth = false | |
| ) | |
| ) { | |
| val dialogWindowProvider = LocalView.current.parent as? DialogWindowProvider | |
| dialogWindowProvider?.window?.setDimAmount(0f) | |
| // statusbar 색 변경 flag clear 필요 | |
| Box( | |
| Modifier | |
| .fillMaxSize(), | |
| contentAlignment = Alignment.Center | |
| ) { | |
| Card( | |
| modifier = Modifier | |
| .size(120.dp), | |
| colors = CardDefaults.cardColors(containerColor = Color.LightGray.copy(alpha = 0.5f)) | |
| ) { | |
| AnimatedLoader(state = state) | |
| } | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import android.os.Bundle | |
| import androidx.activity.ComponentActivity | |
| import androidx.activity.compose.setContent | |
| import androidx.compose.foundation.layout.Arrangement | |
| import androidx.compose.foundation.layout.Column | |
| import androidx.compose.foundation.layout.Row | |
| import androidx.compose.foundation.layout.fillMaxSize | |
| import androidx.compose.foundation.layout.fillMaxWidth | |
| import androidx.compose.material3.Text | |
| import androidx.compose.material3.TextButton | |
| import androidx.compose.runtime.Composable | |
| import androidx.compose.ui.Modifier | |
| import androidx.compose.ui.tooling.preview.Preview | |
| import dev.wotosts.lottie.ui.AnimatedLoader | |
| import dev.wotosts.lottie.ui.rememberAnimatedLoaderState | |
| import dev.wotosts.lottie.ui.theme.LottieTheme | |
| class MainActivity : ComponentActivity() { | |
| override fun onCreate(savedInstanceState: Bundle?) { | |
| super.onCreate(savedInstanceState) | |
| setContent { | |
| LottieTheme { | |
| // A surface container using the 'background' color from the theme | |
| Column( | |
| modifier = Modifier.fillMaxSize() | |
| ) { | |
| val state = rememberAnimatedLoaderState() | |
| Row( | |
| modifier = Modifier.fillMaxWidth(), | |
| horizontalArrangement = Arrangement.Center | |
| ) { | |
| TextButton(onClick = { state.startLoading() }) { | |
| Text(text = "Start") | |
| } | |
| TextButton(onClick = { state.endLoading() }) { | |
| Text(text = "End") | |
| } | |
| } | |
| //if (state.show) AnimatedLoaderDialog(state = state) | |
| AnimatedLoader(state = state) | |
| } | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment