I want to create an angular gradient like this in Android Jetpack Compose.
The gradient is made in Figma and below is the android code in Figma Inspect.
I could only find resources for the linear gradient with only one angle.
How can I create this Angular gradient in Jetpack compose?
What you need is Brush.sweepGradient with colorStops and setting center correctly. Gradient stops start from 3'o clock, right center, so you need to add 0.25 to each stop and move the ones that pass 1 to start, i moved 2 colors from bottom to top at 0.01, and 0.14
#Composable
private fun SweepGradientExample() {
val colorStops = listOf(
0.01f to Color(0x8C1339FF),
0.14f to Color(0x8CFF13A1),
0.31f to Color(0x8C1380FF),
0.54f to Color(0x8CD013FF),
0.81f to Color(0x8C7B13FF),
).toTypedArray()
val density = LocalDensity.current
val centerX: Float
val centerY: Float
with(density) {
centerX = 161.dp.toPx() / 2
centerY = 97.dp.toPx() / 2
}
val brush = Brush.sweepGradient(
colorStops = colorStops,
center = Offset(centerX, centerY)
)
Box(modifier = Modifier
.size(width = 161.dp, height = 97.dp)
.background(brush)
)
}
Result
Related
I send a stream of shapes to the drawing pipeline. At any point in time, shapes contains the number of items that can fill the screen's width or less.
Here's the drawing widget:
#Composable
fun ShapeWidget(shapes: List<Shape>, size: Float, spacing: Float, screenWidth: Float) {
val color = MaterialTheme.colorScheme.tertiary
Canvas(
modifier = Modifier.fillMaxWidth(),
onDraw = {
shapes.forEachIndexed { i, s ->
val x = screenWidth - ((size + spacing) * i)
when (s.type) {
Type.RECT -> {
drawRect(
color = color,
size = Size(size, size),
topLeft = Offset(x, this.center.y - size / 2),
)
}
Type.CIRCLE -> {
drawCircle(
color = color,
radius = size / 2,
center = Offset(x + spacing * 2, center.y)
)
}
}
}
}
)
}
I want to achieve an effect similar to the marquee text effect in Android Views. The shapes start drawing from the right (of the screen) and scroll to the left as more shapes are drawn.
The code above does that, but the animation is janky; it doesn't give the illusion that the shapes are moving. It looks like the shapes are jumping, which is not what I want. This is what it looks like currently.
Please, do you have an idea how to fix this?
I am currently playing with my old Instant Lab device and I am trying to recreate parts of the old app in jetpack compose.
A feature of the device is to detect 3 touch points on the screen in order to create the border of the image to display.
I was able to dectect the 3 touche points using jetpack compose and find the coordinate (x, y) of each touch points :
Now I would like to display my image between these touch points. I know that I need to use the Image Composable in order to display. But I do not know how to apply the right transformation in order to display this composable between these 3 points using rotation and absolute position (?).
Expected result:
Thank you in advance for your help.
Edit:
I tried using a custom shape I apply to a surface with the following composable :
#Composable
private fun Exposing(pointersCoordinates: PointersCoordinates)
{
val exposureShape = GenericShape { _, _ ->
moveTo(pointersCoordinates.xTopLeft(), pointersCoordinates.yTopLeft())
lineTo(pointersCoordinates.xTopRight(), pointersCoordinates.yTopRight())
lineTo(pointersCoordinates.xBottomRight(), pointersCoordinates.yBottomRight())
lineTo(pointersCoordinates.xBottomLeft(), pointersCoordinates.yBottomLeft())
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
) {
Surface(
modifier = Modifier.fillMaxSize(),
shape = exposureShape,
color = Color.Yellow,
border = BorderStroke(1.dp, Color.Red)
) {
Image(
modifier = Modifier.fillMaxSize(),
bitmap = viewModel?.bitmap?.asImageBitmap() ?: ImageBitmap(0, 0),
contentDescription = "photo"
)
}
}
}
It's working correctly :) But is it the best way to do it?
Since you are able to get a Rect from touch points you can use Canvas or Modifier.drawWithContent{}.
Clip image and draw
If you wish to clip your image based on rectangle you can check out this answer. Whit BlendModes you can clip not only to rectangle or shapes that easy to create but shapes you get from web or image
How to clip or cut a Composable?
Another approach for clipping is using clip() function of DrawScope, this approach only clips to a Rect.
Also you can use Modifier.clip() with custom shape to clip it as required as in this answer
Draw without clippin
If you don't want to clip your image draw whole image insider rect you can do it with dstOffset with dstSize or translate with dstSize
#Composable
private fun DrawImageWithTouchSample() {
val rect = Rect(topLeft = Offset(100f, 100f), bottomRight = Offset(1000f, 1000f))
val modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures {
// Tap here to get points
}
}
val image = ImageBitmap.imageResource(id = R.drawable.landscape5)
Canvas(modifier = modifier) {
// Clip image
clipRect(
left = rect.left,
top = rect.top,
right = rect.right,
bottom = rect.bottom
){
drawImage(image = image)
}
// Not clipping image
// drawImage(
// image = image,
// dstOffset = IntOffset(x = rect.left.toInt(), y = rect.top.toInt()),
// dstSize = IntSize(width = rect.width.toInt(), height = rect.height.toInt())
// )
//
translate(
left = rect.left,
top = rect.top + 1000
){
drawImage(
image = image,
dstSize = IntSize(width = rect.width.toInt(), height = rect.height.toInt())
)
}
}
}
Image on top is clipped with clipRect while second one is scaled to fit inside rect because of dstSize
You don't need to use Image. A Box clipped to a circle and with a grey-ish background would do. Of course, you'll need an Image to display the actual image you'll be dragging the points on. Here's a sample implementation:
val x by remember { mutableStateOf (Offset(...)) } // Initial Offset
val y by ...
val z by ...
// The image is occupying all of this composable, and these will be positioned ABOVE the image composable using order of positioning.
Layout(
content = {
Box(/*Copy From Later*/)
Box(...)
Box(...)
}
) { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) } // Default Constraining.
layout(constraints.maxWidth, constraints.maxHeight){
measurables[0].place(x.x, x.y)
measurables[1].place(y.x, y.y)
measurables[2].place(z.x, z.y)
}
}
Layout the box like so,
Box(
modifier = Modifier
.clip(CircleShape)
.size(5.dp) // Change per need
.pointerInput(Unit) {
detectDragGestures { change, _ ->
x = change.position // similarly y for second, and z for the third box
}
}
)
This should track/update/position the points wherever you drag them. All the points are individually determined by their own state-holders. You'll need to add this pointerInput logic to every Box, but it would likely be better if you just created a single function to be invoked based on an index, unique to each Box, but that's not something required to be covered here.
Can't resolve the problem with scrolling a Custom View in Compose.
I have a Canvas nested in a Column with fillMaxSize() modifier.
In Canvas I also use fillMaxSize() modifier (or size() with fixed value, doesn't matter)
I draw a vertical line that can be higher than screen size. And want, if this vertical line is higher, make canvas scrollable, but I can't. Any methods like verticalScroll(), scrollable() has no effects. I'm trying to make height of Canvas higher and use scrollable modifier, but it does no effect also (when I check "size" variable of DrawScope, it's always 1630 on Pixel 4).
Please help.
My code (Simplified and this method will be nested in Column):
#Composable
fun DrawTimeLine(list: List<Unit>){
Canvas(modifier = Modifier
.fillMaxSize()
.scrollable(rememberScrollState(), Orientation.Vertical)
.padding(
top = Dimens.DEFAULT_MARGIN,
bottom = Dimens.BOTTOM_BAR_SIZE,
start = Dimens.DEFAULT_MARGIN
)){
var offsetY = 0f
val offsetX = 0f
val increaseValue = 150f
fun drawTimePoint() {
drawCircle(
color = Color.White,
radius = 15.dp.value,
center = Offset(offsetX, offsetY)
)
}
fun drawTimeLine() {
drawLine(
color = Color.White,
start = Offset(offsetX, offsetY),
end = Offset(offsetX, offsetY + increaseValue),
strokeWidth = 5.dp.value
)
offsetY += increaseValue
}
list.forEach {
drawTimePoint()
drawTimeLine()
}
}
}
I'm looking to draw a line on a canvas with a pattern instead of a color. Here's the code I have right now:
drawLine(
color = progressColor,
start = Offset(if (whiteGap) progressCapWidth else startOffsetBg, yOffset),
end = Offset(endOffsetProgress, yOffset),
strokeWidth = progressHeight.toPx(),
cap = if (roundedCorners) StrokeCap.Round else StrokeCap.Butt,
)
It's part of a custom linear progress bar. Per the design I was given, they want the progress to have this pattern:
This is an example of full progress with this diagonally patterned progress. Is it possible to use a drawable and repeat it instead of a color? Is there a way to just apply/create diagonal white gaps directly in when drawing the line?
We're implementing this whole feature using Jetpack Compose, so I can't do something with traditional XML involved.
Here's how you can draw it with Canvas:
Canvas(
Modifier
.padding(top = 100.dp)
.border(1.dp,Color.Black)
.padding(10.dp)
.height(30.dp)
.fillMaxWidth()
.clip(CircleShape)
) {
val step = 10.dp
val angleDegrees = 45f
val stepPx = step.toPx()
val stepsCount = (size.width / stepPx).roundToInt()
val actualStep = size.width / stepsCount
val dotSize = Size(width = actualStep / 2, height = size.height * 2)
for (i in -1..stepsCount) {
val rect = Rect(
offset = Offset(x = i * actualStep, y = (size.height - dotSize.height) / 2),
size = dotSize,
)
rotate(angleDegrees, pivot = rect.center) {
drawRect(
Color.Blue,
topLeft = rect.topLeft,
size = rect.size,
)
}
}
}
Result:
I'm looking to draw an arc type shape in Jetpack Compose but I'm really struggling to figure out the best way to do it. When I use drawArc, I can't seem to start the arc from the bottom left corner, it just starts from left middle. I've also tried drawRoundRect but I can't find a way to just round the top left and top right corners.
I'll attach a picture, I don't need the white handle within the shape. Desired result:
drawRoundRect attempt:
drawArc attempt:
You could make it by using a a canvas with your device width but try to increase arc limits beyond device width. With this code
Canvas(modifier = Modifier.fillMaxSize()) {
val canvasWidth = size.width
val canvasHeight = size.height
val formWidth = (canvasWidth * 2)
val xPos = canvasWidth / 2
drawArc(
Color.Black,
0f,
-180f,
useCenter = true,
size = Size(formWidth, 600f),
topLeft = Offset(x = -xPos, y = canvasHeight - 300)
)
}
Arc at bottom looks like this:
more info
You can draw it like this
Placed it to center for demonstration and border is to show that it doesn't overflow Canvas, if you don't pay attention, Canvas draws out of its borders unless use Modifier.clipbounds() is used on Canvas Modifier.
Trick for drawing half arc is to set two times of Canvas height, since arc is drawn inside a rectangle in principle. Changind height of Modifier of canvas changes height of arc.
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Canvas(
modifier = Modifier
.border(2.dp, Color.Red)
.fillMaxWidth()
.height(80.dp),
) {
val canvasWidth = size.width
val canvasHeight = size.height
drawArc(
Color.Blue,
startAngle = 180f,
sweepAngle = 180f,
useCenter = true,
size = Size(canvasWidth, 2 * canvasHeight)
)
val handleWidth = 200f
val handleHeight = 30f
drawRoundRect(
Color.White,
size = Size(handleWidth, handleHeight),
cornerRadius = CornerRadius(5.dp.toPx(), 5.dp.toPx()),
topLeft = Offset((canvasWidth - handleWidth) / 2, (canvasHeight - handleHeight)/2)
)
}
}
Result
You can check this tutorial about Jetpack Compose which covers Canvas operations in detail.