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.