We finished the last video by seeing a variety of touch events, and wondering how we could tell what the
user was doing at the time.
So the gesture detector can tell what type of gesture is happening.
That's actually its purpose.
So what we can do,
and I'm going to close down the log cat here, see in the onInterceptTouchEvent function is passed the event
on to a gesture detector, and let it help us to call the appropriate functions for the events that we
want to handle. Everything else
it'll will just let through. So I'm going to change the onInterceptTouchEvent method first.
So we're going to, we've got the log in there, we're going to leave that log call there,
and we're going to put, on the next line, val result equals gestureDetector
dot onTouchEvent parentheses e
Then we're going to Log dot e,
sorry Log.d parentheses,
TAG comma double quotes dot onInterceptTouchEvent returning dollar result. Then instead of returning true
we're going to return result.
Now the theory is that anything the gestureDetectors onTouchEventvent method deals with, should return
true. Anything it doesn't handle, should return false
so that something else can deal with it.
Now I wouldn't normally write a function in such a verbose way, and we can change it later when you're
happy with what's going on.
But I wanted to capture the return value from the call to onTouchEvent, so that we could log it.
So let's create the gestureDetector and override the functions we're interested in, and get rid of that error.
So we're going to create a new gesture detector after the interface declaration, before the onIntercept
TouchEvent. So add the gestureDetector. Now
it's going to be a private
val gestureDetector equals, GestureDetectorCompat is the one we want, parentheses
context comma object colon, then it's going to be GestureDetector dot SimpleOnGestureListener parentheses.
Then we want a code block,
and then we want a closing right brace,
a right parentheses rather. Then we've got our override function on the next line.
So what I've done there is very similar to adding an onClickListener to a button.
We're creating an anonymous class that extends a simple on Gesture Listener,
then overriding the methods we're interested in.
Now we're going to use the generator to generate the functions we want to override using control o, making
sure that I'm actually in that definition.
So the two that we want to override is onSingleTapUp and onLongPress.
Now one thing you may want to do is implement all the functions, and put logging in to see when they're
called.
That's a very useful thing to experiment to get an understanding of how things work.
Now from reading the documentation, we may consider using onSingleTapConfirmed rather than on
SingleTapUp,
and that's because a single tap may be followed by another one to make a double tap.
Now if we'd already responded to the first tap we'd miss the second one.
Now if we were going to allow some other action or a double tap, then onSingleTapConfirmed would definitely
be the one to use for that reason, and just before I OK it I'm going to have a quick check of the
documentation.
And if we scroll down to the bottom two, it says that onSingleTapUp results in the listener being notified
when a tap occurs,
whereas for onSingleTapConfirmed, unlike onSingleTapUp,
this will only be called after the detector is confident that the user's first tap is not followed by
a second tap, leading to a double tap gesture.
Now as we're not checking for double taps,
we can make the app a bit more responsive by removing the need for Android to wait to be sure that
there won't be a second tap.
But you can see why there's two methods there.
So I respond to one or the other, but not both. Alright so back to Android studio.
So I've already selected onSingleTapUp and onLongPress. Now before I click OK, have a look at
the function signatures for all these functions. They nearly all return a boolean value,
True or false, to indicate whether they've handled the event or not,
which is exactly what we relied on in our onInterceptTouchEvent function.
Unfortunately though, the onLongPress and onShowpress functions don't return a value, but we'll come back
to that. Because we're using onLongPress,
it's important that we understand what's going on. Now I'm going to click OK here, but I'm not going to
add any useful code just yet, because we should really try to understand what's happening with all this.
So let's just add some logging initially.
So firstly for the onSingleTapUp, what I'm going to do is change the return to be just return true, then
the previous line we'll just add a logging statement.
So Log.d parentheses TAG comma, double, double quotes dot onSingleTapUp colon starts, and lets take
a copy of that line and I'm going to remove the super call for onLongPress, paste in the log and change
that to the right name.
So that's going to be onLongPress starts.
Alright, so let's run this and see how we go, and whether it works OK.
Alright, so let's try a single click, and you can see we've got onSingleTapUp starts, and single click's working fine,
and the onInterceptTouchEvent, and I'm just going to scroll over
so we can see that, is returning true, which would be correct there. We got that from the call to the onTouch
Event method, because that's what our overridden onSingleTapUp function's returning.
So nothing else will attempt to handle the single click.
In this case that's fine.
We're going to deal with that,
so we don't want anyone else interfering. But note that false is returned first when the action down event
happens. You can see there, returning false. Our gesture detector isn't handling that,
so it returns false.
Now a long press though is a different matter.
That doesn't return a value,
so if we actually run that and check it,
we get false returned as you can see there. That means anything else that might be listening for a long press
will also try to handle it. Now
one reason I can think of for this behavior with long press, is if you hold your finger still for too
long before scrolling. Now to make the log cat a little bit easier to read, click at the end,
press enter and type in long pressing, and press enter again. Breaking up the log entries like this makes
it a bit easier to work out what's happened.
Now this might be hard to see in the video, but I'm going to click and hold the button down. I'm going to go back
to my emulator.
I'm holding the button down.
After a while we see that the onLongPress function being called in the log cat,
and you can see that it was there. Still holding the mouse button down,
I can happily scroll, even though we've theoretically handled the event.
Now if our functioned had returned true, we wouldn't actually be able to scroll as we saw earlier.
So that's the rationale behind the onLongPress and onShowPress methods not returning a boolean. Alright.
So now that we understand how it all works,
and by the way if you're still not sure experiment, but don't worry because this took me quite a while to get
my head around. There's a lot of redirection going on here, with the Android framework calling the onIntercept
TouchEvent function, which in turn calls the onTouch function of the gesture detector.
Now that then calls an appropriate function for the gesture in question, which may or may not have
been overridden, by our anonymous gesture detector compat class.
So you may well have to watch this bit of the video a few times, and work through the entries in the
log cat for it all to make sense.
But anyway let's start doing something a little bit more useful than just logging, when we respond to
a touch of it.
Now in this class, I'm just going to go back to our Android Studio code and close the log cat, what we're doing
currently is pretty simple.
So we're basically going to call back the functions that we created in the listener, which if you recall
is main activity.
So what I'm going to do is start by adding the code to those overridden functions of the gesture detector
onSingleTapUp first.
So I'm just going to make a bit of space here so
we can see what we're doing. There's onSingleTapUp.
Now we've got our log in there so we're going to leave that in there,
and we're going to put the code in here. I'm going to type val childView equals recyclerView dot findChildViewUnder,
and it's going to be parentheses e dot x comma
e dot y. We have got some errors there that we'll talk about shortly.
Next line we're going to add another log, a Log.d parentheses TAG comma double quotes dot onSingle
TapUp
calling listener dot onItemClick. Then the next line, it's going to be listener dot onItemClick parentheses
childView Comma recyclerView dot getChildAdapterPosition,
parentheses will be childView, and then we return true.
Now we've got these two errors on line 30,
when we try to access the properties of the motion event. Now that's passed in as a nullable type.
Now once again that's down to the parameters in the Java code not being annotated.
Now is it possible to get a single tap without an event?
Well actually no. If this function's called, something must have been tapped.
So this is another case where we can safely modify the parameter declaration to use a non-null type.
Let's go ahead and do that,
and that fixes the errors.
Alright so let's now make the change for onLongPress.
So we're going to do the same thing there, we're going to remove the question mark, because it's the same issue there. It's
not possible to get a long press without an event,
so we can safely remove that. Leaving the logging in there,
we're going to do, basically, a very similar code,
and what we can probably do there to save a bit of time is copy those three lines
into our onLongPress, and just make a few changes. So we've got val childView equals recyclerView dot
findChild
ViewUnder, e dot x e dot y but that's exactly the same. But the function that's being called
here is onLongPress calling listener, it's going to be onItemLongClick.
Then the next line is going to be the onItemLongClick call,
and with the same arguments, childView recyclerView dot getChildAdapterPosition and a passing childView.
So here essentially, both functions do the same thing, except that onSingleTapUp also returns the value true.
Now using the motion event that's passed as a parameter,
they check to see which view is underneath it,
and that's done by using the coordinates on the screen that were tapped.
So this findChildViewUnder function call does pretty much what it says. It checks to see what
was underneath the x y coordinates of the tap, and returns the view that it found.
Now if the listener's been set with a valid object, we just log the fact and call the onItemClick or
onItemLongClick functions that we created in main activity.
So most of the work's going to be done in main activity, but let's run the app again just to see that
those calls are being made successfully.
So we're going to run it. So we'll break up the log cat with a comment before each operation.
So firstly what we're going to do is go to the end, press enter, type in single tap,
press enter again, then go back to our emulator.
Then we're just going to tap the screen.
So basically a click causes the onSingleTapUp method to be, function to be called, which then in turn calls
the main activity's onItemClick.
So I'm going to come down to the end of the log again and type in long tap,
and let's go back and check if that works, do a long
tap, and we can see there that the long press caused the onLongPress function to then call main activity's on
ItemLongClick,
but you can see that there. Now I haven't been showing you that because I've been going off screen,
but we can also see if we do the long taps or short taps, we can see the Toast messages popping up,
normal tap, long tap. And note that it's also showing the position of the photos that we clicked on long press -
position 8 there, long press, position 10 and so on.
So we're good to go this point.
We've got a listener that's correctly responding to events and notifying main activity,
when the ones we're interested in, occur. Phew!
This is one of the most complicated things you'll come across in the Android framework, but it's incredibly
flexible, and it lets you do all sorts of cool things with animations in a recycler view,
but that power comes with a price.
So it's certainly not the easiest thing to understand. Now if you are struggling to make sense of it,
don't worry. You have some code that works, and you can just use that as a template for responding to
touch events in your apps.
Hopefully though the logging will help in working out what's happening, and what's being called when events
happen.
Now there's a few other ways to check for clicks on items in a recycler view, and a bit of Googling
will throw up some alternatives.
A popular approach is to implement the listener in either the adapter or the view holder, and that can
be easier to implement, depending on what you want to do when you get an event.
Now those approaches are well documented,
but this approach doesn't seem to be, perhaps because many people feel it's just too hard.
And so for that reason we use this approach because it's the one that Google suggest,
and if you understand this, then frankly you'll have no trouble understanding the other functions because
they're actually easier.
Alright so let's stop the video here, and in the next video we're going to add the code in main activity to do something
more useful than just displaying a Toast message.
So I'll see you in the next video.