1
00:00:05,300 --> 00:00:11,960
We finished the last video by seeing a variety of touch events, and wondering how we could tell what the

2
00:00:11,960 --> 00:00:13,650
user was doing at the time.

3
00:00:13,940 --> 00:00:17,690
So the gesture detector can tell what type of gesture is happening.

4
00:00:17,810 --> 00:00:19,540
That's actually its purpose.

5
00:00:19,550 --> 00:00:20,660
So what we can do, 

6
00:00:20,800 --> 00:00:27,320
and I'm going to close down the log cat here, see in the onInterceptTouchEvent function is passed the event

7
00:00:27,320 --> 00:00:33,140
on to a gesture detector, and let it help us to call the appropriate functions for the events that we

8
00:00:33,140 --> 00:00:35,310
want to handle. Everything else

9
00:00:35,330 --> 00:00:40,630
it'll will just let through. So I'm going to change the onInterceptTouchEvent method first.

10
00:00:40,880 --> 00:00:45,330
So we're going to, we've got the log in there, we're going to leave that log call there,

11
00:00:45,560 --> 00:00:51,410
and we're going to put, on the next line, val result equals gestureDetector

12
00:00:54,960 --> 00:00:58,720
dot onTouchEvent parentheses e

13
00:00:59,690 --> 00:01:00,910
Then we're going to Log dot e,

14
00:01:01,590 --> 00:01:03,800
sorry Log.d parentheses,

15
00:01:03,910 --> 00:01:16,150
TAG comma double quotes dot onInterceptTouchEvent returning dollar result. Then instead of returning true

16
00:01:16,150 --> 00:01:18,770
we're going to return result.

17
00:01:19,250 --> 00:01:25,200
Now the theory is that anything the gestureDetectors onTouchEventvent method deals with, should return

18
00:01:25,210 --> 00:01:29,000
true. Anything it doesn't handle, should return false

19
00:01:29,050 --> 00:01:31,190
so that something else can deal with it.

20
00:01:31,290 --> 00:01:35,920
Now I wouldn't normally write a function in such a verbose way, and we can change it later when you're

21
00:01:35,920 --> 00:01:37,210
happy with what's going on.

22
00:01:37,390 --> 00:01:43,660
But I wanted to capture the return value from the call to onTouchEvent, so that we could log it.

23
00:01:43,870 --> 00:01:50,210
So let's create the gestureDetector and override the functions we're interested in, and get rid of that error.

24
00:01:50,260 --> 00:01:57,610
So we're going to create a new gesture detector after the interface declaration, before the onIntercept

25
00:01:57,640 --> 00:02:02,330
TouchEvent. So add the gestureDetector. Now

26
00:02:06,620 --> 00:02:07,750
it's going to be a private 

27
00:02:07,840 --> 00:02:17,020
val gestureDetector equals, GestureDetectorCompat is the one we want, parentheses

28
00:02:17,510 --> 00:02:29,580
context comma object colon, then it's going to be GestureDetector dot SimpleOnGestureListener parentheses.

29
00:02:31,800 --> 00:02:32,960
Then we want a code block,

30
00:02:36,390 --> 00:02:38,490
and then we want a closing right brace,

31
00:02:38,530 --> 00:02:44,140
a right parentheses rather. Then we've got our override function on the next line.

32
00:02:44,660 --> 00:02:49,180
So what I've done there is very similar to adding an onClickListener to a button.

33
00:02:49,350 --> 00:02:54,110
We're creating an anonymous class that extends a simple on Gesture Listener,

34
00:02:54,530 --> 00:02:57,670
then overriding the methods we're interested in.

35
00:02:57,920 --> 00:03:02,930
Now we're going to use the generator to generate the functions we want to override using control o, making

36
00:03:02,930 --> 00:03:06,100
sure that I'm actually in that definition.

37
00:03:06,230 --> 00:03:15,940
So the two that we want to override is onSingleTapUp and onLongPress.

38
00:03:15,990 --> 00:03:20,550
Now one thing you may want to do is implement all the functions, and put logging in to see when they're

39
00:03:20,550 --> 00:03:21,160
called.

40
00:03:21,300 --> 00:03:25,770
That's a very useful thing to experiment to get an understanding of how things work.

41
00:03:25,800 --> 00:03:31,830
Now from reading the documentation, we may consider using onSingleTapConfirmed rather than on 

42
00:03:31,830 --> 00:03:33,320
SingleTapUp,

43
00:03:33,480 --> 00:03:38,400
and that's because a single tap may be followed by another one to make a double tap.

44
00:03:38,670 --> 00:03:42,600
Now if we'd already responded to the first tap we'd miss the second one.

45
00:03:42,830 --> 00:03:48,810
Now if we were going to allow some other action or a double tap, then onSingleTapConfirmed would definitely

46
00:03:48,810 --> 00:03:53,190
be the one to use for that reason, and just before I OK it I'm going to have a quick check of the

47
00:03:53,190 --> 00:03:54,000
documentation.

48
00:04:00,950 --> 00:04:07,800
And if we scroll down to the bottom two, it says that onSingleTapUp results in the listener being notified

49
00:04:08,130 --> 00:04:09,790
when a tap occurs,

50
00:04:10,110 --> 00:04:14,290
whereas for onSingleTapConfirmed, unlike onSingleTapUp,

51
00:04:14,310 --> 00:04:20,470
this will only be called after the detector is confident that the user's first tap is not followed by

52
00:04:20,470 --> 00:04:23,640
a second tap, leading to a double tap gesture.

53
00:04:23,640 --> 00:04:26,070
Now as we're not checking for double taps,

54
00:04:26,070 --> 00:04:31,770
we can make the app a bit more responsive by removing the need for Android to wait to be sure that

55
00:04:31,770 --> 00:04:32,980
there won't be a second tap.

56
00:04:33,210 --> 00:04:35,050
But you can see why there's two methods there.

57
00:04:35,340 --> 00:04:40,220
So I respond to one or the other, but not both. Alright so back to Android studio.

58
00:04:40,350 --> 00:04:46,710
So I've already selected onSingleTapUp and onLongPress. Now before I click OK, have a look at

59
00:04:46,920 --> 00:04:52,490
the function signatures for all these functions. They nearly all return a boolean value,

60
00:04:52,650 --> 00:04:56,540
True or false, to indicate whether they've handled the event or not,

61
00:04:56,820 --> 00:05:01,590
which is exactly what we relied on in our onInterceptTouchEvent function.

62
00:05:01,590 --> 00:05:08,340
Unfortunately though, the onLongPress and onShowpress functions don't return a value, but we'll come back

63
00:05:08,340 --> 00:05:10,900
to that. Because we're using onLongPress,

64
00:05:10,920 --> 00:05:16,770
it's important that we understand what's going on. Now I'm going to click OK here, but I'm not going to

65
00:05:16,800 --> 00:05:21,590
add any useful code just yet, because we should really try to understand what's happening with all this.

66
00:05:21,630 --> 00:05:24,110
So let's just add some logging initially.

67
00:05:24,200 --> 00:05:32,870
So firstly for the onSingleTapUp, what I'm going to do is change the return to be just return true, then

68
00:05:32,880 --> 00:05:34,490
the previous line we'll just add a logging statement.

69
00:05:34,500 --> 00:05:45,060
So Log.d parentheses TAG comma, double, double quotes dot onSingleTapUp colon starts, and lets take

70
00:05:45,110 --> 00:05:54,850
a copy of that line and I'm going to remove the super call for onLongPress, paste in the log and change

71
00:05:54,850 --> 00:05:55,690
that to the right name.

72
00:05:55,700 --> 00:06:01,110
So that's going to be onLongPress starts.

73
00:06:01,420 --> 00:06:05,110
Alright, so let's run this and see how we go, and whether it works OK.

74
00:06:13,330 --> 00:06:20,960
Alright, so let's try a single click, and you can see we've got onSingleTapUp starts, and single click's working fine,

75
00:06:21,320 --> 00:06:24,630
and the onInterceptTouchEvent, and I'm just going to scroll over

76
00:06:24,680 --> 00:06:30,620
so we can see that, is returning true, which would be correct there. We got that from the call to the onTouch

77
00:06:30,620 --> 00:06:36,270
Event method, because that's what our overridden onSingleTapUp function's returning.

78
00:06:36,350 --> 00:06:39,580
So nothing else will attempt to handle the single click.

79
00:06:39,620 --> 00:06:41,310
In this case that's fine.

80
00:06:41,380 --> 00:06:42,620
We're going to deal with that,

81
00:06:42,710 --> 00:06:48,800
so we don't want anyone else interfering. But note that false is returned first when the action down event

82
00:06:49,070 --> 00:06:53,580
happens. You can see there, returning false. Our gesture detector isn't handling that,

83
00:06:53,720 --> 00:06:55,490
so it returns false.

84
00:06:55,600 --> 00:06:58,120
Now a long press though is a different matter.

85
00:06:58,340 --> 00:07:00,040
That doesn't return a value,

86
00:07:00,320 --> 00:07:02,420
so if we actually run that and check it,

87
00:07:07,720 --> 00:07:12,760
we get false returned as you can see there. That means anything else that might be listening for a long press

88
00:07:13,060 --> 00:07:15,640
will also try to handle it. Now

89
00:07:15,730 --> 00:07:21,310
one reason I can think of for this behavior with long press, is if you hold your finger still for too

90
00:07:21,310 --> 00:07:26,740
long before scrolling. Now to make the log cat a little bit easier to read, click at the end,

91
00:07:28,120 --> 00:07:38,730
press enter and type in long pressing, and press enter again. Breaking up the log entries like this makes

92
00:07:38,730 --> 00:07:41,640
it a bit easier to work out what's happened.

93
00:07:42,030 --> 00:07:46,290
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

94
00:07:46,290 --> 00:07:47,730
to my emulator.

95
00:07:48,180 --> 00:07:49,650
I'm holding the button down.

96
00:07:49,980 --> 00:07:53,540
After a while we see that the onLongPress function being called in the log cat,

97
00:07:53,550 --> 00:07:56,800
and you can see that it was there. Still holding the mouse button down,

98
00:07:56,800 --> 00:08:02,030
I can happily scroll, even though we've theoretically handled the event.

99
00:08:02,040 --> 00:08:07,210
Now if our functioned had returned true, we wouldn't actually be able to scroll as we saw earlier.

100
00:08:07,530 --> 00:08:14,250
So that's the rationale behind the onLongPress and onShowPress methods not returning a boolean. Alright.

101
00:08:14,490 --> 00:08:16,560
So now that we understand how it all works,

102
00:08:16,650 --> 00:08:21,120
and by the way if you're still not sure experiment, but don't worry because this took me quite a while to get

103
00:08:21,120 --> 00:08:27,710
my head around. There's a lot of redirection going on here, with the Android framework calling the onIntercept

104
00:08:27,720 --> 00:08:33,740
TouchEvent function, which in turn calls the onTouch function of the gesture detector.

105
00:08:34,049 --> 00:08:38,909
Now that then calls an appropriate function for the gesture in question, which may or may not have

106
00:08:38,909 --> 00:08:43,730
been overridden, by our anonymous gesture detector compat class.

107
00:08:43,860 --> 00:08:48,600
So you may well have to watch this bit of the video a few times, and work through the entries in the

108
00:08:48,600 --> 00:08:50,880
log cat for it all to make sense.

109
00:08:51,210 --> 00:08:56,040
But anyway let's start doing something a little bit more useful than just logging, when we respond to

110
00:08:56,040 --> 00:08:57,130
a touch of it.

111
00:08:57,480 --> 00:09:03,350
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

112
00:09:03,350 --> 00:09:04,710
currently is pretty simple.

113
00:09:04,910 --> 00:09:10,700
So we're basically going to call back the functions that we created in the listener, which if you recall

114
00:09:10,700 --> 00:09:12,020
is main activity.

115
00:09:12,410 --> 00:09:18,000
So what I'm going to do is start by adding the code to those overridden functions of the gesture detector

116
00:09:18,010 --> 00:09:19,640
onSingleTapUp first.

117
00:09:23,610 --> 00:09:24,970
So I'm just going to make a bit of space here so

118
00:09:25,010 --> 00:09:27,550
we can see what we're doing. There's onSingleTapUp.

119
00:09:27,680 --> 00:09:30,340
Now we've got our log in there so we're going to leave that in there,

120
00:09:31,020 --> 00:09:41,780
and we're going to put the code in here. I'm going to type val childView equals recyclerView dot findChildViewUnder,

121
00:09:41,820 --> 00:09:45,800
and it's going to be parentheses e dot x comma

122
00:09:45,900 --> 00:09:50,430
e dot y. We have got some errors there that we'll talk about shortly.

123
00:09:50,450 --> 00:09:58,290
Next line we're going to add another log, a Log.d parentheses TAG comma double quotes dot onSingle

124
00:09:58,290 --> 00:09:59,030
TapUp

125
00:10:01,720 --> 00:10:13,660
calling listener dot onItemClick. Then the next line, it's going to be listener dot onItemClick parentheses

126
00:10:14,230 --> 00:10:21,220
childView Comma recyclerView dot getChildAdapterPosition,

127
00:10:22,060 --> 00:10:28,050
parentheses will be childView, and then we return true.

128
00:10:28,470 --> 00:10:31,580
Now we've got these two errors on line 30,

129
00:10:31,640 --> 00:10:37,890
when we try to access the properties of the motion event. Now that's passed in as a nullable type.

130
00:10:37,910 --> 00:10:43,210
Now once again that's down to the parameters in the Java code not being annotated.

131
00:10:43,220 --> 00:10:47,730
Now is it possible to get a single tap without an event?

132
00:10:48,350 --> 00:10:51,890
Well actually no. If this function's called, something must have been tapped.

133
00:10:52,010 --> 00:10:58,930
So this is another case where we can safely modify the parameter declaration to use a non-null type.

134
00:10:58,940 --> 00:11:00,180
Let's go ahead and do that,

135
00:11:03,150 --> 00:11:05,000
and that fixes the errors.

136
00:11:05,930 --> 00:11:08,570
Alright so let's now make the change for onLongPress.

137
00:11:08,600 --> 00:11:15,930
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

138
00:11:16,130 --> 00:11:18,280
not possible to get a long press without an event,

139
00:11:18,290 --> 00:11:21,770
so we can safely remove that. Leaving the logging in there,

140
00:11:22,250 --> 00:11:24,990
we're going to do, basically, a very similar code,

141
00:11:25,370 --> 00:11:29,220
and what we can probably do there to save a bit of time is copy those three lines

142
00:11:32,860 --> 00:11:39,850
into our onLongPress, and just make a few changes. So we've got val childView equals recyclerView dot

143
00:11:39,860 --> 00:11:40,460
findChild

144
00:11:40,500 --> 00:11:45,940
ViewUnder, e dot x e dot y but that's exactly the same. But the function that's being called

145
00:11:45,990 --> 00:11:53,050
here is onLongPress calling listener, it's going to be onItemLongClick.

146
00:11:53,390 --> 00:11:57,150
Then the next line is going to be the onItemLongClick call,

147
00:12:00,280 --> 00:12:06,540
and with the same arguments, childView recyclerView dot getChildAdapterPosition and a passing childView.

148
00:12:06,560 --> 00:12:13,880
So here essentially, both functions do the same thing, except that onSingleTapUp also returns the value true.

149
00:12:14,390 --> 00:12:17,410
Now using the motion event that's passed as a parameter,

150
00:12:17,520 --> 00:12:20,190
they check to see which view is underneath it,

151
00:12:20,500 --> 00:12:24,010
and that's done by using the coordinates on the screen that were tapped.

152
00:12:24,010 --> 00:12:30,040
So this findChildViewUnder function call does pretty much what it says. It checks to see what

153
00:12:30,040 --> 00:12:36,040
was underneath the x y coordinates of the tap, and returns the view that it found.

154
00:12:36,060 --> 00:12:41,410
Now if the listener's been set with a valid object, we just log the fact and call the onItemClick or

155
00:12:41,410 --> 00:12:45,180
onItemLongClick functions that we created in main activity.

156
00:12:45,550 --> 00:12:50,290
So most of the work's going to be done in main activity, but let's run the app again just to see that

157
00:12:50,290 --> 00:12:52,430
those calls are being made successfully.

158
00:12:52,450 --> 00:12:58,540
So we're going to run it. So we'll break up the log cat with a comment before each operation.

159
00:12:58,980 --> 00:13:04,140
So firstly what we're going to do is go to the end, press enter, type in single tap,

160
00:13:06,960 --> 00:13:09,000
press enter again, then go back to our emulator.

161
00:13:09,000 --> 00:13:12,070
Then we're just going to tap the screen.

162
00:13:15,340 --> 00:13:21,680
So basically a click causes the onSingleTapUp method to be, function to be called, which then in turn calls

163
00:13:21,680 --> 00:13:24,330
the main activity's onItemClick.

164
00:13:24,420 --> 00:13:27,090
So I'm going to come down to the end of the log again and type in long tap,

165
00:13:30,630 --> 00:13:33,120
and let's go back and check if that works, do a long

166
00:13:33,150 --> 00:13:41,990
tap, and we can see there that the long press caused the onLongPress function to then call main activity's on

167
00:13:41,990 --> 00:13:43,720
ItemLongClick,

168
00:13:43,820 --> 00:13:48,360
but you can see that there. Now I haven't been showing you that because I've been going off screen,

169
00:13:48,370 --> 00:13:54,520
but we can also see if we do the long taps or short taps, we can see the Toast messages popping up,

170
00:13:54,520 --> 00:14:03,700
normal tap, long tap. And note that it's also showing the position of the photos that we clicked on long press -

171
00:14:04,830 --> 00:14:09,540
position 8 there, long press, position 10 and so on.

172
00:14:09,660 --> 00:14:11,490
So we're good to go this point.

173
00:14:11,490 --> 00:14:16,980
We've got a listener that's correctly responding to events and notifying main activity,

174
00:14:17,040 --> 00:14:21,480
when the ones we're interested in, occur. Phew!

175
00:14:21,740 --> 00:14:27,390
This is one of the most complicated things you'll come across in the Android framework, but it's incredibly

176
00:14:27,390 --> 00:14:32,870
flexible, and it lets you do all sorts of cool things with animations in a recycler view,

177
00:14:33,060 --> 00:14:35,110
but that power comes with a price.

178
00:14:35,310 --> 00:14:41,080
So it's certainly not the easiest thing to understand. Now if you are struggling to make sense of it,

179
00:14:41,100 --> 00:14:47,340
don't worry. You have some code that works, and you can just use that as a template for responding to

180
00:14:47,340 --> 00:14:49,370
touch events in your apps.

181
00:14:49,420 --> 00:14:54,660
Hopefully though the logging will help in working out what's happening, and what's being called when events

182
00:14:54,660 --> 00:14:55,890
happen.

183
00:14:55,890 --> 00:15:00,630
Now there's a few other ways to check for clicks on items in a recycler view, and a bit of Googling

184
00:15:00,630 --> 00:15:02,620
will throw up some alternatives.

185
00:15:02,760 --> 00:15:08,490
A popular approach is to implement the listener in either the adapter or the view holder, and that can

186
00:15:08,490 --> 00:15:13,410
be easier to implement, depending on what you want to do when you get an event.

187
00:15:13,480 --> 00:15:15,390
Now those approaches are well documented,

188
00:15:15,570 --> 00:15:20,330
but this approach doesn't seem to be, perhaps because many people feel it's just too hard.

189
00:15:20,580 --> 00:15:25,120
And so for that reason we use this approach because it's the one that Google suggest,

190
00:15:25,200 --> 00:15:30,180
and if you understand this, then frankly you'll have no trouble understanding the other functions because

191
00:15:30,180 --> 00:15:31,300
they're actually easier.

192
00:15:31,620 --> 00:15:36,990
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

193
00:15:36,990 --> 00:15:39,880
more useful than just displaying a Toast message.

194
00:15:39,900 --> 00:15:41,260
So I'll see you in the next video.

