I'm currently experimenting with the new Jetpack Compose UI toolkit and for now I really love it. With the following code I can draw any shape on the screen, make it clickable and move it around:
@Composable
fun Shape(
color: Color, @FloatRange(from = 0.0, to = 1.0) transparency: Float, strokeWidth: Dp,
onMoveShape: (dragAmount: Offset) -> Unit,
builder: Path.(size: Size, layoutDirection: LayoutDirection) -> Unit
) {
val fillColor = color.copy(transparency)
val drawingShape = GenericShape(builder)
val position = Modifier.fillMaxSize()
val border = Modifier.border(strokeWidth, color, drawingShape)
val background = Modifier.background(fillColor, drawingShape)
val clip = Modifier.clip(drawingShape)
val clickListener = Modifier.clickable { /* ... */ }
val dragListener = Modifier.draggable { change, dragAmount ->
change.consumeAllChanges()
onMoveShape(dragAmount)
}
Box(position border background clip clickListener dragListener)
}
The onMoveShape callback moves the points used in the builder around which triggers a recomposition which works great. My problem here is that the area from where I can start the drag and click events does not update when I move the shape. I think it's best to explain that in this screenshot:
I removed the code for the red dots above since it is not important for the question. I guess since the click and drag listeners do not depend on the drawingShape they don't get recomposed and that's why the area is not updated. Any ideas how I can archive this?
The
for Modifier and the draggable
Modifier are extension functions btw.
CodePudding user response:
To create a clickable and draggable composable,
@Composable
fun DraggableShape() {
Box(modifier = Modifier.fillMaxSize()) {
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
.background(Color.Blue)
.size(50.dp)
.clickable {
Log.e("TAG","Clicked")
}
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offsetX = dragAmount.x
offsetY = dragAmount.y
}
}
)
}
}
Source: Android Docs
The click events are working even after dragging.
Note:
Use pointerInput
for dragging in all directions and draggable
for dragging in a single direction.
If this is still not working for you, kindly provide a minimal code with the issue reproducible.
(Current code has extensions, shapes, etc and I couldn't find the exact issue in that)
CodePudding user response:
Do not place your dragListener inside your composable. If your composable recomposes, it will get destroyed and recreated causing it to lose state. Instead, pass in the the drag listener as a lamda parameter. Use state hoisting to do this. Your hoisted composable should not recompose. Placing normal listeners like click listeners inside a composable is fine because most of the time, recomposing them isn't going to effect their logic - such as a button click event. But if your listener needs to maintain state data during events like dragging, you need to maintain that state. Try something like this:
@Composable
fun ShapeHandler() {
val dragListener = Modifier.draggable { change, dragAmount ->
change.consumeAllChanges()
onMoveShape(dragAmount)
}
Shape(
color = xxx,
transparency = xxx,
strokeWidth = xxx
onMoveShape = xxx,
builder = xxx,
dragListener = dragListener
)
}
@Composable
fun Shape(
color: Color,
@FloatRange(from = 0.0, to = 1.0) transparency: Float,
strokeWidth: Dp,
onMoveShape: (dragAmount: Offset) -> Unit,
builder: Path.(size: Size, layoutDirection: LayoutDirection) -> Unit,
dragListener: DragListener
) {
val fillColor = color.copy(transparency)
val drawingShape = GenericShape(builder)
val position = Modifier.fillMaxSize()
val border = Modifier.border(strokeWidth, color, drawingShape)
val background = Modifier.background(fillColor, drawingShape)
val clip = Modifier.clip(drawingShape)
val clickListener = Modifier.clickable { /* ... */ }
Box(position border background clip clickListener dragListener)
}