The Slider in Jetpack Compose allows changing the color of the thumb but not the shape.
I'm looking solution to change the shape of the thumb from circle to rectangle as represented in the attached image
I tried to add Slider.kt file to the project as mentioned here but, strangely, when I copy this code to the project I got a lot of errors, see attached screenshot
With M3 androidx.compose.material3.Slider you can use the thumb attribute to use a custom thumb.
You can use a simple Spacer or a Box to obtain a Rectangle:
var sliderPosition by remember { mutableStateOf(0f) }
val interactionSource = MutableInteractionSource()
Column {
Text(text = sliderPosition.toString())
Slider(
modifier = Modifier.semantics { contentDescription = "Localized Description" },
value = sliderPosition,
onValueChange = { sliderPosition = it },
valueRange = 0f..5f,
steps = 4,
interactionSource = interactionSource,
onValueChangeFinished = {
// launch some business logic update with the state you hold
},
thumb = {
val shape = RectangleShape
Spacer(
modifier = Modifier
.size(20.dp)
.indication(
interactionSource = interactionSource,
indication = rememberRipple(
bounded = false,
radius = 20.dp
)
)
.hoverable(interactionSource = interactionSource)
.shadow(if (enabled) 6.dp else 0.dp, shape, clip = false)
.background(Red, shape)
)
},
)
}
Note: it requires for material3 at least the version 1.0.0-beta03
Here is a source code for Slider composable.
You can copy it to your project, rename, and change the Thumb shape in the SliderThumb composable:
...
.shadow(if (enabled) elevation else 0.dp, CircleShape, clip = false)
.background(colors.thumbColor(enabled).value, CircleShape)
...
Related
Hey guys I am using RoundedCornerShape(4.dp) to my Surface which looks fine. When I tried to click on the item it not showing me 4dp corner in Surface. I tried this stack overflow 1 and stack overflow 2 but nothing works.
binding.itemComposable.setContent {
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
val options = getOptions()
options.forEachIndexed { _, optionText ->
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val backgroundColor = if (isPressed) DuckEggBlue else OffWhite
val textColor = if (isPressed) TealBlue else Slate
val borderWidth = if (isPressed) 1.dp else 0.dp
val borderColor = if (isPressed) Aqua else OffWhite
val clickable = Modifier.clickable(
interactionSource = interactionSource,
indication = rememberRipple(true)
) {
println("Item Click")
}
Surface(
modifier = Modifier
.then(clickable)
.border(borderWidth, borderColor),
shape = RoundedCornerShape(4.dp)
) {
Text(
modifier = Modifier
.fillMaxWidth()
.background(backgroundColor)
.padding(16.dp),
text = optionText,
style = Typography.h3,
fontWeight = FontWeight.Medium,
color = textColor
)
}
}
}
}
Without click on item corner is 4 dp
When I click it's not changing corner
If you want to handle the click on a Surface you have to use the function that accepts an onClick():
Surface(
onClick = {},
shape = RoundedCornerShape(4.dp),
border = BorderStroke(borderWidth,borderColor),
interactionSource = interactionSource
)
Create a variable for shape
val shape = RoundedCornerShape(4.dp)
Use it in Modifier.clip() and Modifier.border() like this,
Surface(
modifier = Modifier
.clip(shape)
.border(
width = borderWidth,
color = borderColor,
shape = shape,
)
.then(clickable),
// shape = shape,
)
shape in border() specifies the shape of the border which by default is RectangleShape. Hence, you are seeing the rectangle border.
shape in clip() changes the shape of the composable before the click action is added. This is to make the ripple effect appear only on the given shape.
Note: Order of modifiers are important.
The shape in the Surface may not be needed after these changes.
If youre using Surface to wrapping the content, try to add a container inside the content for example Box or Column. Then use your Surface only as a shape mask, the background and other content will be flexible as you want.
This is the example
Surface(
modifier = Modifier
.then(clickable)
.border(borderWidth, borderColor),
shape = RoundedCornerShape(4.dp)
) {
Box(modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.background(Color.Green)){
Text(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
text = optionText,
style = Typography.h3,
fontWeight = FontWeight.Medium,
color = textColor
)
}
}
I want to display a ripple affect after click on a view and also change it alpha after click on it.
However, the ripple effect only work well if alpha change from 0.5->1, when alpha change from 1->0.5, the ripple effect don't display fully.
fun Greeting2(name: String) {
val isProcessing = remember { mutableStateOf(false) }
Column(
modifier = Modifier
.padding(36.dp)
.alpha(if (isProcessing.value) 0.5f else 1f)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
) {
isProcessing.value = !isProcessing.value
}
) {
Image(
painter = painterResource(R.drawable.ic_btn_speak_now),
contentDescription = "",
modifier = Modifier
.width(80.dp)
.height(80.dp)
)
Text(text = "Hello $name!")
}
}
Here is the demo. Any way to achieve both alpha and ripple effect together?
I guess it might be because of the mechanism how the ripple is displayed internally. Maybe it's a sort of clash between the recompositions occuring because of both the changing alpha and the propagating ripple. To fix that, you can just wrap your column in another composable, like so.
#Preview
#Composable
fun Greeting2() {
val name = "Android!" // I used preview so had to remove the parameter
var isProcessing by remember { mutableStateOf(false) }
val alpha by animateFloatAsState(targetValue = if (isProcessing) 0.5f else 1f, animationSpec = keyframes { durationMillis = 1 })
Box(
Modifier
.alpha(alpha)
){
Column(
modifier = Modifier
.padding(36.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
) {
isProcessing = !isProcessing
}
) {
Image(
painter = painterResource(R.drawable.ic_launcher_foreground),
contentDescription = "",
modifier = Modifier
.width(80.dp)
.height(80.dp)
)
Text(text = "Hello $name!")
}
}
}
I might have made some modifications but you get the idea. Also, I wrapped in a Box instead of a Surface since it defaults to a background.
My code:
OutlinedTextField(
value = state.value,
onValueChange = { state.value = it },
modifier = Modifier.fillMaxWidth().padding(start = 30.dp, end = 30.dp),
label = { Text(text = "Something", fontSize = 14.sp) },
shape = RoundedCornerShape(12.dp),
)
I want to increase the border width so that the colors focusedBorderColor, disabledBorderColor are supported.
Outline border is defined as a constant value in OutlinedTextField.
private val IndicatorUnfocusedWidth = 1.dp
private val IndicatorFocusedWidth = 2.dp
There is no direct way to override these values.
So, you have to create complete custom TextField Composables if you need to achieve dynamic border width.
You can copy-paste the complete code in OutlinedTextField.kt and TextFieldImpl.kt and modify them as required to create the custom Composables.
You can change OutlinedTextField border like this
var hasFocus by remember { mutableStateOf(false) }
OutlinedTextField(
modifier = modifier
.border(
width = 1.dp,
color = if (hasFocus) Color.Red else Color.Unspecified
)
.onFocusChanged { focusState -> hasFocus = focusState.hasFocus },
colors = TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = Color.Unspecified,
unfocusedBorderColor = Color.Unspecified
)
)
Another solution is to use BaseTextField instead of OutlinedTextField
I am trying the change a button background from a solid color to a dawable image with transparent background to make sure I can see the pattern
I moved to jetpack so I have created
Button(onClick = { /*TODO*/ },
colors = ButtonDefaults.buttonColors(
backgroundColor = colorResource(id = R.color.gainsboro_00)),
modifier = Modifier
.fillMaxWidth()
.height(60.dp),
shape = RoundedCornerShape(0.dp)) {
Text(text = stringResource(id = R.string.login),
color = colorResource(id = R.color.gainsboro_05),
style = MaterialTheme.typography.body1)
}
This button has a grey background.
I would like to apply the drawable below:
<?xml version="1.0" encoding="UTF-8" ?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/pattern_stripe_loose_gainsboro_05"
android:tileMode="repeat"/>
as a background instead of the colored one and have the pattern displayed. the above xml is just repeating a pattern to create a background
So I expect this:
using the pattern:
When using the traditional way with layout and so on it works but I can't make it work on jetpack
Any idea ?
Compose does not have such a feature yet. You can create a feature request on issue tracker.
Before this is implemented, you could create a pure Compose solution that draws a lot of images, but I think there is no point in doing that when there is ImageView already optimized by engineers. In such cases you can use AndroidView for interop with old views.
#Composable
fun TileAndroidImage(
#DrawableRes drawableId: Int,
contentDescription: String,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val drawable = remember(drawableId) {
BitmapDrawable(
context.resources,
BitmapFactory.decodeResource(
context.resources,
drawableId
)
).apply {
tileModeX = Shader.TileMode.REPEAT
tileModeY = Shader.TileMode.REPEAT
}
}
AndroidView(
factory = {
ImageView(it)
},
update = { imageView ->
imageView.background = drawable
},
modifier = modifier
.semantics {
this.contentDescription = contentDescription
role = Role.Image
}
)
}
Next part, is puttin it in background of the button. In compose we use containers to do so. You can create your TiledButton. I pass zero padding to container button and add real padding manually so it'll not affect the background:
#Composable
fun TiledButton(
onClick: () -> Unit,
#DrawableRes backgroundDrawableId: Int,
modifier: Modifier = Modifier,
enabled: Boolean = true,
shape: Shape = MaterialTheme.shapes.small,
border: BorderStroke? = null,
contentColor: Color = MaterialTheme.colors.primary,
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
content: #Composable RowScope.() -> Unit
) {
Button(
onClick = onClick,
contentPadding = PaddingValues(0.dp),
enabled = enabled,
shape = shape,
border = border,
elevation = null,
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Transparent,
contentColor = contentColor,
disabledBackgroundColor = Color.Transparent,
disabledContentColor = contentColor.copy(alpha = ContentAlpha.disabled),
),
modifier = modifier
) {
Box(
contentAlignment = Alignment.Center,
) {
TileAndroidImage(
drawableId = backgroundDrawableId,
contentDescription = "...",
modifier = Modifier.matchParentSize()
)
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(contentPadding),
content = content,
)
}
}
}
Usage:
TiledButton(
onClick = { /*TODO*/ },
backgroundDrawableId = R.drawable.tile,
border = BorderStroke(1.dp, Color.Blue),
) {
Text("Apple")
}
Why not just create a box with that background and place a text while making the entire box clickable? The box will act as the entire button. I mean that's what a button essentially is, isn't it? A box containing some text? Oh, if you cannot figure out a way to get the drawable set as a background on your Box, just use something like an Image combined with the fillMaxSize() Modifier.
Something like
Box(Modifier.fillMaxWidth().height(...),
horizontalArrangement = Arrangement.CenterHorizontally){
Image(painter = painterResource(R.drawable.b_pattern),
contentDescription = "Lorem Ipsum")
Text(modifier = Modifier.align(Alignment.CenterVertically),
text = "Lorem Ipsum")
}
Just try it out and let me know please.
In this answer I got wrong ripple animation. Do you know how to create ripple with rounded corners using Jetpack Compose?
With default ripple I have this:
Code:
Card(shape = RoundedCornerShape(30.dp),
border = BorderStroke(width = 2.dp, color = buttonColor(LocalContext.current)),
backgroundColor = backColor(LocalContext.current),
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(radius = 30.dp)
) { show = !show }
) { ... } //Show is animation of other element.
//If I put radius of ripple 200 dp(it's a height of card) ripple works not normal.
Starting with M2 1.0.0-beta08 you can solve this issue using the onClick lambda parameter in the Card instead of the clickable modifier:
Card(
shape = RoundedCornerShape(30.dp),
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
onClick = { show = !show }
){
//card content
}
If you need the clickable or the combinedClickable modifier you have to use the variant without the onClick parameter and to apply also the clip modifier to the Card using the same Card shape:
val shape = RoundedCornerShape(30.dp)
Card(
shape = shape,
modifier = Modifier
//...height, width, padding
.clip(shape)
.combinedClickable(
onLongClick = { /** do something */ },
onClick = { /** do something */ }
)
){
//card content
}
With M3 Card you can do the same.
Until 1.0.0-beta07 applying a .clickable modifier to the Card the ripples aren't clipped by the bounds of the layout.
As workaround you can apply the .clickable modifier to the content of the Card (for example a Box):
Card(
shape = RoundedCornerShape(30.dp),
border = BorderStroke(width = 2.dp, color = Color.Blue),
backgroundColor = Color.White,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
Box(Modifier
.clickable(
onClick = { /* ...*/ }
)
){
Text("Text")
}
}
I've so far identified 2 options:
In addition to setting the shape, use .clip modifier to clip the Card using the same shape:
Card(
shape = RoundedCornerShape(30.dp),
modifier = Modifier
.clip(RoundedCornerShape(30.dp))
.clickable {
//do something
}
) {
Box {
Text("Text")
}
}
The downside of this approach is that the elevation shadow gets clips as well, so your Card loses it's shadow.
Set the .clickable on the Card content composable:
Card(
shape = RoundedCornerShape(30.dp)
) {
Box(
modifier = Modifier.clickable {
//do something
}
) {
Text("Text")
}
}
Hope this will grant you the easiest solution
Just add .clip(RoundedCornerShape(30.dp)) in the modifier parameter
Here is the full code :
Card(modifier = Modifier
.padding(30.dp)
.size(100.dp)
.clip(RoundedCornerShape(30.dp))
.clickable {
// After click //
}) { }
I checked the sources of Card/Surface composables and found out that you need to have background and clip modifiers with the same shape. So for example the following Box has rounded corner shape and click ripple is cut with the same bounds:
val shape = RoundedCornerShape(16.dp)
Box(
modifier = Modifier
.background(
color = Color.Yellow,
shape = shape
)
.clip(shape)
.clickable { onClick() },
) {
// your content here
}
using rememberRipple(bounded = false) will give a circular ripple effect around the clicked component. It can be used as ->
Modifier.clickable(
indication = rememberRipple(bounded = false),
interactionSource = remember {
MutableInteractionSource()
}
) { }
When you use long press or other gesture,you can use modifier.indication
val interactionSource = remember { MutableInteractionSource() }
Card(
modifier = Modifier
.padding(12.dp, 6.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.indication(interactionSource, LocalIndication.current)
.pointerInput(Unit) {
detectTapGestures(
onPress = { offset ->
val press = PressInteraction.Press(offset)
interactionSource.emit(press)
tryAwaitRelease()
interactionSource.emit(PressInteraction.Release(press))
},
onLongPress = {},
onTap = {}
)
}
)