I have 2 screens with videoview inside and i make transition between them using accompanist-navigation-animation lib. Transition between 2 compose “screens” is slow, i think it might be related to how i start videos, but i cant figure it out. Also when transition from first to second screen is made recomposition is run many times.
Can somebody with more knowledge in compose please take a look how i handle video playback please.
#ExperimentalAnimationApi
#Composable
fun Navigation() {
val navController = rememberAnimatedNavController()
val activity = (LocalContext.current as? Activity)
AnimatedNavHost(
navController = navController,
startDestination = "promoScreen1"
) {
composable("promoScreen1",
exitTransition = {
slideOutOfContainer(
AnimatedContentScope.SlideDirection.Left,
animationSpec = tween(1000)
)
}
) {
val repeatsPromoViewModel: RepeatsPromoViewModel = viewModel()
RepeatsPromoScreen(
repeatsPromoViewModel = viewModel(),
navController = navController,
videoFileName = "repeat_promo1",
title = stringResource(id = R.string.repeats_promo_title1),
subtitle = stringResource(id = R.string.repeats_promo_subtitle),
description = stringResource(id = R.string.repeats_promo_description),
bottomButtonText = stringResource(id = R.string.next).uppercase(),
onBottomButtonClicked = {
repeatsPromoViewModel._overlayVisible.value = true
repeatsPromoViewModel.isPlaying.value = false
navController.navigate("promoScreen2")
}
)
}
composable("promoScreen2", enterTransition = {
slideIntoContainer(
AnimatedContentScope.SlideDirection.Left,
animationSpec = tween(1000)
)
}) {
val repeatsPromoViewModel: RepeatsPromoViewModel = viewModel()
BackHandler(true) {} //disable back button
RepeatsPromoScreen(
repeatsPromoViewModel = repeatsPromoViewModel,
navController = navController,
videoFileName = "repeat_promo2",
title = stringResource(id = R.string.repeats_promo_title2),
subtitle = stringResource(id = R.string.repeats_promo_subtitle2),
description = stringResource(id = R.string.repeats_promo_description2),
bottomButtonText = stringResource(id = R.string.planning_tutorial_finish).uppercase()
) { activity?.finish() }
}
}
}
#ExperimentalAnimationApi
#Composable
fun RepeatsPromoScreen(
repeatsPromoViewModel: RepeatsPromoViewModel,
navController: NavController,
videoFileName: String,
title: String,
subtitle: String,
description: String,
bottomButtonText: String,
onBottomButtonClicked: () -> Unit
) {
val activity = (LocalContext.current as? Activity)
val overlayVisible by repeatsPromoViewModel.overlayVisible
val configuration = LocalConfiguration.current
val isSmallScreen = configuration.screenHeightDp < 720 || configuration.screenWidthDp <= 360
var modifier = Modifier
.fillMaxSize()
.background(color = Color(0xFFEBF4F9))
if (isSmallScreen) {
modifier =
modifier.then(Modifier.verticalScroll(rememberScrollState())) //enable scrolling on small screens
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
) {
Box(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Start)
) {
IconButton(
onClick = { activity?.finish() }
) {
Icon(
Icons.Filled.Close,
contentDescription = "Close screen"
)
}
}
Text(
text = title,
fontSize = 28.sp,
lineHeight = 22.sp,
textAlign = TextAlign.Center,
fontFamily = FontFamily(Font(R.font.source_serif_pro_black)),
modifier = Modifier.padding(top = 24.dp),
color = colorResource(id = R.color.toshl_profile_name)
)
Text(
text = subtitle,
fontSize = 14.sp,
lineHeight = 17.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 10.dp, start = 50.dp, end = 50.dp),
color = colorResource(id = R.color.toshl_sync_subtitle)
)
val rawId =
activity?.resources?.getIdentifier(videoFileName, "raw", activity.packageName)
val path = "android.resource://" + activity?.packageName + "/" + rawId
val retriever = MediaMetadataRetriever()
retriever.setDataSource(activity, Uri.parse(path))
val frame = retriever.frameAtTime
val videoWidth = frame?.width?.toFloat() ?: 0.toFloat()
val videoHeight = frame?.height?.toFloat() ?: 0.toFloat()
val ratio = configuration.screenWidthDp / UiHelper.convertPxToDp(
activity!!.applicationContext,
videoWidth
)
val videoView = remember(activity) {
VideoView(activity).apply {
setVideoPath(path)
setOnPreparedListener { mp: MediaPlayer ->
mp.setOnInfoListener(MediaPlayer.OnInfoListener { mp, what, _ ->
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
repeatsPromoViewModel._overlayVisible.value = false
return#OnInfoListener true
}
false
})
mp.start()
mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING)
mp.isLooping = true
}
}
}
val videoContainerHeight =
UiHelper.convertPxToDp(activity.applicationContext, videoHeight) * ratio
Box() {
AndroidView(
modifier = Modifier
.padding(top = 25.dp, start = 0.dp, end = 0.dp)
.height(videoContainerHeight.dp),
factory = { context ->
videoView
}
)
//overlay
this#Column.AnimatedVisibility(visible = overlayVisible) {
Box(
modifier = Modifier
.padding(top = 25.dp, start = 0.dp, end = 0.dp)
.height(videoContainerHeight.dp)
.background(Color(0xFFEBF4F9))
.clip(RectangleShape)
.fillMaxSize()
)
}
}
Text(
text = description,
fontSize = 14.sp,
lineHeight = 17.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 30.dp, start = 50.dp, end = 50.dp),
color = colorResource(id = R.color.toshl_sync_subtitle)
)
if (isSmallScreen) { //just add button below other elements
PromoBottomButton(
paddingTop = 32.dp,
paddingBottom = 16.dp,
onFinishClicked = { },
text = stringResource(id = R.string.next).uppercase()
)
} else { //pin button to bottom of screen (that's why its wrapped inside another column)
Column(
modifier = Modifier
.fillMaxSize()
.align(Alignment.CenterHorizontally),
verticalArrangement = Arrangement.Bottom
) {
PromoBottomButton(
paddingTop = 16.dp,
paddingBottom = 32.dp,
onFinishClicked = { onBottomButtonClicked.invoke() },
text = bottomButtonText
)
}
}
}
}
Related
I use ModalBottomSheetLayout and I want the back part to darken when opening the lower curtain and it was not clickable. How to do it?
If you set screen Color = Color.Unspecified, the background will not be clickable, but at the same time it will be colorless. It doesn't suit me.
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun ForpostScreen(forpostViewModel: ForpostViewModel = koinViewModel()) {
val scope = rememberCoroutineScope()
val bottomSheetOpen = remember{ mutableStateOf(false) }
val viewState = forpostViewModel.forpostViewState.observeAsState()
val bottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden
)
ModalBottomSheetLayout(
sheetState = bottomSheetState,
sheetContent = {
Box(modifier = Modifier
.fillMaxWidth()
.height(500.dp)) {
Image(
painter = painterResource(id = R.drawable.botton_sheet_top_line),
contentDescription = "Линия",
modifier = Modifier
.height(20.dp)
.width(70.dp)
.padding(top = 10.dp)
.align(Alignment.TopCenter)
)
Text(
text = stringResource(id = R.string.all_cams_button),
color = colorResource(id = R.color.title_text_color),
fontWeight = FontWeight(600),
fontSize = 25.sp,
modifier = Modifier
.padding(top = 30.dp)
.align(Alignment.TopCenter)
)
}
},
sheetShape = RoundedCornerShape(topStart = 25.dp, topEnd = 25.dp),
sheetElevation = 12.dp,
) {
Surface(color = Color.White) {
Column {
Button(
onClick = {
bottomSheetOpen.value = true
scope.launch {
bottomSheetState.show()
}
},
colors = ButtonDefaults.buttonColors(colorResource(id = R.color.button_color)),
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.padding(top = 20.dp, start = 20.dp)
.height(30.dp)
.width(130.dp),
contentPadding = PaddingValues(bottom = 0.dp),
content = {
Text(
text = stringResource(id = R.string.all_cams_button),
color = Color.White,
fontWeight = FontWeight(400)
)
}
)
Text(
modifier = Modifier.padding(top = 50.dp, start = 20.dp),
text = "Репина, 1 Б",
color = colorResource(id = R.color.title_text_color),
fontWeight = FontWeight(700),
fontSize = 18.sp,
letterSpacing= 1.sp
)
ButtonsLazyColumn(
listItem = listOf("Двор", "Подъезд", "Парковка", "Въезд", "Пост охраны"),
height = 50,
modifierPaddingStart = 20,
modifierPaddingTop = 15,
modifierPaddingEnd = 10,
)
Surface(modifier = Modifier
.fillMaxWidth()
.padding(top = 20.dp)
.height(220.dp)
.background(Color.Black)) {
when (viewState.value) {
is ForpostViewState.Loading -> { VideoLoadingView() }
is ForpostViewState.Load -> { PlayerView(forpostViewModel = forpostViewModel) }
else -> {}
}
}
Divider(
modifier = Modifier.padding(top = 100.dp),
color = colorResource(id = R.color.sevstar_gray_light),
thickness = 1.dp
)
ButtonsLazyColumn(
listItem = listOf("1 сек", "1 мин", "5 мин", "10 мин", "30 мин"),
height = 50,
modifierPaddingStart = 20,
modifierPaddingTop = 15,
modifierPaddingEnd = 10
)
Divider(
modifier = Modifier.padding(top = 20.dp),
color = colorResource(id = R.color.sevstar_purple),
thickness = 1.dp
)
}
}
Box(
modifier = Modifier
.fillMaxSize()
.background(
color = if (bottomSheetOpen.value) Color.Black.copy(alpha = 0.5f)
else Color.Transparent
))
}
if (bottomSheetState.currentValue != ModalBottomSheetValue.Hidden
&& bottomSheetState.offset.value > 1800) bottomSheetOpen.value = false
LaunchedEffect(key1 = viewState, block = {
forpostViewModel.obtainEvent(event = ForpostEvents.EnterScreen)
})
}
I made the back darkening myself and it works, but in this case the back part becomes clickable.
You can use something like
val bottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
confirmStateChange = {false}
)
and you have to manually close the bottom sheet like,
onClick = { scope.launch { bottomSheetState.hide() } }
I am very new to jetpack compose please help,
I have use Surface() but my views are overlapping one another,
I want separate view as first one should be TopHeader() and another one should be BillForm
My code is:-
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp {
//TopHeader()
MainContent()
}
}
}
}
#Composable
fun MyApp(content: #Composable () -> Unit) {
JetTipAppTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
content()
}
}
}
TopHeader function to display the pink color view in the given image
#Composable
fun TopHeader(totalPerPerson: Double = 134.0) {
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(15.dp)
.height(150.dp)
.clip(shape = CircleShape.copy(all = CornerSize(12.dp))),
//.clip(shape = RoundedCornerShape(corner = CornerSize(12.dp))),
color = Color(0xFFE9D7F7)
) {
Column(
modifier = Modifier.padding(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
val total = "%.2f".format(totalPerPerson)
Text(
text = "Total per person",
style = MaterialTheme.typography.h5
)
Text(
text = "$$total",
style = MaterialTheme.typography.h4,
fontWeight = FontWeight.ExtraBold
)
}
}
}
This is MainContent function:-
#Composable
fun MainContent() {
BillForm(){ billAmt ->
Log.d("AMT","MainContent: $billAmt")
}
}
BillForm composable function
#Composable
fun BillForm(modifier: Modifier = Modifier,
onValChange: (String) -> Unit = {}
){
val totalBillState = remember{
mutableStateOf("")
}
val validState = remember(totalBillState.value) {
totalBillState.value.trim().isNotEmpty()
}
val keyboardController = LocalSoftwareKeyboardController.current
val sliderPositionState = remember {
mutableStateOf(0f) //slider will take position from zero
}
TopHeader()
Surface(
modifier = Modifier
.padding(2.dp)
.fillMaxWidth(),
shape = RoundedCornerShape(corner = CornerSize(8.dp)),
border = BorderStroke(width = 1.dp, color = Color.LightGray)
) {
Column(
modifier = Modifier.padding(6.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start
) {
InputField(
valueState = totalBillState,
labelId = "Enter Bill",
enabled = true,
isSingleLined = true,
onAction = KeyboardActions{
if (!validState)return#KeyboardActions
onValChange(totalBillState.value.trim())
keyboardController?.hide()
}
)
//if(validState){
Row(
modifier = Modifier.padding(3.dp),
horizontalArrangement = Arrangement.Start
) {
Text(text = "Split",
modifier = Modifier.align(
alignment = Alignment.CenterVertically
))
//Spacer in between text and buttons
Spacer(modifier = Modifier.width(120.dp))
//Row for Buttons
Row(modifier = Modifier.padding(horizontal = 3.dp),
horizontalArrangement = Arrangement.End
) {
RoundIconButton( imageVector = Icons.Default.Remove,
onClick = { })
Text(text = "2",
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(start = 9.dp, end = 9.dp)
)
RoundIconButton( imageVector = Icons.Default.Add,
onClick = { })
}
}
//Tip Row
Row(modifier = Modifier
.padding(horizontal = 3.dp, vertical = 12.dp)
) {
Text(text = "Tip",
modifier = Modifier.align(alignment = Alignment.CenterVertically))
Spacer(modifier = Modifier.width(200.dp))
Text(text = "$33.00",
modifier = Modifier.align(alignment = Alignment.CenterVertically))
}
Column(verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "33%")
Spacer(modifier = Modifier.height(14.dp))
//Slider
Slider(value = sliderPositionState.value,
onValueChange = { newVal ->
sliderPositionState.value = newVal //<- this will change the position of the slider
Log.d("Slider","BillForm: $newVal")
},
modifier = Modifier.padding(start = 16.dp, end = 16.dp),
steps = 5,
onValueChangeFinished = {
}
)
}
// }else{
// Box() {
//
// }
// }
}
}
}
TopHeader composable view:-
BillForm view:-
What I want is like this given image:-
Use a Column(){} Composable for that purpose.
You can follow these basics that can help you to organize your composables and understand how its work.
Based on what was answered in this question (Open ModalSheetLayout on TextField focus instead of Keyboard) and the exchange of comments I did with #Abhimanyu, I was able to get the ModalBottomSheetLayout to appear when I click on one of the TextFields, however I encountered two more problems. I can't center what's in the content, nor can I center the content that's inside the sheet content. Can anyone help me understand why?
Here is a print of what is happening and my code:
#ExperimentalMaterialApi
#Preview
#Composable
fun ProfileScreen() {
var profileModalBottomSheetType by remember { mutableStateOf(ProfileModalBottomSheetType.NONE) }
val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val coroutineScope = rememberCoroutineScope()
if (modalBottomSheetState.currentValue != ModalBottomSheetValue.Hidden) {
DisposableEffect(Unit) {
onDispose {
profileModalBottomSheetType = ProfileModalBottomSheetType.NONE
}
}
}
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetShape = RoundedCornerShape(topStart = 13.dp, topEnd = 13.dp),
sheetContent = {
Box(
modifier = Modifier
.padding(top = 10.dp)
.height(10.dp)
.width(100.dp)
.background(
color = Color.LightGray,
shape = RoundedCornerShape(4.dp)
)
)
when (profileModalBottomSheetType) {
ProfileModalBottomSheetType.SELECT_RATE -> {
SelectRateModalBottomSheet(listOf("Exact Rate", "Range"))
}
else -> {}
}
}
) {
LazyColumn(
modifier = Modifier
.width(320.dp)
) {
item {
HeightSpacer(40.dp)
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = R.drawable.ic_clearjobs_logo_2x),
contentDescription = null
)
}
HeightSpacer(47.dp)
Column(
modifier = Modifier
.width(320.dp)
.padding(start = 20.dp, end = 20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Profile Light Title",
style = TextStyle32Light,
textAlign = TextAlign.Center
)
Text(
text = "Profile Bold Title",
style = TextStyle32Bold,
textAlign = TextAlign.Center
)
}
HeightSpacer(47.dp)
Column(
modifier = Modifier
.background(
shape = RoundedCornerShape(
topStart = 13.dp,
topEnd = 13.dp
), color = Color.White
)
.padding(bottom = 140.dp)
.width(320.dp)
) {
Text(
text = stringResource(id = R.string.your_profile),
style = TextStyle28Bold,
modifier = Modifier.padding(
top = 40.dp,
start = 20.dp,
bottom = 30.dp
)
)
Text(
text = stringResource(id = R.string.salary_range),
style = TextStyle28Bold,
modifier = Modifier.padding(
top = 40.dp,
start = 20.dp,
bottom = 30.dp
)
)
Box {
LightBlueBorderTextField(
title = "Rate",
initialState = "Exact Rate",
textFieldTextStyle = TextStyle16BlackOpacity50Normal,
enabled = false
) { innerTextField ->
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.weight(1f)
.padding(start = Dimen10)
) {
innerTextField()
}
}
}
Box(
modifier = Modifier
.matchParentSize()
.alpha(0f)
.clickable(
onClick = {
profileModalBottomSheetType =
ProfileModalBottomSheetType.SELECT_RATE
toggleModalBottomSheetState(
coroutineScope = coroutineScope,
modalBottomSheetState = modalBottomSheetState,
)
}
)
)
}
}
}
}
}
}
#Composable
fun SelectRateModalBottomSheet(options: List<String>) {
LazyColumn(
modifier = Modifier.padding(
start = 20.dp,
end = 20.dp,
top = 15.dp,
bottom = 15.dp
)
) {
items(options.size) { optionIndex ->
val option = options[optionIndex]
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 15.dp)
) {
Box(Modifier.weight(1f)) {
Text(text = option, style = TextStyle16BlackOpacity50Normal)
}
RadioButton(
selected = false,
onClick = { /*TODO*/ },
modifier = Modifier
.width(20.dp)
.height(20.dp)
)
}
if (optionIndex != options.lastIndex) {
Divider(color = Color.DarkGray, thickness = 1.dp)
HeightSpacer(dimen = 15.dp)
}
}
}
}
#Composable
fun HeightSpacer(dimen: Dp) {
Spacer(modifier = Modifier.height(dimen))
}
#Preview
#Composable
fun LightBlueBorderTextField(
title: String = "",
initialState: String = "",
textFieldTextStyle: TextStyle = TextStyle18Normal,
enabled: Boolean = true,
decorationBox: #Composable (innerTextField: #Composable () -> Unit) -> Unit = { innerTextField ->
Box(
Modifier.padding(start = Dimen10),
contentAlignment = Alignment.CenterStart
) {
innerTextField()
}
}
) {
Column {
val state = remember { mutableStateOf(TextFieldValue(initialState)) }
if (title.isNotEmpty()) {
Text(
text = title,
style = TextStyle16BlackBold,
modifier = Modifier.padding(
top = Dimen40,
start = Dimen30,
bottom = Dimen10
)
)
} else {
HeightSpacer(Dimen40)
}
CustomTextField(
state = state,
modifier = Modifier
.height(Dimen45)
.padding(start = Dimen20, end = Dimen20)
.border(
width = Dimen1,
color = LightBlue,
shape = RoundedCornerShape(Dimen13)
)
.background(Color.White, RoundedCornerShape(Dimen13))
.fillMaxWidth(),
textStyle = textFieldTextStyle,
decorationBox = decorationBox,
enabled = enabled
)
}
}
#Composable
fun CustomTextField(
state: MutableState<TextFieldValue>,
modifier: Modifier,
textStyle: TextStyle = TextStyle18Normal,
decorationBox: #Composable (innerTextField: #Composable () -> Unit) -> Unit,
enabled: Boolean = true
) {
BasicTextField(
modifier = modifier,
value = state.value,
onValueChange = { value -> state.value = value },
singleLine = true,
textStyle = textStyle,
decorationBox = decorationBox,
enabled = enabled
)
}
#ExperimentalMaterialApi
fun toggleModalBottomSheetState(
coroutineScope: CoroutineScope,
modalBottomSheetState: ModalBottomSheetState,
action: (() -> Unit)? = null,
) {
coroutineScope.launch {
if (!modalBottomSheetState.isAnimationRunning) {
if (modalBottomSheetState.isVisible) {
modalBottomSheetState.hide()
} else {
modalBottomSheetState.show()
}
}
action?.invoke()
}
}
internal enum class ProfileModalBottomSheetType {
NONE,
SELECT_JOB_KEYWORDS,
SELECT_WORK_LOCATIONS,
SELECT_TAGS,
SELECT_RATE,
SELECT_SALARY_PERIOD
}
I solved this issue by putting all the ModalBottomSheet content inside a Box with Modifier.fillMaxSize() and the contentAlignment = Alignment.Center. See my code below:
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetShape = RoundedCornerShape(topStart = Dimen13, topEnd = Dimen13),
sheetContent = {
JobDetailsModalBottomSheet(modalBottomSheetType, jobClicked) {
modalBottomSheetType = JobOpeningsScreenModalBottomSheetType.NONE
toggleModalBottomSheetState(
coroutineScope = coroutineScope,
modalBottomSheetState = modalBottomSheetState,
)
}
}) {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
// any content
}
}
I'm not really sure why this doesn't work without Box, but after a few tries, this was the solution I found and it worked.
I have a LazyVerticalGrid composable that shows my items. I want to show an error message with a button in the center of LazyVerticalGrid when an error has occured. Here is my error item and my LazyVerticalGrid:
ErrorItem
#Composable
fun ErrorItem(
message: String,
modifier: Modifier = Modifier,
onRetryClick: () -> Unit,
) {
Column(
modifier = modifier
.padding(dimensionResource(id = R.dimen.dimen_8)),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = message,
maxLines = 1,
style = MaterialTheme.typography.subtitle2,
color = MaterialTheme.colors.error,
modifier = Modifier.padding(vertical = dimensionResource(id = R.dimen.dimen_16))
)
Button(
onClick = onRetryClick,
shape = CircleShape,
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.error,
contentColor = MaterialTheme.colors.onError
)
) {
Text(text = stringResource(id = R.string.try_again))
}
}
}
#Composable
fun CompaniesScreen(
modifier: Modifier = Modifier
) {
val viewModel: CompaniesScreenViewModel = hiltViewModel()
val companies: LazyPagingItems<Company> = viewModel.companies.collectAsLazyPagingItems()
val isLoading = companies.loadState.refresh is LoadState.Loading
val isError = companies.loadState.refresh is LoadState.Error
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isLoading)
val listState = rememberLazyGridState()
Scaffold(modifier = modifier, topBar = { LogoToolbar() }) { padding ->
SwipeRefresh(indicator = { state, trigger ->
SwipeRefreshIndicator(
state = state,
refreshTriggerDistance = trigger,
scale = true,
backgroundColor = MaterialTheme.colors.primary,
contentColor = MaterialTheme.colors.onPrimary
)
}, state = swipeRefreshState, onRefresh = companies::refresh) {
LazyVerticalGrid(
state = listState,
modifier = Modifier
.fillMaxSize()
.padding(
horizontal = dimensionResource(id = R.dimen.dimen_20),
vertical = dimensionResource(
id = R.dimen.dimen_24
)
),
columns = GridCells.Fixed(2),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.dimen_20)),
verticalArrangement = Arrangement.spacedBy(
dimensionResource(
id = R.dimen.dimen_24
)
)
) {
if (!isLoading && !isError) {
items(companies.itemCount) { index ->
CompanyItem(companyName = companies[index]?.name!!)
}
}
companies.apply {
when {
loadState.refresh is LoadState.Loading -> {
items(16) {
CompanyItem(companyName = "", isLoading = true)
}
}
loadState.refresh is LoadState.Error -> {
val e = companies.loadState.refresh as LoadState.Error
item(span = {GridItemSpan(2)}) {
ErrorItem(
modifier = Modifier.fillMaxSize(), // fillParentMaxSize() is not working
message = e.error.localizedMessage
?: stringResource(id = R.string.something_went_wrong),
onRetryClick = companies::retry
)
}
}
}
}
}
}
}
}
I have tried to use fillParentMaxSizewith my ErrorItem but it is not working. Here is the result :
If you place your LazyVerticalGrid and ErrorItem inside a Box with contentAlignment = Alignment.Center ErrorItem will show center of your Box when isError is true
Box(
modifier= Modifier.fillMaxSize(),
contentAlignment = Alignment.Center){
LazyVerticalGrid(
// Rest of the properties
)
if (isError){
ErrorItem(""){
}
}
}
I managed to work this out, and setup 3 cards one on top of the other as seperate boxs compose elements with onclick and on drag properties.
The issue is now, that I'd like the card that I'm pressing/dragging to set to the front, so, I played with the z-index modifier, but, it looks like I'm doing something wrong. Any idea?
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Test1Theme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
for (i in 1 until 4) {
DraggableBox(title = "Box_${+1}", initX = 100f*i.toFloat(), initY = 100f, content =
{
Text(text = "Box_${i}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
}
)
}
}
}
}
}
}
#Composable
fun DraggableBox(title: String, initX: Float = 0f, initY: Float = 0f, content: #Composable() () -> Unit) {
val cardInitWidth = 135f
val cardInitHeight = 190f
val expandValue = 20f
Box(
modifier = Modifier
.fillMaxSize()
) {
val shape = RoundedCornerShape(12.dp)
val coroutineScope = rememberCoroutineScope()
val enable = remember { mutableStateOf(true) }
var offsetX = remember { Animatable(initialValue = initX) }
var offsetY = remember { Animatable(initialValue = initY) }
val interactionSource = remember { MutableInteractionSource() }
val clickable = Modifier.clickable(
interactionSource = interactionSource,
indication = LocalIndication.current
) { }
val isPressed by interactionSource.collectIsPressedAsState()
val size = animateSizeAsState(
targetValue = if (enable.value && !isPressed) {
Size(width = cardInitWidth, height = cardInitHeight)
} else {
Size(width = cardInitWidth + expandValue, height = cardInitHeight + expandValue)
}
)
Box(
Modifier
.offset {
IntOffset(
x = offsetX.value.roundToInt(),
y = offsetY.value.roundToInt()
)
}
.zIndex(zIndex = if (enable.value && !isPressed) 5f else 0f)
.size(size.value.width.dp, size.value.height.dp)
.clip(shape)
//.background(Color(0xFF5FA777))
.background(color = MaterialTheme.colors.primary)
.border(BorderStroke(2.dp, Color.Black), shape = shape)
.pointerInput(Unit) {
detectDragGestures(
onDragStart = {
enable.value = !enable.value
},
onDrag = { change, dragAmount ->
change.consumeAllChanges()
coroutineScope.launch {
offsetX.snapTo(targetValue = offsetX.value + dragAmount.x)
offsetY.snapTo(targetValue = offsetY.value + dragAmount.y)
}
spring(stiffness = Spring.StiffnessHigh, visibilityThreshold = 0.1.dp)
},
onDragEnd = {
enable.value = !enable.value
spring(stiffness = Spring.StiffnessLow, visibilityThreshold = 0.1.dp)
coroutineScope.launch {
launch {
offsetY.animateTo(
targetValue = initY,
animationSpec = tween(
durationMillis = 700,
delayMillis = 50,
easing = LinearOutSlowInEasing
)
)
}
offsetX.animateTo(
targetValue = initX,
animationSpec = tween(
durationMillis = 700,
delayMillis = 50,
easing = LinearOutSlowInEasing
)
)
}
}
)
}
.then(clickable)
) {
Row (modifier = Modifier
.fillMaxHeight(),
verticalAlignment = Alignment.CenterVertically
)
{
Column (modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
)
{
Column (
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(text = "init-X: ${initX.toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
Text(text = "init-Y: ${initY.toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
}
Column (
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(text = "offset-X: ${offsetX.value.roundToInt().toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
Text(text = "offset-Y: ${offsetY.value.roundToInt().toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
}
Column (
horizontalAlignment = Alignment.CenterHorizontally
)
{
content()
}
}
}
}
}
}
The Modifier.zIndex works only for children within the same parent.
In your case you should move this modifier to the topmost Box. To do so you have to move enable and isPressed one level up too, and I would move all the other variables as well - but that's just a matter of taste, I guess.
val enable = remember { mutableStateOf(true) }
val isPressed by interactionSource.collectIsPressedAsState()
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(zIndex = if (enable.value && !isPressed) 5f else 0f)
) {
// ...