So far, we've seen how to manage onTouchEvent() events on our custom view, but that was on a view occupying the whole screen size, so it was a bit of a simple approach. If we want to include or view inside a ViewGroup that also handles touch events, for example, a ScrollView, what do we've to change?
Let's change the layout for this one:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="@dimen/activity_vertical_margin" tools:context="com.packt.rrafols.customview.MainActivity"> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentStart="true" android:layout_marginTop="13dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="100dp" android:paddingBottom="100dp" android:text="Top" android:background="@color/colorPrimaryDark" android:textColor="@android:color/white" android:gravity="center"/> <com.packt.rrafols.customview.CircularActivityIndicator android:layout_width="match_parent" android:layout_height="300dp"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="100dp" android:paddingBottom="100dp" android:text="Bottom" android:background="@color/colorPrimaryDark" android:textColor="@android:color/white" android:gravity="center"/> </LinearLayout> </ScrollView> </RelativeLayout>
Basically, we've put our custom view inside ScrollView, so both can process events. We should be selective in which events have to be processed by our view and have to be processed by which the ScrollView.
To do so, the view provides us with the getParent() method, to get its parent:
https://developer.android.com/reference/android/view/ViewParent.html.
Once we've the parent, we can call requestDisallowInterceptTouchEvent to disallow the parent and its parents to intercept touch events. In addition, to only consume the events we're interested in, we added a check to see if the location where the user touched is inside the radius of the circle or outside. If the touch is outside, we'll ignore the event and won't process it.
private boolean computeAndSetAngle(float x, float y) { x -= getWidth() / 2; y -= getHeight() / 2; double radius = Math.sqrt(x * x + y * y); if(radius > circleSize/2) return false; int angle = (int) (180.0 * Math.atan2(y, x) / Math.PI) + 90; selectedAngle = ((angle > 0) ? angle : 360 + angle); return true; } @Override public boolean onTouchEvent(MotionEvent event) { boolean processed; switch(event.getAction()) { case MotionEvent.ACTION_DOWN: processed = computeAndSetAngle(event.getX(), event.getY()); if(processed) { getParent().requestDisallowInterceptTouchEvent(true); changePressedState(true); } return processed; case MotionEvent.ACTION_UP: getParent().requestDisallowInterceptTouchEvent(false); changePressedState(false); return true; case MotionEvent.ACTION_MOVE: processed = computeAndSetAngle(event.getX(), event.getY()); invalidate(); return processed; default: return false; } }
We compute the radius applying the same Cartesian to the polar transformation we used before. We also changed the code, so if the touch is inside the radius of the circle, we call getParent().requestDisallowInterceptTouchEvent(true) on the ACTION_DOWN event, telling the ViewParent to not intercept the touch events. We need to undo this action by calling the opposite getParent().requestDisallowInterceptTouchEvent(false) on the ACTION_UP event.
This is the result of this change, and we can see that there is a TextView view on top and another one at the bottom of our custom view:
Now if we touch on the circle, our custom view will only process the event and change the circle angle. On the other hand, touching just outside the circle we'll let the ScrollView process the events.
There aren't that many changes, but when building a custom view that can potentially be reused in multiple places, we should definitely test it on multiple layout configurations to see how it behaves.
Find the full source code of this example in the Example10-Events folder in the GitHub repository.