Handling Touch Events

One way to listen for touch events is to set a touch event listener using the following View function:

    fun setOnTouchListener(l: View.OnTouchListener)

This function works the same way as setOnClickListener(View.OnClickListener). You provide an implementation of View.OnTouchListener, and your listener will be called every time a touch event happens.

However, because you are subclassing View, you can take a shortcut and override this View function instead:

    override fun onTouchEvent(event: MotionEvent): Boolean

This function receives an instance of MotionEvent, a class that describes the touch event, including its location and its action. The action describes the stage of the event:

Action constants Description
ACTION_DOWN user touches finger on the screen
ACTION_MOVE user moves finger on the screen
ACTION_UP user lifts finger off the screen
ACTION_CANCEL a parent view has intercepted the touch event

In your implementation of onTouchEvent(MotionEvent), you can check the value of the action by calling the MotionEvent function:

    final fun getAction(): Int

Let’s get to it. In BoxDrawingView.kt, add a log tag and then an implementation of onTouchEvent(MotionEvent) that logs a message for each of the four actions.

Listing 30.3  Implementing BoxDrawingView (BoxDrawingView.kt)

private const val TAG = "BoxDrawingView"

class BoxDrawingView(context: Context, attrs: AttributeSet? = null) :
        View(context, attrs) {

    override fun onTouchEvent(event: MotionEvent): Boolean {
        val current = PointF(event.x, event.y)
        var action = ""
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                action = "ACTION_DOWN"
            }
            MotionEvent.ACTION_MOVE -> {
                action = "ACTION_MOVE"
            }
            MotionEvent.ACTION_UP -> {
                action = "ACTION_UP"
            }
            MotionEvent.ACTION_CANCEL -> {
                action = "ACTION_CANCEL"
            }
        }

        Log.i(TAG, "$action at x=${current.x}, y=${current.y}")

        return true
    }
}

Notice that you package your X and Y coordinates in a PointF object. You want to pass these two values together as you go through the rest of the chapter. PointF is a container class provided by Android that does this for you.

Run DragAndDraw and pull up Logcat. Touch the screen and drag your finger. (On the emulator, click and drag.) You should see a report of the X and Y coordinates of every touch action that BoxDrawingView receives.

Tracking across motion events

BoxDrawingView is intended to draw boxes on the screen, not just log coordinates. There are a few problems to solve to get there.

First, to define a box, you need two points: the start point (where the finger was initially placed) and the end point (where the finger currently is).

To define a box, then, requires keeping track of data from more than one MotionEvent. You will store this data in a Box object.

Create a class named Box to represent the data that defines a single box.

Listing 30.4  Adding Box (Box.kt)

class Box(val start: PointF) {

    var end: PointF = start

    val left: Float
        get() = Math.min(start.x, end.x)

    val right: Float
        get() = Math.max(start.x, end.x)

    val top: Float
        get() = Math.min(start.y, end.y)

    val bottom: Float
        get() = Math.max(start.y, end.y)

}

When the user touches BoxDrawingView, a new Box will be created and added to a list of existing boxes (Figure 30.3).

Figure 30.3  Objects in DragAndDraw

Objects in DragAndDraw

Back in BoxDrawingView, use your new Box object to track your drawing state.

Listing 30.5  Adding drag lifecycle functions (BoxDrawingView.kt)

class BoxDrawingView(context: Context, attrs: AttributeSet? = null) :
        View(context, attrs) {

    private var currentBox: Box? = null
    private val boxen = mutableListOf<Box>()

    override fun onTouchEvent(event: MotionEvent): Boolean {
        val current = PointF(event.x, event.y)
        var action = ""
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                action = "ACTION_DOWN"
                // Reset drawing state
                currentBox = Box(current).also {
                    boxen.add(it)
                }
            }
            MotionEvent.ACTION_MOVE -> {
                action = "ACTION_MOVE"
                updateCurrentBox(current)
            }
            MotionEvent.ACTION_UP -> {
                action = "ACTION_UP"
                updateCurrentBox(current)
                currentBox = null
            }
            MotionEvent.ACTION_CANCEL -> {
                action = "ACTION_CANCEL"
                currentBox = null
            }
        }

        Log.i(TAG, "$action at x=${current.x}, y=${current.y}")

        return true
    }

    private fun updateCurrentBox(current: PointF) {
        currentBox?.let {
            it.end = current
            invalidate()
        }
    }
}

Any time an ACTION_DOWN motion event is received, you set currentBox to be a new Box with its origin as the event’s location. This new Box is added to the list of boxes. (In the next section, when you implement custom drawing, BoxDrawingView will draw every Box within this list to the screen.)

As the user’s finger moves around the screen, you update currentBox.end. Then, when the touch is canceled or when the user’s finger leaves the screen, you update the current box with the final reported location and null out currentBox to end your draw motion. The Box is complete; it is stored safely in the list but will no longer be updated about motion events.

Notice the call to invalidate() in the updateCurrentBox() function. This forces BoxDrawingView to redraw itself so that the user can see the box while dragging across the screen. Which brings you to the next step: drawing the boxes to the screen.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
54.210.85.205