called from this
composable("main_screen") {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
VideoPlayerScreen()
}
and defined here
#Composable
fun VideoPlayerScreen() {
val context = LocalContext.current
var playWhenReady by remember { mutableStateOf(true) }
val exoPlayer = remember {
ExoPlayer.Builder(context).build().apply {
val s : String = "android.resource://com.gamerz.splash/" + R.raw.introstudios
setMediaItem(MediaItem.fromUri(s))
repeatMode = ExoPlayer.REPEAT_MODE_OFF
playWhenReady = playWhenReady
prepare()
play()
}
}
DisposableEffect(
AndroidView(
modifier = Modifier.size(200.dp),
factory = {
StyledPlayerView(context).apply {
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
player = exoPlayer
useController = false
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT)
}
}
)
) {
onDispose {
exoPlayer.release()
}
}
}
With no RESIZE_MODE_ZOOM, this is what it looks like.
I feel like i should be able to change the black to transparent? Or at least properly crop out the black sides of video?
Related
I'm trying to implement https://google.github.io/accompanist/navigation-material/ and i want to expand modelsheet to custom height or more than half screen but i don't have any idea how to achieve it
Currently ModelBottomSheet
Wish to expand like this
Instead of using the show method of the ModalBottomSheetState, You can use the animateTo method. The show method will default to a half screen size modal. The animateTo(ModalBottomSheetValue.Expanded) will expand to the full size of the content. In the example i've used a BoxWithConstrains to get the screen size and set the size of the modal content to 80%.
I hope this helps!
#Composable
#Preview
fun BottomSheetDemo() {
val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
BoxWithConstraints {
val sheetHeight = this.constraints.maxHeight * 0.8f
val coroutineScope = rememberCoroutineScope()
Column {
Button(onClick = {
coroutineScope.launch { modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded) }
}) {
Text(text = "Expand")
}
Button(onClick = {
coroutineScope.launch { modalBottomSheetState.animateTo(ModalBottomSheetValue.Hidden) }
}) {
Text(text = "Collapse")
}
}
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Red,
sheetState = modalBottomSheetState,
sheetContent = {
Box(modifier = Modifier.height(with(LocalDensity.current) { sheetHeight.toDp() })) {
Text(text = "This is some content")
}
}
) {}
}
}
EDIT:
If you want to use the material navigation, you will need a custom extension function. The difference in this function with the original is the skipHalfExpanded parameter. This on will make it possible to create bottom sheets larger then half screen.
#Composable
fun rememberBottomSheetNavigator(
animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec
): BottomSheetNavigator {
val sheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
animationSpec = animationSpec,
skipHalfExpanded = true
)
return remember(sheetState) {
BottomSheetNavigator(sheetState = sheetState)
}
}
The implementation itself will be something like this:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val bottomSheetNavigator = rememberBottomSheetNavigator()
val navController = rememberNavController(bottomSheetNavigator)
ModalBottomSheetLayout(bottomSheetNavigator) {
NavHost(navController, "home") {
composable(route = "home") {
Home(navController)
}
bottomSheet(route = "sheet") {
ModalDemo()
}
}
}
}
}
}
#Composable
fun Home(navController: NavController) {
val coroutineScope = rememberCoroutineScope()
Column {
Button(onClick = {
coroutineScope.launch { navController.navigate("sheet") }
}) {
Text(text = "Expand")
}
}
}
#Composable
fun ModalDemo() {
Column(Modifier.fillMaxWidth().height(700.dp).background(Color.Red), horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "This is some content")
}
}
I have got two problems, scrolling through LazyColumn or VerticalPager with AndroidView filling the whole screen as a child item lags the screen and the scrolling behavior for a couple of milliseconds as well as overlapping items. In my code the AndroidView creates PlayerView, I also tried to replace PlayerView with a TextView to check maybe the problem is with PlayerView itself. I can't seem to find the root of the problem exactly, maybe with the AndroidView or the implementation of the VerticalPager itself, or maybe because it fills the whole screen?
ViewScreen
#OptIn(ExperimentalPagerApi::class)
#Composable
fun VideoScreen() {
val pagerState = rememberPagerState()
Box {
VerticalPager(
count = videos.size,
state = pagerState,
horizontalAlignment = Alignment.CenterHorizontally,
itemSpacing = 10.dp
) { index ->
VideoPlayer(
vid = videos[index],
shouldPlay = false
)
}
}
}
VideoPlayer
#Composable
fun VideoPlayer(
vid: Video,
shouldPlay: Boolean
) {
val exoPlayer = rememberExoPlayerWithLifecycle(vid.url)
val playerView = rememberPlayerView(exoPlayer)
AndroidView(
factory = { playerView },
modifier = Modifier,
update = {
exoPlayer.playWhenReady = shouldPlay
}
)
DisposableEffect(key1 = true) {
onDispose {
exoPlayer.release()
}
}
}
rememberExoPlayerWithLifecycle
#Composable
fun rememberExoPlayerWithLifecycle(
url: String
): ExoPlayer {
val context = LocalContext.current
val exoPlayer = remember(url) {
ExoPlayer.Builder(context).build().apply {
videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
repeatMode = Player.REPEAT_MODE_ONE
setHandleAudioBecomingNoisy(true)
val defaultDataSource = DefaultHttpDataSource.Factory()
val source = ProgressiveMediaSource.Factory(defaultDataSource)
.createMediaSource(MediaItem.fromUri(url))
setMediaSource(source)
prepare()
}
}
var appInBackground by remember {
mutableStateOf(false)
}
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(key1 = lifecycleOwner, appInBackground) {
val lifecycleObserver = getExoPlayerLifecycleObserver(exoPlayer, appInBackground) {
appInBackground = it
}
lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
onDispose {
lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
}
}
return exoPlayer
}
rememberPlayerView
#Composable
fun rememberPlayerView(exoPlayer: ExoPlayer): PlayerView {
val context = LocalContext.current
val playerView = remember {
PlayerView(context).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
useController = false
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
player = exoPlayer
setShowBuffering(SHOW_BUFFERING_ALWAYS)
}
}
DisposableEffect(key1 = true) {
onDispose {
playerView.player = null
}
}
return playerView
}
build.gradle (Project)
buildscript {
ext {
compose_version = '1.3.0-beta01'
}
}
plugins {
...
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}
...
build.gradle (Module)
...
dependencies {
...
//Pager
implementation "com.google.accompanist:accompanist-pager:0.26.2-beta"
//Media 3
def mediaVersion = "1.0.0-beta02"
implementation "androidx.media3:media3-exoplayer:$mediaVersion"
implementation "androidx.media3:media3-ui:$mediaVersion"
...
}
Overlapping Items
Use AspectRatioFrameLayout.RESIZE_MODE_FIT
I was having the same issue and update verticalpager to 0.25.1 exoplater to 2.18.1 and used a StyledPlayerView instead of PlayerView and it seems to be working.
I know that I can track the moment when lottie animation is completed using progress.
But the problem is that I want to start a new screen at the moment when the animation is completely finished.
Here is the code of my animation
#Composable
fun AnimatedScreen(
modifier: Modifier = Modifier,
rawId: Int
) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier.fillMaxSize()
) {
val compositionResult: LottieCompositionResult = rememberLottieComposition(
spec = LottieCompositionSpec.RawRes(rawId)
)
AnimatedScreenAnimation(compositionResult = compositionResult)
}
}
#Composable
fun AnimatedScreenAnimation(compositionResult: LottieCompositionResult) {
val progress by animateLottieCompositionAsState(composition = compositionResult.value)
Column {
if (progress < 1) {
Text(text = "Progress: $progress")
} else {
Text(
modifier = Modifier.clickable { },
text = "Animation is done"
)
}
LottieAnimation(
composition = compositionResult.value,
progress = progress,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.FillBounds
)
}
}
And here is code of my screen where i want to wait for the end of the animation and then go to a new screen:
#Composable
fun SplashScreen(
navController: NavController,
modifier: Modifier = Modifier,
viewModel: SplashScreenViewModel = getViewModel()
) {
val resIdState = viewModel.splashScreenResId.collectAsState()
val resId = resIdState.value
if (resId != null) {
AnimatedScreen(modifier = modifier, rawId = resId)
}
LaunchedEffect(true) {
navigate("onboarding_route") {
popUpTo(0)
}
}
}
I used the progress & listened to it's updates & as soon as it reaches 1f I'll call my function.
Example:
#Composable
fun Splash() {
LottieTest {
// Do something here
}
}
#Composable
fun LottieTest(onComplete: () -> Unit) {
val composition: LottieCompositionResult =
rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.camera))
val progress by animateLottieCompositionAsState(
composition.value,
iterations = 1,
)
LaunchedEffect(progress) {
Log.d("MG-progress", "$progress")
if (progress >= 1f) {
onComplete()
}
}
LottieAnimation(
composition.value,
progress,
)
}
Note: This is just the way I did it. The best way is still unknown(to me atleast). I feel it lacks the samples for that.
Also, You can modify a lot from this & just concentrate on the core flow.
At the moment I'm setting the delay as a constant, instead I want to take the time that is used in the json animation, how can this be done?
Here is code where i used constant:
val composableScope = rememberCoroutineScope()
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
LottieExample()
composableScope.launch {
delay(2000L)
navController.navigate(viewModel.nextRoute) {
popUpTo(0)
}
}
}
And here is the animation code itself:
#Composable
fun LottieExample() {
val compositionResult: LottieCompositionResult = rememberLottieComposition(
spec = LottieCompositionSpec.RawRes(R.raw.splash_with_background)
)
val progress by animateLottieCompositionAsState(
composition = compositionResult.value,
isPlaying = true,
iterations = LottieConstants.IterateForever,
speed = 1.0f
)
LottieAnimation(
composition = compositionResult.value,
progress = progress,
modifier = Modifier.padding(all = 12.dp)
)
}
Took compositionResult out of bounds and pull out the duration of the animation.
Solved with next solution:
val composableScope = rememberCoroutineScope()
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
val compositionResult: LottieCompositionResult = rememberLottieComposition(
spec = LottieCompositionSpec.RawRes(R.raw.splash_with_background)
)
SplashScreenAnimation(compositionResult = compositionResult)
composableScope.launch {
delay(compositionResult.value?.duration?.toLong() ?: SPLASH_SCREEN_DURATION)
viewModel.destination.firstOrNull()?.let { route ->
navController.clearBackStackAndNavigateTo(route)
}
}
}
How to implement a progress bar like animation to the second rectangle, as well as control the animation such as start & pause
#Composable
fun ProgressBar(modifier: Modifier = Modifier.size(300.dp, 180.dp)) {
Canvas(modifier = modifier.fillMaxSize().padding(16.dp)) {
val canvasWidth = size.width
val canvasHeight = size.height
val canvasSize = size
val percentage = 0.2F
drawRoundRect(
color = Pink80,
topLeft = Offset(x = 0F, y = 0F),
size = Size(canvasSize.width, 55F),
cornerRadius = CornerRadius(60F, 60F),
)
drawRoundRect(
color = Purple40,
topLeft = Offset(x = 0F, y = 0F),
size = Size(canvasWidth * 0.2F , 55F),
cornerRadius = CornerRadius(60F, 60F),
)
}
}
Here are two solutions. The first one uses Compose's own LinearProgressIndicator. The second one is a custom built one. It is very simple and has the advantage that you can easily customize it.
No need to use a canvas. When the progress bar reaches it's maximum, the code resets its value back to zero, but you can leave it set to 100% by not resetting the value. The LaunchedEffect is only being used to simulate updating the value. In a production app, you wouldn't use this but rather update the value with some other means that aligns with your code's business logic...
LinearProgressIndicator:
See: LinearProgressIndicator
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(intent)
setContent {
var enabled by remember { mutableStateOf(false) }
var progress by remember { mutableStateOf(0.1f) }
val animatedProgress by animateFloatAsState(
targetValue = progress,
)
LaunchedEffect(enabled) {
while ((progress < 1) && enabled) {
progress += 0.005f
delay(10)
}
}
if (progress >= 1f) {
enabled = false
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(20.dp), horizontalAlignment = Alignment.CenterHorizontally
) {
LinearProgressIndicator(progress = animatedProgress, modifier = Modifier.requiredHeight(20.dp))
Spacer(Modifier.requiredHeight(30.dp))
Button(
onClick = {
enabled = !enabled
}
) {
if (enabled) {
Text("Pause")
} else {
Text("Start")
}
}
}
}
}
}
Custom Progress Indicator:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(intent)
setContent {
var enabled by remember { mutableStateOf(false) }.apply { this.value }
var progressValue by remember { mutableStateOf(0) }
LaunchedEffect(enabled) {
while ((progressValue < 100) && enabled) {
progressValue++
delay(10)
}
if (progressValue == 100) {
enabled = false
progressValue = 0
}
}
Column(modifier = Modifier.fillMaxSize().padding(20.dp)) {
ProgressBar(value = progressValue)
Button(
onClick = {
enabled = !enabled
}
) {
if (enabled) {
Text("Pause")
} else {
Text("Start")
}
}
}
}
}
}
#Composable
fun ProgressBar(
value: Int
) {
Box(
modifier = Modifier
.fillMaxWidth()
.requiredHeight(20.dp)
.border(width = 1.dp, color = Color.Black)
) {
Box(
modifier = Modifier
.fillMaxWidth(value.toFloat() / 100f)
.fillMaxHeight()
.background(color = Color.Red)
) {
}
}
}
https://github.com/DogusTeknoloji/compose-progress
You can check this library as support animated and also step progress. The library base on canvas