Building a CustomView -TicTacToe

Part 2

This is the second post of this “Building a CustomView” series, please check out the first one, if you haven’t already.

Handling Touch (Part 2)

In the last post we were able to draw text inside the first square. Actually, we could draw text inside any square we want, we just have to pick the right element from our squares array and pass to drawTextInsideRectangle() method. InOrder to do that we need to detect the touch on individual square sections.

Before that let’s read a little about Android Touch System.

Understanding Basics of Android Touch Event System

figure 1

In the figure 1, when the user touches the View it starts a flow of touch event which is wrapped up as a MotionEvent object. This object has information about touch location, event action, number of pointers, event time, pressure etc. We are only interested in action and location here.

Image Credit —
  1. Now, the flow of touch event happens in two phase. In first phase, every one whose bounds contain the touch point coordinates is notified about the touch. This starts at the Activity and flows top down through views or viewGroups. To dispatch event down they call the dispatchTouchEvent() method.
  2. The flow of the event can be broken by anyone using onInterceptTouchEvent() ( just have to return true ) when they are being notified about it.
  3. Now when the flow finally reaches the targeted view, it gets notified. If the user has implemented OnTouchListener.OnTouch(), this is where it gets handled else it goes to onTouchEvent of that view. From this targeted view the handling phase starts.
  4. If onTouchEvent()returns true for any touch event, then the flow stops there. This is what we are going to do, override onTouchEvent() and return true inside our custom view.
  5. If the targeted view does not handle the touch i.e returns false from onTouchEvent() .Then the handling flow starts from bottom to top, all the views from bottom to top are recursively given a chance to handle it. If no one handles it, the activity gets the chance to handle it and then the handling flow stops there.
  6. If you want to go in detail check this link.

We probably did not require that much info but its good to know.

Now we know what need’s to be done. We override onTouchEvent() and handle event actions inside our TicTacToeView. We are not handling multi-touch actions just the basic ones.

  1. event.x and event.y are the touch coordinates. This gives us the latest touch info.
  2. A “gesture” begins with ACTION_DOWN and ends with ACTION_UP.
  3. ACTION_DOWN: This gets called when the user first time places its finger on the screen. x, y are the touch coordinates for the same. This is the starting point of a gesture.
  4. ACTION_MOVE: This gets called when the user starts moving his finger on the screen.This will be called multiple times giving you latest x, y coordinates. We won’t be needing it.
  5. ACTION_UP: This is fired when the user removes his finger from the screen. Coordinates x, y will give you location from where he removed his finger. This is the end point of a gesture.
  6. ACTION_CANCEL: This is called when its parent takes the possession of the event. Like when we start a touch gesture on a view inside a RecyclerView but instead of clicking we start a scroll. This can also be the end point and here we will have to reset things and behave like the gesture never happened.

Note: For simplicity, we are taking the touch object as finger but it could be any of this — mouse, pen, finger, trackball.

If we return false from ACTION_DOWN no further action events are received.

Detecting and Highlighting the Touched Rectangle

We get the coordinate(x, y) of touch event happening on ACTION_DOWN, we just have to find from our squares array that which square is currently being touched or in other words in which square the coordinate x, y is present.

We loop through our squares(array of rects) that we have saved in our first post and we check whether the coordinate x, y is inside it (rect.contains(x, y)). If we find one, we return a Pair of the index for that square.

We will use the above method inside ACTION_DOWN.

We will also use one global variable touching to determine whether the user is touching the screen or not. Once he removes the finger, we set it to false, check ACTION_UP in the below-provided code. We save our touched square’s indexes and then call invalidate(rect: Rect) method to let the view know its time to redraw. Note that we are passing a rect area as a parameter so only that area is redrawn.

Why we need to redraw? That’s because we will highlight our touched square as long as touching is true.

Now inside onDraw() when we get touching is true we will highlight that touched square. We know the touched square as we have saved its indexes in rectIndex. rectIndex.first and rectIndex.second returns row and column indexes of touched square respectively.

We have used canvas.drawRect(rect, paint) to draw the highlight. Let’s see some result —

That wasn’t that difficult. Was it?

Now next part is to draw text(X or O) inside that square, but wait haven’t we already done that? We just have to pass that rect inside drawTextInsideRectangle(rect: Rect) that we have created in the previous post.

Yes, that is partially correct. When we call the invalidate() method, view redraws from scratch.The previous state is lost. Analyse the code below.

if (touching) {

Where is the else part for removing the highlight when the user removes his finger from the screen?.

It’s not required that’s because when the view redraws it will lose its previous state and there will be no highlight initially. Also notice that we are also redrawing our lines when invalidate() is called.

So even if we draw the text in a square it’s going to be erased when we click the other one. What we need is something to store that state. Remember in the previous post we created one array squareData and initialised with all empty text entries. We are going to use that array. We just have to fill that array with correct text and inside onDraw, we have to draw everything from it using drawTextInsideRectangle(rect: Rect).

Registering a click and storing the state

Filling the array with correct text is done when a successful touch gesture is detected. We have used the getRectIndexesFor() function inside ACTION_DOWN for detecting the initial touched square and insideACTION_UP for detecting the final touched square. If both originating square and final square are same we consider it as click else we ignore it. That means when the user presses on square one and moves his finger to square two and then lifts its finger, we will not consider it as a click.

onSquarePressed() method is implemented in the activity and notifies it about the click on a specific square. Based on some logic the activity tell the TicTacToeView to draw “X”or to draw “O”.

val moveX = "X"
val moveY = "O"
fun drawXAtPosition(x: Int, y: Int) {
squareData[x][y] = moveX

fun drawOAtPosition(x: Int, y: Int) {
squareData[x][y] = moveY

Above two methods are used to fill in the entries inside squareData and after that, we tell to redraw. This way when the user clicks a square, that info is passed to the activity which in turns call the drawXAtPosition or drawOAtPosition methods. These methods enter data inside squareData based on the clicked index and then calls invalidate()on that square.

Now we just have to draw the states out inside onDraw().

drawSquareStates() method loops through the squareDate array and draws all the entries out inside the onDraw() .

Let’s see the final result of this post.

Yeah! We are almost done building TicTacToeView.

Thank you so much for your time. Your comments and suggestions are welcome.