How can I set the content in my Scaffold to be reusable with content padding. Is it possible to extract contentPadding from a composable when I declare it? i.e. MyScaffoldContent
Cannot find a parameter with this name: contentPadding
#Composable
fun MyReusableScaffold(scaffoldTitle: String, scaffoldContent: #Composable () -> Unit) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
title = { Text(text = scaffoldTitle) },
scrollBehavior = scrollBehavior
)
},
content = { contentPadding ->
scaffoldContent(contentPadding = contentPadding)
}
)
}
// Example composable
#Composable
fun MyScaffoldContent(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues()
) {
Text(
text = "Header $item",
modifier = Modifier.fillMaxWidth()
)
}
// Example update
#Composable
fun MyHomeScreen(navController: NavController) {
MyReusableScaffold(
scaffoldTitle = "Hello Android",
scaffoldContent = MyScreenContent(contentPadding = ?))
)
}
#Composable
fun MyScreenContent(modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues()) {
Text(
text = "Header",
modifier = Modifier.fillMaxWidth()
)
}
if you want the MyHomeScreen composable to use the value of the content padding from MyReusableScaffold, you have to define a parameter in the scaffoldContent lambda and pass the contentPadding to it.
#Composable
fun MyReusableScaffold(scaffoldContent: #Composable ( contentPadding: PaddingValues) -> Unit) {
Scaffold(
topBar = {
},
content = { contentPadding ->
scaffoldContent(contentPadding = contentPadding)
}
)
}
#Composable
fun MyScaffoldContent(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues()
) {
Text(
text = "Header",
modifier = Modifier.fillMaxWidth()
)
}
#Composable
fun MyHomeScreen(navController: NavController) {
MyReusableScaffold(
scaffoldContent = {
MyScreenContent(contentPadding = it)
}
)
// or
// MyReusableScaffold(
// scaffoldContent = {
// MyScaffoldContent(contentPadding = it)
// }
// )
}
Related
I am using IconButton in my project. I am opening Dialog when I am click on IconButton. Whenever I clicked on IconButton the dialog opens and IconButton little bit shifts other side and back to position when Dialog close. You can see in this video. So what is the problem of this? I want to avoid this..
OptionItemStateFul
#Composable
fun OptionItemStateFul() {
val isOptionItemClickAction = remember { mutableStateOf(false) }
OptionItemStateLess(isOptionItemClickAction) {
DialogOptionsView(
openDialogCustom = isOptionItemClickAction,
dialogOptionsData = DialogOptionsData(headerText = "Hi There", itemsList = listOf(1, 2)),
)
}
}
OptionItemStateLess
#Composable
fun OptionItemStateLess(
isOptionItemClickAction: MutableState<Boolean>,
onOptionItemClickAction: #Composable () -> Unit,
) {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(5.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = " itemName 1 item 2",
modifier = Modifier
.padding(vertical = 13.dp)
.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OptionItemLabel(isOptionItemClickAction, onOptionItemClickAction)
}
}
OptionItemLabel
#Composable
fun OptionItemLabel(
isOptionItemClickAction: MutableState<Boolean>,
onOptionItemClickAction: #Composable () -> Unit
) {
IconButton(
modifier = Modifier.padding(end = 10.dp),
onClick = { isOptionItemClickAction.value = true }
) {
Icon(
painter = painterResource(R.drawable.ic_menu),
contentDescription = null,
tint = Aqua,
)
}
AnimatedVisibility(isOptionItemClickAction.value) {
onOptionItemClickAction()
}
}
PreviewOptionItemStateFul
#Preview(showBackground = true)
#Composable
fun PreviewOptionItemStateFul() {
OptionItemStateFul()
}
Thanks
This change is coming because the dialog is in the row, so the dialog should be kept outside the row. Like this
updated - OptionItemStateLess
#Composable
fun OptionItemStateLess(
isOptionItemClickAction: MutableState<Boolean>,
onOptionItemClickAction: #Composable () -> Unit,
) {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(5.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = " itemName 1 item 2",
modifier = Modifier
.padding(vertical = 13.dp)
.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OptionItemLabel(isOptionItemClickAction)
}
AnimatedVisibility(isOptionItemClickAction.value) {
onOptionItemClickAction()
}
}
updated - OptionItemLabel
#Composable
fun OptionItemLabel(
isOptionItemClickAction: MutableState<Boolean>
) {
IconButton(
modifier = Modifier.padding(end = 10.dp),
onClick = { isOptionItemClickAction.value = true }
) {
Icon(
Icons.Default.Menu,
contentDescription = null,
)
}
}
I am working with Jetpack Compose and I have a page with several TextFields, where I want that, in several of them, when I click on the input, instead of appearing the keyboard, a ModalSheetLayout appears with different layouts that I have. Is this possible? I'm still not able to do this, all I can do is simple things when the focus changes. Can anyone help me?
The below sample should give a basic idea of how to do this.
Code
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun BottomSheetSelectionDemo() {
val coroutineScope: CoroutineScope = rememberCoroutineScope()
val modalBottomSheetState: ModalBottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
)
val colors = arrayListOf("Red", "Green", "Blue", "White", "Black")
val (value, setValue) = remember {
mutableStateOf(colors[0])
}
val toggleModalBottomSheetState = {
coroutineScope.launch {
if (!modalBottomSheetState.isAnimationRunning) {
if (modalBottomSheetState.isVisible) {
modalBottomSheetState.hide()
} else {
modalBottomSheetState.show()
}
}
}
}
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetContent = {
LazyColumn {
items(colors) {
Text(
text = it,
modifier = Modifier
.fillMaxWidth()
.clickable {
setValue(it)
toggleModalBottomSheetState()
}
.padding(
horizontal = 16.dp,
vertical = 12.dp,
),
)
}
}
},
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize(),
) {
MyReadOnlyTextField(
value = value,
label = "Select a color",
onClick = {
toggleModalBottomSheetState()
},
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = 16.dp,
vertical = 4.dp,
),
)
}
}
}
#Composable
fun MyReadOnlyTextField(
modifier: Modifier = Modifier,
value: String,
label: String,
onClick: () -> Unit,
) {
Box(
modifier = modifier,
) {
androidx.compose.material3.OutlinedTextField(
value = value,
onValueChange = {},
modifier = Modifier
.fillMaxWidth(),
label = {
Text(
text = label,
)
},
)
Box(
modifier = Modifier
.matchParentSize()
.alpha(0f)
.clickable(
onClick = onClick,
),
)
}
}
I have the following screen built in Compose -
#Composable
fun DashboardScreen(heroesViewModel: HeroesViewModel = get()) {
val searchState by heroesViewModel.searchState.collectAsState()
val uiState by heroesViewModel.uiState.collectAsState()
val focusRequester = remember { FocusRequester() }
Column(modifier = Modifier.fillMaxSize()) {
SearchBar(
searchState = searchState,
onQueryChanged = { text ->
heroesViewModel.submitEvent(HeroesViewModel.UiEvent.SearchQueryChanged(text))
},
onSearchFocusChange = { focused ->
heroesViewModel.submitEvent(HeroesViewModel.UiEvent.SearchBarFocusChanged(focused))
},
onClearQueryClicked = {
heroesViewModel.submitEvent(HeroesViewModel.UiEvent.ClearQueryClicked)
},
onBack = {},
focusRequester
)
LazyColumn {
items(uiState.modelsListResponse ?: listOf()) { model ->
if (model is HeroListSeparatorModel)
HeroesListSeparatorItem(model)
else if (model is HeroesListModel)
HeroesListItem(model) {
heroesViewModel.submitEvent(HeroesViewModel.UiEvent.ListItemClicked(model))
}
}
}
}
}
And here is my SearchBar -
#Composable
fun SearchBar(
searchState: SearchState,
onQueryChanged: (String) -> Unit,
onSearchFocusChange: (Boolean) -> Unit,
onClearQueryClicked: () -> Unit,
onBack: () -> Unit,
focusRequester : FocusRequester,
modifier: Modifier = Modifier
) {
val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current
val focused = searchState.focused
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
AnimatedVisibility(visible = focused) {
BackButton(focusManager, keyboardController, onBack)
}
SearchTextField(
searchState,
onQueryChanged,
onSearchFocusChange,
onClearQueryClicked,
focusRequester
)
}
}
#Composable
fun SearchTextField(
searchState: SearchState,
onQueryChanged: (String) -> Unit,
onSearchFocusChanged: (Boolean) -> Unit,
onClearQueryClicked: () -> Unit,
focusRequester : FocusRequester,
modifier: Modifier = Modifier
) {
val focused = searchState.focused
var query = searchState.query
val searching = searchState.searching
Surface(
modifier = modifier
.then(
Modifier
.height(56.dp)
.padding(
top = 8.dp, bottom = 8.dp,
start = if (focused.not()) 16.dp else 0.dp,
end = 16.dp
)
),
color = Color(0xffF5F5F5),
shape = RoundedCornerShape(percent = 50)
) {
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Box(
contentAlignment = Alignment.CenterStart,
modifier = modifier
) {
if (query.isEmpty()) {
SearchHint(modifier = modifier.padding(start = 24.dp, end = 8.dp))
}
Row(verticalAlignment = Alignment.CenterVertically) {
BasicTextField(
value = query,
onValueChange = {
query = it
onQueryChanged(it)
},
modifier = Modifier
.fillMaxSize()
.weight(1f)
.onFocusChanged { focusState ->
onSearchFocusChanged(focusState.isFocused)
}
.focusRequester(focusRequester)
.padding(top = 9.dp, bottom = 8.dp, start = 24.dp, end = 8.dp),
singleLine = true
)
when {
searching -> {
CircularProgressIndicator(
modifier = Modifier
.padding(horizontal = 16.dp)
.width(25.dp)
.size(24.dp)
)
}
query.isNotEmpty() -> {
IconButton(onClick = onClearQueryClicked) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = null
)
}
}
}
}
}
}
}
}
#Composable
fun SearchHint(
modifier: Modifier = Modifier,
hint: String = "Enter a hero name"
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxSize()
.then(modifier)
) {
Text(
text = hint,
color = Color(0xff757575)
)
}
}
class SearchState(
query: String,
focused: Boolean,
searching: Boolean,
) {
var query by mutableStateOf(query)
var focused by mutableStateOf(focused)
var searching by mutableStateOf(searching)
}
What I want to achieve is the ability to know when the user has tapped or click anywhere outside of my SearchBar Composable. I want to send an event to the ViewModel so that he recomposes the screen, removing the keyboard and removing the cursor I have on my SearchBar (basically just resetting the focus).
I have tried using the focusRequester like I did in my SearchBar but without success as nothing happened, tried using the clickable {} block which is not what I need (I need the tap and not the click) and tried using Modifier.pointerInput with detectTapGestures and it did not work, not at the root LazyColumn and not at the ListItem level.
What I am looking for should be something really easy.
Found the answer - I ended up using isScrollInProgress variable from LazyListState class that provides a boolean indicating if the list is currently in scrolling. When the value was true I removed the focus from where I needed to remove it and it worked :)
Attached the solution -
val listState = rememberLazyListState()
...
LazyColumn(state = listState) {
items(uiState.modelsListResponse ?: listOf()) { model ->
heroesViewModel.submitEvent(HeroesViewModel.UiEvent.ListIsScrolling(listState.isScrollInProgress))
if (model is HeroListSeparatorModel)
HeroesListSeparatorItem(model)
else if (model is HeroesListModel)
HeroesListItem(model) {
heroesViewModel.submitEvent(HeroesViewModel.UiEvent.ListItemClicked(model))
}
}
}
Use PointerInputModifier which provides access to the underlying MotionEvents originally dispatched to Compose.
Text(modifier = Modifier
.pointerInteropFilter { motionEvent ->
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
// When the user touches the composable
}
MotionEvent.ACTION_UP -> {
// When the user removes touch from the composable
}
}
true
},
text = "Click Me!"
)
Inside pointerInteropFilter block MotionEvent.ACTION_DOWN is triggered when user touches on the composable.
I am try to learn jetpack compose, and I want to apply bottom sheet for on long click listener, I have example of code below, and I try to use bottom sheet for onLongClick listener, but I have some confused about how can I apply for row data? I had some example but I do not have an idea about this problem?
#Composable
fun BSDataScreen() {
val modalBottomSheetState = rememberModalBottomSheetState(initialValue =
ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetContent = {
SheetScreen()
},
sheetState = modalBottomSheetState,
sheetShape = RoundedCornerShape(topStart = 15.dp, topEnd = 15.dp),
sheetBackgroundColor = Color.White,
) {
Scaffold(
backgroundColor = Color.White,
) {
DataScreen(
scope = scope, state = modalBottomSheetState)}}}
#Composable
fun DataScreen(
scope: CoroutineScope,
state: ModalBottomSheetState
) {
val listOfData = listOf(
Data( painterResource(R.drawable.image1)),
Data(painterResource(R.drawable.image2)),
Data(painterResource(R.drawable.image3),)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.background(Color.White)
) {
LazyColumn(
modifier = Modifier
) {
items(listOfData.size) { index ->
DataListItem(listOfData[index])}}}}
#Composable
fun DataListItem(data: Data) {
val context = LocalContext.current
Column(
modifier = Modifier.padding(5.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(5.dp)
.combinedClickable(
onLongClick= {
scope.launch {
state.show()
}},)
) {
Image(
painter = data.painter,
contentDescription = null,)}}}
You can create a lambda onClick: (Data) -> Unit and hoist state from DataListItem to DataScreen for each item
#Composable
fun DataListItem(data: Data, onLongClick: (Data) -> Unit) {
val context = LocalContext.current
Column(
modifier = Modifier.padding(5.dp)
) {
Row(modifier = Modifier
.fillMaxWidth()
.padding(5.dp)
.combinedClickable(
onClick = {
},
onLongClick = {
onLongClick(data)
}
)
) {
}
}
}
And in DataScreen
LazyColumn(
modifier = Modifier
) {
items(listOfData.size) { index ->
DataListItem(listOfData[index]) { data: Data->
// you can call bottom sheet or pass data if required
scope.launch {
state.show()
}
}
}
}
I am try to learning jetpack compose those days, so I want to learning bottom sheet in jetpack compose, I do it just for one text, but I want to use it for multiple text, I have one example here, but I am not sure how to implement to my project, is there any solution for more than two text button to apply bottom sheet?
#Composable
fun BottomSheetMyScreen() {
val modalBottomSheetState = rememberModalBottomSheetState(initialValue =
ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetContent = {
BottomSheetFirstScreen()
},
sheetState = modalBottomSheetState,
sheetShape = RoundedCornerShape(topStart = 5.dp, topEnd = 5.dp),
sheetBackgroundColor = Color.Red,
) {
Scaffold(
backgroundColor = Color.Red
) {
MyScreen(
scope = scope, state = modalBottomSheetState
)}}}
#Composable
fun MyScreen(
scope: CoroutineScope, state: ModalBottomSheetState,
) {
MainRow(
name = "name1",
onClick = { scope.launch {
state.show()
}}
)
MainRow(
name = "name2",
onClick = { scope.launch {
state.show()
} }
)}}
#Composable
fun MainRow(
name: String,
onClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clickable(onClick = onClick),
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.width(150.dp)
) {
Text(
text = name,
)}}}
I edit Your Code, you can use this code:
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun BottomSheetMyScreen() {
val modalBottomSheetState = rememberModalBottomSheetState(
initialValue =
ModalBottomSheetValue.Hidden
)
val scope = rememberCoroutineScope()
val sheetContentState = remember {
mutableStateOf(0)
}
ModalBottomSheetLayout(
sheetContent = {
when (sheetContentState.value) {
0 -> {
BottomSheetFirstScreen()
}
1 -> {
BottomSheetSecondScreen()
}
}
},
sheetState = modalBottomSheetState,
sheetShape = RoundedCornerShape(topStart = 5.dp, topEnd = 5.dp),
sheetBackgroundColor = Color.Red,
) {
Scaffold(
backgroundColor = Color.Red
) {
MyScreen(
scope = scope, state = modalBottomSheetState, sheetContentState = sheetContentState
)
}
}
}
Main Row
#Composable
fun MainRow(
name: String,
onClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = name,
modifier = Modifier.clickable { onClick.invoke() }
)
}
}
And MyScreen
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun MyScreen(
scope: CoroutineScope,
state: ModalBottomSheetState,
sheetContentState: MutableState<Int>
) {
Column(modifier = Modifier.fillMaxWidth()) {
MainRow(
name = "name 1",
onClick = {
scope.launch {
sheetContentState.value = 0
state.show()
}
}
)
MainRow(
name = "name 2",
onClick = {
scope.launch {
sheetContentState.value = 1
state.show()
}
}
)
}
}