How to move a rectangle around a canvas in Jetpack Compose? - android

I have a canvas with a rectangle that I would like to be able to move around. With a Composable, I know how to use the dragging modifier as described here: https://developer.android.com/jetpack/compose/gestures#dragging
But my canvas rectangle has no modifier:
Canvas(modifier = Modifier.fillMaxSize()) {
drawRect(Color.Blue, topLeft = Offset(0f, 0f), size = Size(this.size.width, 55f))
So how can I drag it? Is there a way with Compose or is it better to just use the native way with a native canvas?

With 1.0.0-beta04 you can use the pointerInput modifier in the Canvas to control the dragging gesture through the detectDragGestures function and save the Offset and apply it in the topLeft parameter in the drawRect.
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
Canvas(modifier = Modifier.fillMaxSize()
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offsetX += dragAmount.x
offsetY += dragAmount.y
}
}
){
val canvasQuadrantSize = size / 2F
drawRect(
topLeft = Offset(offsetX,offsetY),
color = Color.Green,
size = canvasQuadrantSize
)
}

Related

Jetpack Compose icon shadow/elevation

Is there a way to have an Icon (with ImageVector) component with a shadow/elevation in Jetpack Compose?
I want to make an IconButton with an elevated Icon but there seems to be no solution available for this problem. Things like Modifier.shadow() will only draw a shadow box around my icon and the Icon component itself has no elevation parameter.
This ticket seems like a duplicate of How to add a shadow / border / elevation to an icon in Jetpack Compose at first glance, but that ticket is not referring to the Icon component in combination with an ImageVector. Also, the proposed solution does not work and it wasn't updated in 6 months.
To further clarify, I want my Icon to look like this:
What you require is a library that converts imageVectors or xml files into Path. As i know of there is no built-in library for this. There are probably few out there that converts into Path or Shape.
When you have a shape or path what you need to do is draw with this shape as Modifier or into Canvas
fun Modifier.vectorShadow(
path: Path,
x: Dp,
y: Dp,
radius: Dp
) = composed(
inspectorInfo = {
name = "vectorShadow"
value = path
value = x
value = y
value = radius
},
factory = {
val paint = remember {
Paint()
}
val frameworkPaint = remember {
paint.asFrameworkPaint()
}
val color = Color.DarkGray
val dx: Float
val dy: Float
val radiusInPx: Float
with(LocalDensity.current) {
dx = x.toPx()
dy = y.toPx()
radiusInPx = radius.toPx()
}
drawBehind {
this.drawIntoCanvas {
val transparent = color
.copy(alpha = 0f)
.toArgb()
frameworkPaint.color = transparent
frameworkPaint.setShadowLayer(
radiusInPx,
dx,
dy,
color
.copy(alpha = .7f)
.toArgb()
)
it.drawPath(path, paint)
}
}
}
)
Usage
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
) {
val center = with(LocalDensity.current) {
150.dp.toPx()
}
val path1 = createPolygonPath(center, center, 6, center)
val path2 = createPolygonPath(center, center, 5, center)
Canvas(
modifier = Modifier
.size(300.dp)
.vectorShadow(path1, 0.dp, 0.dp, 6.dp)
.border(3.dp, Color.Green)
) {
drawPath(path1, Color.White)
}
Spacer(modifier = Modifier.height(10.dp))
Canvas(
modifier = Modifier
.size(300.dp)
.vectorShadow(path2, 3.dp, 3.dp, 10.dp)
.border(3.dp, Color.Green)
) {
drawPath(path2, Color.White)
}
}
Result
createPolygonPath is a sample function to create Path. If you manage to convert your vector to Path rest is simple.
fun createPolygonPath(cx: Float, cy: Float, sides: Int, radius: Float): Path {
val angle = 2.0 * Math.PI / sides
return Path().apply {
moveTo(
cx + (radius * cos(0.0)).toFloat(),
cy + (radius * sin(0.0)).toFloat()
)
for (i in 1 until sides) {
lineTo(
cx + (radius * cos(angle * i)).toFloat(),
cy + (radius * sin(angle * i)).toFloat()
)
}
close()
}
}
It's not exactly what you want but for elevating an icon you can simply do this:
Icon(
Icons.Outlined.Refresh, contentDescription = "back",
modifier = Modifier
.size(300.dp)
.offset(10.dp, 10.dp), tint = Color(0, 0, 0, 40)
)
Icon(
Icons.Outlined.Refresh, contentDescription = "front",
modifier = Modifier.size(300.dp), tint = Color(0xFFb6d7a8)
)
The problem is that it is lacking the blurring effect.

Android Compose How to scale and rotate an image by dragging border?

I'm trying to make a photo editor that can be scale, rotate and drag to any place.
But I want to control the scale and rotate behavior via the corner rotate icon
not by the image itself.
I know it must to be using pointerInput from the modifier, but not sure how to implement it.
Can anybody help with that and give some example?
From example here: Android Touch System Gesture-Handling Modifiers in Jetpack Compose
#Composable
fun TransformableDemo() {
var scale by remember { mutableStateOf(1f) }
var rotation by remember { mutableStateOf(0f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val state = rememberTransformableState {
zoomChange, offsetChange, rotationChange ->
scale *= zoomChange
rotation += rotationChange
offset += offsetChange
}
Box(
modifier = Modifier
.graphicsLayer(
scaleX = scale,
scaleY = scale,
rotationZ = rotation,
translationX = offset.x,
translationY = offset.y
)
.transformable(state = state)
.background(Color.Blue)
.fillMaxSize()
)
}

Draw lines at an angle in jetpack compose without a canvas

so I wanted to draw a line at an angle from point A to point B in jetpack compose.
Is there a way to do it without a canvas, since a canvas won't really work with what I want to do
If you want to draw behind other composables you can use the drawBehind modifier.
If you want to draw both behind and in front you can use the drawWithContentmodifier.
If you also want to cache the result as much as possible you can use the drawWithCache modifier.
Example of a line behind the content and a line in front of the content using the drawWithCache modifier
#Composable
fun DrawWithCacheExample() {
val width = 400.dp
val height = 200.dp
var offsetX by remember { mutableStateOf(0f) }
Box(
modifier = Modifier
.size(width, height)
.border(1f.dp, Color.Black, RectangleShape)
.drawWithCache {
onDrawWithContent {
// draw behind the content
drawLine(Color.Black, Offset.Zero, Offset(width.toPx(), height.toPx()), 1f)
// draw the content
drawContent()
// draw in front of the content
drawLine(Color.Black, Offset(0f, height.toPx()), Offset(width.toPx(), 0f), 1f)
}
}
) {
Box(modifier = Modifier
.size(width / 2, height / 2)
.offset { IntOffset(offsetX.roundToInt(), (height / 4).roundToPx()) }
.background(Color.Yellow)
.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
offsetX += delta
}
)
)
}
}

Scrollable Custom View Compose (Canvas)

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()
}
}
}

Jetpack Compose Path will draw outside canvas bounds

I have a signature box, at the moment when you sign, it is possible to draw outside the canvas. I need the path to stay within the bounds of the canvas. I could manually do this when capturing the path but I figure there is probably an automatic way.
Canvas(modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.border(1.dp, MaterialTheme.colors.primaryVariant, shape = RoundedCornerShape(4.dp))
.pointerInput(Unit) {
detectDragGestures(onDragStart = {
touchMove(path, it.x, it.y, -1f, -1f, true)
}) { change, _ ->
change.consumeAllChanges()
touchMove(
path,
change.position.x,
change.position.y,
change.previousPosition.x,
change.previousPosition.y,
false
)
}
}) {
canvasWidth = size.width
canvasHeight = size.height
drawPath(path, color = Color.Blue, style = Stroke(width = 4f))
}
Dearie me. Figured it out minuets after writing.
Modifier.clipToBounds()
Strange this isn't mentioned in any of the examples. Its the opposite behaviour of normal android clipping I think.

Categories

Resources