0

I'm want to make recomposition of floorMap when floor(floorState) is change. Variant to sum all paths to one and give this path a state is bad because in the next step I want make every roomPath and object a clickable.

Here is my Jetpack compose code for transform input data and draw map:

fun initMap(
    pointsList: List<Point>,
    pathsList: List<PathModel>,
    width: Float,
    height: Float
): PathsAndObjectsHolderForDrawing {
    val pathsAndObjects = PathsAndObjectsHolderForDrawing()
    clearPathsAndObjectsHolder(pathsAndObjects)

    val requiredPoints = pointsList.filter { isPointObject(it) || pathsList.any { path -> path.pathId == it.pathId } }.toSet()

    for (pathType in enumValues<PathType>()) {
        val pathsOfType = pathsList.filter { it.pathType == pathType }
        val paths = pathsOfType.map { pathModel ->
            val pointsForPath = requiredPoints.filter { it.pathId == pathModel.pathId }.sortedBy { it.inPathId }
            makePathFromPoints(pointsForPath, width, height)
        }

        when (pathType) {
            PathType.ELEVATOR -> pathsAndObjects.elevatorsPath.paths.addAll(paths)
            PathType.STAIRS -> pathsAndObjects.stairsPath.paths.addAll(paths)
            PathType.ROOM -> pathsAndObjects.roomsPath.paths.addAll(paths)
            PathType.OUTER_WALL -> pathsAndObjects.outerWallPath.paths.addAll(paths)
            PathType.INTERNAL_WALL -> pathsAndObjects.internalWallsPath.paths.addAll(paths)
            PathType.OTHER -> pathsAndObjects.othersPath.paths.addAll(paths)
        }
    }

    return pathsAndObjects
}

fun clearPathsAndObjectsHolder(paths: PathsAndObjectsHolderForDrawing) {
    paths.elevatorsPath.paths.clear()
    paths.stairsPath.paths.clear()
    paths.roomsPath.paths.clear()
    paths.outerWallPath.paths.clear()
    paths.internalWallsPath.paths.clear()
    paths.othersPath.paths.clear()
    paths.objects.objects.clear()
}

fun makePathFromPoints(points: List<Point>, width: Float, height: Float): Path {
    val path = Path()
    for ((index, point) in points.withIndex()){
        if (index == 0) path.moveTo(point.x * width, point.y * height)
        else {
            when (point.pointParameter) {
                PointParameter.LINE -> path.lineTo(point.x * width, point.y * height)
                PointParameter.BEZIER3 -> cubicBezierForThreePoints(listOf(points[index - 2], points[index - 1], points[index]), path, width, height)
                else -> Unit
            }
        }
    }
    return path
}

fun cubicBezierForThreePoints(points: List<Point>, path: Path, width: Float, height: Float){
    val secondPoint = Pair(points[0].x * width, ((points[1].y - points[0].y) * 4 / 3 + points[0].y) * height)
    val thirdPoint = Pair(points[2].x * width, ((points[1].y - points[2].y) * 4 / 3 + points[2].y) * height)
    val fourthPoint = Pair(points[2].x * width, points[2].y * height)
    path.cubicTo(secondPoint.first, secondPoint.second, thirdPoint.first, thirdPoint.second, fourthPoint.first, fourthPoint.second)
}

fun isPointObject(point: Point): Boolean = point.pointType == PointType.OBJECT

Here is a let way. It is Work, but recomposition may not occur or may occur with a delay.

@Composable
fun Map3(modifier: Modifier = Modifier,
        floorState: StateFlow<FloorState>
) {
    var pathsAndObjects: PathsAndObjectsHolderForDrawing = remember { PathsAndObjectsHolderForDrawing() }

//    val coroutineScope = CoroutineScope(Dispatchers.Default)


    val paint = Paint()
    paint.color = Color.Red
    paint.strokeWidth = 10f


    Canvas(
        modifier = modifier
            .aspectRatio(3 / 2f)
            .fillMaxSize()
    ) {
        val height = size.height
        val width = size.width

        Log.d("height", height.toString())

        floorState.let {
            pathsAndObjects = initMap(it.value.points, it.value.paths, width, height)
            Log.d("let", pathsAndObjects.objects.objects.size.toString())
            drawPoints(pathsAndObjects.objects.objects, PointMode.Points, Color.Red, 10f)
        }

    }
}

Here is way by coroutineScope inside composable with StateFlow.collectAsState. It is doesn't Work.

@Composable
fun Map(modifier: Modifier = Modifier,
        floorState: StateFlow<FloorState>
) {
    var pathsAndObjects = remember { PathsAndObjectsHolderForDrawing() }

    val coroutineScope = rememberCoroutineScope()

//    val floorState by floorState.collectAsState()

    val paint = Paint()
    paint.color = Color.Red
    paint.strokeWidth = 10f
    val block: MutableState<Canvas.() -> Unit> =
        remember { mutableStateOf( {drawPoints(PointMode.Points, listOf(Offset(450f, 450f)), paint) } ) }

    Canvas(
        modifier = modifier
            .aspectRatio(3 / 2f)
            .fillMaxSize()
    ) {
        val height = size.height
        val width = size.width
        val paint = Paint().apply {
                    style = PaintingStyle.Stroke
                    strokeWidth = 5f
                    color = Color.Black
                }
        drawIntoCanvas{
            coroutineScope.launch {
                floorState.collect {floorState ->
                    block.value = {
                        initMap(floorState.points, floorState.paths, width, height).also {pathsAndObjects ->
                        pathsAndObjects.roomsPath.paths.forEach{ path ->
                            drawPath(path, Color.Red)
                        }
                            drawPoints(pathsAndObjects.objects.objects,
                                PointMode.Points,
                                pathsAndObjects.objects.color
                            )

                        }
                }
                }
            }
            val a = block.value
            it.a()
        }
        }
    }

Also tried it in drawIntoCanvas with flow. It is doesn't Work.

        coroutineScope.launch {
            floorState.collect {State ->
                initMap(floorState.value.points, floorState.value.paths, width, height)
            }
        }

            pathsAndObjects = initMap(floorState.value.points, floorState.value.paths, width, height)
            if (pathsAndObjects.elevatorsPath.paths.isNotEmpty()) {
                for (item in pathsAndObjects.elevatorsPath.paths) {
                    drawPath(item, Color.Green, style = Stroke(2.dp.toPx()))
                }
            }

            if (pathsAndObjects.stairsPath.paths.isNotEmpty()) {
                for (item in pathsAndObjects.stairsPath.paths) {
                    drawPath(item, Color.Green, style = Stroke(2.dp.toPx()))
                }
            }


            if (pathsAndObjects.roomsPath.paths.isNotEmpty()) {
                for (item in pathsAndObjects.roomsPath.paths) {
                    drawPath(item, Color.Green, style = Stroke(2.dp.toPx()))
                }
            }

            if (pathsAndObjects.outerWallPath.paths.isNotEmpty()) {
                for (item in pathsAndObjects.outerWallPath.paths) {
                    drawPath(item, Color.Green, style = Stroke(2.dp.toPx()))
                }
            }

            if (pathsAndObjects.internalWallsPath.paths.isNotEmpty()) {
                for (item in pathsAndObjects.internalWallsPath.paths) {
                    drawPath(item, Color.Green, style = Stroke(2.dp.toPx()))
                }
            }

            if (pathsAndObjects.othersPath.paths.isNotEmpty()) {
                for (item in pathsAndObjects.othersPath.paths) {
                    drawPath(item, Color.Green, style = Stroke(2.dp.toPx()))
                }
            }

            if (pathsAndObjects.objects.objects.isNotEmpty()) {
                Log.d("pointShouldBeDraw", "whyNot?")
                drawPoints(
                    pathsAndObjects.objects.objects,
                    pointMode = PointMode.Points,
                    color = Color.Red,
                    strokeWidth = 10f
                )
            }

Here is by launched effect. It is doesn't Work.

@Composable
fun Map(modifier: Modifier = Modifier,
        floorState: StateFlow<FloorState>,
        configuration: Configuration,
        density: Density
) {
    val floorState = floorState.collectAsState()
    var pathsAndObjects: PathsAndObjectsHolderForDrawing = remember { initMap(floorState.value.points, floorState.value.paths,
        configuration.screenWidthDp.toFloat() * density.density,
        configuration.screenWidthDp.toFloat() * density.density
    ) }
    
    
    LaunchedEffect(floorState.value) {
        val floor = floorState.value
        pathsAndObjects = withContext(Dispatchers.Default) {
            initMap(floor.points, floor.paths,
                configuration.screenWidthDp.toFloat() * density.density,
                configuration.screenWidthDp.toFloat() * density.density
            )
        }
    }

    val paint = Paint()
    paint.color = Color.Red
    paint.strokeWidth = 10f
    val block: MutableState<Canvas.() -> Unit> =
        remember { mutableStateOf( {drawPoints(PointMode.Points, listOf(Offset(450f, 450f)), paint) } ) }

    Canvas(
        modifier = modifier
            .aspectRatio(3 / 2f)
            .fillMaxSize()
    ) {
        val height = size.height
        val width = size.width

        floorState.value.let {
            drawPoints(pathsAndObjects.objects.objects, PointMode.Points, Color.Red, 10f)
        }
    }
}

Please suggest a way I can recompose Canvas, or some other way to make my logic work.

kong_zi
  • 1
  • 2

0 Answers0