How to combine multiple Modifier objects in Jetpack Compose? - android

I have a composable that passes a Modifier instance to its child composable as follows:
#Composable
fun MyComposable(
modifier: Modifier = Modifier,
content: #Composable BoxScope.() -> Unit,
) {
Box(
modifier = modifier.fillMaxWidth(),
content = content,
)
}
This adds the fillMaxWidth modifier to the modifier argument. However, this is not the desired behaviour because I would like fillMaxWidth to be the default width, but still allow the caller to override it.
How do I combine/merge the two modifiers while making my local modifiers the default?

You can simply use Modifier.then(otherModifier).
Note: Order is important and you might want to consider what you are adding yourself and what you are adding from outside.
composed is used for stateful modifiers like when you want to implement custom touch controls where you will be called every-time anything changes.
See Composed Docs

Use the Modifier.composed function.
#Composable
fun MyComposable(
modifier: Modifier = Modifier,
content: #Composable BoxScope.() -> Unit,
) {
OtherComposable(
modifier = Modifier.fillMaxWidth().composed { modifier },
content = content,
)
}

Use the Modifier.then function.
#Composable
fun MyComposable(
modifier: Modifier = Modifier,
content: #Composable BoxScope.() -> Unit,
) {
OtherComposable(
modifier = Modifier.fillMaxWidth().then(modifier),
content = content,
)
}

Related

When should one use Modifier.then?

What is the purpose or when is it useful to use Modifier.then in jetpack compose?
If you decided some certain properties for a composable but still want to make it customizable you can use then method to take a modifier that you passed as a parameter in your Composable's constructor.
An example :
#ExperimentalComposeUiApi
#Composable
fun CalculatorButton(
symbol: String,
modifier: Modifier = Modifier,
color: Color = Color.White,
textStyle: TextStyle = TextStyle(),
onClick: () -> Unit
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.clip(RoundedCornerShape(100.dp))
.background(color)
.clickable {
onClick()
}
.then(modifier) // <--------- This Line we pass modifier parameter
// after certain properteis
) { //content
}
You can use it for conditionals, for instance
Modifier. fillMaxWitdh()
.then(
if (condition) Modifier.background(color)
else Modifier.alpha(alpha)
)

Why do I get the warning information "Modifier parameter should be named modifier" in Jetpack Compose?

I use Jetpack Compose in a Android Studio project, the code A can work well, but I get the following warning information, why ?
"Modifier parameter should be named modifier"
Code A
#Composable
fun ScreenAbout(
rootModifier: Modifier = Modifier,
onBack: () -> Unit,
scaffoldState: ScaffoldState = rememberScaffoldState()
) {
Scaffold(
modifier = rootModifier.fillMaxSize(),
scaffoldState = scaffoldState,
topBar = { AboutAppBar(onBack = onBack) }
) { paddingValues ->
Column(
...
}
There is Link Check.
It checks Composable functions with Modifiers parameters for consistency with guidelines.
For functions with one / more modifier parameters, the first modifier parameter must:
Be named modifier
Have a type of Modifier
Either have no default value, or have a default value of Modifier
If optional, be the first optional parameter in the parameter list
In you case just use:
#Composable
fun ScreenAbout(
modifier: Modifier = Modifier,
Or if you want to suppress the warning add
#SuppressLint("ModifierParameter")
#Composable
fun ScreenAbout(
rootModifier: Modifier = Modifier,

how should name the modifier params in Jetpack compose component which gets two or more modifiers?

If we have a compose component which gets two or more modifiers, how should we handle it ?
I mean the naming of modifiers while lint complains changing the name of modifier parameter
Sample code to figure out easily :
#Composable
private fun CompletionSection(iconModifier: Modifier, textModifier: Modifier, isActivated: Boolean, newText: String?) {
if (isActivated) {
Icon(
painter = painterResource(R.drawable.ds_ic_check_circle),
modifier = iconModifier
.wrapContentSize()
.padding(top = 18.dp),
tint = MaterialTheme.colors.positive,
contentDescription = null
)
} else if (!newText.isNullOrBlank()) {
Surface(
modifier = textModifier.padding(top = 18.dp),
shape = RoundedCornerShape(32.dp),
border = BorderStroke(width = 2.dp, color = MaterialTheme.colors.primary.copy(alpha = 0.6f)),
) {
Text(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
fontSize = 11.sp,
color = MaterialTheme.colors.primary.copy(alpha = 0.6f),
text = newText,
modifier = Modifier
.defaultMinSize(minHeight = 20.dp)
.wrapContentSize()
.padding(horizontal = 6.dp, vertical = 2.dp),
style = MaterialTheme.typography.android.caption2
)
}
}
}
Here, where the function is used →
ConstraintLayout(
modifier = Modifier.fillMaxSize(),
constraintSet = decoupledConstraints(
marginSpacing02 = marginSpacing02,
marginSpacing01 = marginSpacing01,
entity = entity
)
) {
CompletionSection(
iconModifier = Modifier.layoutId("completedIcon"),
textModifier = Modifier.layoutId("newTextField"),
isActivated = isActivated,
newText = newText
)
}
I assume the reason for this kind of warning is because you usually have one modifier that has to be applied to the whole view. Having an other modifier in arguments is kind of OK, but, for example if you need to apply Modifier.align, you had to duplicate it.
In your case, when you look from where you're using this function, it's hard to tell which modifier will be applied and which is not - it depends on other parameters and you have to know the logic.
I think at least it could have one generic modifier named modifier, which would apply for both views, and two named ones - in my opinion this would make the API a bit more predictable. You can chain modifiers like this: modifier.then(iconModifier).yourModifier()
Anyway, you can suppress it:
#SuppressLint("ModifierParameter")
#Composable
// ...
https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier
Composables that accept modifiers to be applied to a specific subcomponent foo should name the parameter fooModifier and follow the same guidelines above for default values and behavior. Subcomponent modifiers should be grouped together and follow the parent composable's modifier. For example:
#Composable
fun ButtonBar(
onOk: () -> Unit,
onCancel: () -> Unit,
modifier: Modifier = Modifier,
buttonModifier: Modifier = Modifier
) {
Row(modifier) {
Button(onCancel, buttonModifier) {
Text("Cancel")
}
Button(onOk, buttonModifier) {
Text("Ok")
}
}
}
Composables are designed to accept only one Modifier, so the lint won't be satifsfied no matter how you rename them.
The Composable is something like a unit of interface, and it having multiple modifiers is leaking its inner workings to the outer composables that use it.

ColumnScope Modifier as a parameter of a composable

I'm making a compose scaffold-like composable which contains a text composable. I want the scaffold to have a modifier parameter for the text. I would like to be able to pass a modifier from ColumnScope, for instance to be able to align the text.
Here is a simplified version of the scaffold:
#Composable
fun MyScaffold(
text: String,
modifier: Modifier,
) {
Text(
modifier = modifier,
text = text
)
}
When calling the Scaffold, I want to pass a modifier so that the text will be horizontally aligned:
#Preview
#Composable
fun PreviewScaffold() {
MyScaffold(
text = "Hello",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
However this doesn't compile, because the align() method is an extension function in the ColumnScope so it only works when inside the ColumnsScope.
How can I pass a ColumnScope Modifier as a parameter of MyScaffold?
Please note that an alternative would be to pass a whole Text composable in the scaffold instead of just a string, but I'd rather have just a string so that I can make most of the styling inside the scaffold but just give the outside caller the possibility to override it.
Thanks!
You can scope your composable with the required scope:
#Composable
fun ColumnScope.MyScaffoldX(
text: String,
modifier: Modifier,
) {
Text(
modifier = modifier.align(Alignment.CenterHorizontally),
text = text
)
}
and then use it with:
Column() {
MyScaffoldX(text = "Hello")
}
Otherwise you can have the Modifier parameter in your composable as in your example:
#Composable
fun MyScaffold(
text: String,
modifier: Modifier = Modifier,
) {
Text(
modifier = modifier,
text = text
)
}
using it with:
Column() {
MyScaffold(
text = "Hello",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}

Is it possible to "Scale" width and height any widget in jetpack compose?

I have a Stack widget which hosts a Box and an Image.
As the state changes, I want to scale the Box widget by whatever value the state has, for example by 2x.
I couldn't find anything about scaling the widgets on the Modifier or Box properties so I decided to react to the state changes by manipulating the size using "Modifier.size" which is not ideal for me.
So is there support for scaling the widgets or should I manually play with the size property?
-Thanks
#Composable
fun Pointer(modifier: Modifier = Modifier, state: TransitionState, onClick: () -> Unit) {
Stack(modifier) {
Box(
shape = CircleShape, backgroundColor = Color.Gray.copy(alpha = .3f),
modifier = Modifier.size(state[width])
)
Image(
asset = imageResource(id = R.drawable.ic_pointer),
modifier = Modifier
.clickable(onClick = onClick)
)
}
}
Compose 1.0.0-alpha08
As time passes by, there is a new compose version which renames 'drawLayer' to 'graphicsLayer' and adds a new scale modifier (uses 'graphicsLayer' underneath).
Thus composable will look like:
#Composable
fun Pointer(scale: Float, modifier: Modifier = Modifier) {
Box(modifier) {
Box(
modifier = Modifier
.matchParentSize()
.scale(scale)
.background(Color.Cyan, CircleShape)
)
Image(
imageVector = Icons.Filled.Done,
modifier = Modifier
.align(Alignment.Center)
)
}
}
Compose 1.0.0-alpha07 (original answer)
I believe you may achive the desired behavior with drawLayer modifier. For example a simple composable, which displays an scalable circle and an unscalable icon on top of it:
#Composable
fun Pointer(scale: Float, modifier: Modifier = Modifier) {
Box(modifier) {
Box(
modifier = Modifier
.matchParentSize()
.drawLayer(scaleX = scale, scaleY = scale)
.background(Color.Cyan, CircleShape)
)
Image(
asset = Icons.Filled.Done,
modifier = Modifier
.align(Alignment.Center)
)
}
}
And usage:
Pointer(
scale = 1f,
modifier = Modifier
.background(Color.Magenta)
.padding(25.dp)
.preferredSize(50.dp)
.align(Alignment.CenterHorizontally)
)
When scale = 1f
When scale = 2f
I don't want to care about the size of the box or explicitly keep a reference to it
That is going to be a problem. In Compose, widgets like Box() are stateless functions. You cannot ask a Box() how big it is — instead, you need to tell the Box() how big it is, using a suitable Modifier.
Frequently, "how big it is" is a fixed value or rule, set in the code (e.g., Modifier.size(200.dp)). You should be able to have the size be dependent upon some state that you track yourself, so long as that state is a State, so Compose knows to recompose (call your function again) when that State changes. If you go that route, then scaling is a matter of checking the current State value, applying your scale factor, and using the result for the new State value.

Categories

Resources