I'm working on an app where i want to use a custom composable as the drag decoration when dragging an element.
By default, jetpack compose uses the dragged element itself as the drag decoration, but i want to use a different composable instead.
If I do nothing, compose will automatically use the source element as the drag decoration, like in the example below:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
App(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
)
}
}
}
}
@Composable
fun DragSource(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.background(color = Color.Yellow)
.size(100.dp),
contentAlignment = Alignment.Center
) {
Text(text = "Drag Me")
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun App(modifier: Modifier = Modifier) {
Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
DragSource(
modifier = Modifier.dragAndDropSource {
detectTapGestures(
onLongPress = {
startTransfer(
DragAndDropTransferData(
ClipData.newPlainText("image uri", "image uri")
)
)
}
)
}
)
}
}
Now, instead of using the source element as the drag decoration, I want to use a custom composable like this one:
@Composable
fun DraggedShape(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.size(100.dp)
.background(color = Color.Red, shape = RoundedCornerShape(16.dp))
)
}
How can I achieve this?
I know the dragAndDropSource
provides a drawDragDecoration
parameter where you can draw custom decorations using the canvas api, like in the example below, i draw a blue rect:
DragSource(
modifier = Modifier.dragAndDropSource(
drawDragDecoration = { // draw scope
drawRect(color = Color.Blue)
}
) {
//...
}
)
However, this only allows drawing with canvas, and I need to use a composable as the drag decoration, not just a a shape that i can drag with canvas api.
One idea i have is to somehow convert the composable into a bitmap and then draw it in the drawDragDecoration
lambda using the canvas api (i do not know even if this is possible in compose). Is there a better way to do this? Any workarounds or suggestions would be greatly appreciated!
Thanks in advance.
As you suggested you can do it by turning that composable to ImageBitmap using , and easiest way is using rememberGraphicsLayer to get GraphicsLayer and
graphicsLayer.record {
this@drawWithContent.drawContent()
}
to record a Composable drawing and turn it into a ImageBitmap with
onPress = {
if (imageBitmap == null){
coroutineScope.launch {
imageBitmap = graphicsLayer.toImageBitmap()
}
}
}
Result
Full code
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun App(modifier: Modifier = Modifier) {
Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
val graphicsLayer: GraphicsLayer = rememberGraphicsLayer()
val coroutineScope = rememberCoroutineScope()
var imageBitmap by remember {
mutableStateOf<ImageBitmap?>(null)
}
Box(modifier = Modifier
.drawWithContent {
graphicsLayer.record {
this@drawWithContent.drawContent()
}
}
.size(100.dp)
.background(Color.Green, CutCornerShape(16.dp)),
contentAlignment = Alignment.Center
) {
Text("Custom shape")
}
DragSource(
modifier = Modifier.dragAndDropSource(
drawDragDecoration = {
imageBitmap?.let {
drawImage(it)
}
}
) {
detectTapGestures(
onPress = {
if (imageBitmap == null) {
coroutineScope.launch {
imageBitmap = graphicsLayer.toImageBitmap()
}
}
},
onLongPress = {
startTransfer(
DragAndDropTransferData(
ClipData.newPlainText("image uri", "image uri")
)
)
}
)
}
)
}
}