0

I created an interactive line, but that might be irrelevant. Even if there was no interaction, this renders unexpected results:-

@Composable
fun PowerClock() {
    var dynamicAngle by remember { mutableStateOf(90f.toRadians()) }
    val angle by animateFloatAsState(targetValue = dynamicAngle)
    Canvas(
        modifier = Modifier
            .fillMaxHeight()
            .fillMaxWidth()
            .pointerInput(Unit) { //Irrelevent, the results go wrong even without invoking this at all
                coroutineScope {

                    while (true) {
//                        val touchDownPointerId = awaitPointerEventScope { awaitFirstDown().id }
                        detectDragGestures { _, dragAmount ->
                            dynamicAngle += atan(dragAmount.x / dragAmount.y)
                        }
                    }
                }
            }
    ) {
        val length = 500
        val path = Path().apply {
            moveTo(size.width / 2, size.height / 2)
            relativeLineTo(length * cos(angle), length * sin(angle))
        }

        drawPath(path, Color.Blue, style = Stroke(10f))
    }
}

Here's a bit of a preview,

An intriguing behaviour portrayed by Cavnas is that looking at my implementation, the angle should change based on both the x and y change, right? But in actuality, y is out ignored. I have tested this.

Is this a bug in Cavnas or am I implementing something wrong?

Richard Onslow Roper
  • 5,477
  • 2
  • 11
  • 42
  • I'll just remove it. I never meant to disrespect anyone, but maybe the fact that we cannot judge the tone online, might add some unintended subtlety to the context, anyway I feel quite guilty if anyone gets even little bit disturbed or disrespected because of me, so I apologize, sincerely. – Richard Onslow Roper Oct 28 '21 at 10:42
  • I'm just curios, what makes you write such things instead of just "Am I doing something wrong or is it a Compose bug?" – Phil Dukhov Oct 28 '21 at 10:45

1 Answers1

1

I've followed this answer and adopted code to Compose:

var touchPosition by remember { mutableStateOf(Offset.Zero) }
Canvas(
    modifier = Modifier
        .fillMaxHeight()
        .fillMaxWidth()
        .pointerInput(Unit) { //Irrelevent, the results go wrong even without invoking this at all
            while (true) {
                detectDragGestures { change, _ ->
                    touchPosition = change.position
                }
            }
        }
) {
    val rect = Rect(Offset.Zero, size)
    val length = 500
    val path = Path().apply {
        moveTo(rect.center.x, rect.center.y)
        val angle = (touchPosition - rect.center).let { atan2(it.y, it.x) }
        relativeLineTo(length * cos(angle), length * sin(angle))
    }

    drawPath(path, Color.Blue, style = Stroke(10f))
}

Result:

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • This is almost exactly what I required. The only thing being this is heavily dependent on the touch position. For example, if the user played around with it, then touched somewhere else, it would snap to that position. – Richard Onslow Roper Oct 28 '21 at 11:23
  • I wish to paste this Composable on a very high-tech background composable. So I want that no matter where the user touches the screen, it should only move when the user creates a clock or counter clock circular motion (they are not required to press directly on the line, but the line will move nonetheless). I'll mark this as the answer for now anyway, but it'd be very nice if that could be implemented. I am afraid the entire code will need to be written from scratch for that since this is completely based on touch positions... – Richard Onslow Roper Oct 28 '21 at 11:26
  • The angle calculation can only be done depending on the center of the view. If you add `Modifier.onSizeChanged`, you can use the size value to calculate the angle during touch processing. – Phil Dukhov Oct 28 '21 at 11:30
  • Good mroning/evening (Time-Zone), sir. The proposed solution works very effectively and efficiently. I would like to ask you if there is a simple method by which I can make the indicator start at a particular angle? That is, upon the first Composition, the line here starts at the angle calculated from `Offset.Zero`. If I want to make it start at -54f, how could something like that be achieved? Regards, MARSK – Richard Onslow Roper Nov 07 '21 at 18:32
  • what do you mean by `-54f`? Degrees? – Phil Dukhov Nov 07 '21 at 18:45
  • Yes, of course, sir. The range of values returned by `atan2` is from -PI/2 to PI/2 radians. So I convert them to degrees for convenience. – Richard Onslow Roper Nov 07 '21 at 18:46
  • Plus, -54 radians would be more than 3000 degrees so we don't really require that large radian values when we can work with principal values themselves, right? – Richard Onslow Roper Nov 07 '21 at 18:48
  • @MARSK You need to [convert degrees to radians](https://stackoverflow.com/a/20594694/3585796)? – Phil Dukhov Nov 07 '21 at 18:49
  • ..., I don't get it. I want it to start at a particular angle, instead of the one calculated from `Offset,Zero` – Richard Onslow Roper Nov 07 '21 at 18:50
  • Did I miscommunicate anything? Just let me know if there's any confusion. – Richard Onslow Roper Nov 07 '21 at 18:51
  • Let me re-state. As per the proposed solution, the default value of `touchPosition` is `Offset.Zero`. So, when the app is first started, by default, the interactive line is pointing towards the top-left corner of the screen, i.e., coordinates (0,0). I wish to change this behaviour to a state where I can specify what should be the starting (default) angle on the app startup. – Richard Onslow Roper Nov 07 '21 at 18:53
  • I just guess that you've followed my suggestion about calculating the angle inside `pointerInput` and using value from `onSizeChanged`. If you didn't you still need `onSizeChanged` to calculate the initial position. – Phil Dukhov Nov 07 '21 at 18:55
  • I don't get it. Why do I need `onSizeChanged`? If I want to get the size of the Composable, even `pointerInput` exposes a `size` value, which I guess is exactly the same as the one returned by `onSizeChanged`. Also, why did I need it to calculate initial angle if I already have access to the Composable's center inside the Canvas' `drawScope`, just the way you portrayed in your solution? – Richard Onslow Roper Nov 07 '21 at 19:13
  • Can you explain in complete layman language? Like you were explaining to a newborn? I kinda have a hard time grasping these concepts, nowadays. – Richard Onslow Roper Nov 07 '21 at 19:31
  • @MARSK didn't know `pointerInput` has size =) Then I don't see your problem, modify my solution to calculate angle inside `pointerInput` and set initial angle. `Math.toRadians(-54)` should work, if `-54` is really the degree you wanna set to be initial – Phil Dukhov Nov 07 '21 at 20:03