I am using Navigation Drawer in my app, that contains some Fragments just like in the below picture.
Every Fragment Contains another ViewPager that is an ImageSlider, and below that is a Listview and at the top I am using the SwipeRefreshLayout. My problem is the image slider works well on devices that has Android version 3.0 or higher but the swipe left or right doesn't works on devices 2.3 and lower, instead it invokes the Parent ViewPager's swipe that is it navigates the fragment. I am using support Version 4 library for this purpose to support devices lower than 3.0. All functions works quite well on 2.3 devices except that one. I have googled it but I haven't found any help anywhere. So to make it scroll what should I do for this, any idea/help will be highly appreciated.
You can use this ViewPager as your parent ViewPager. This allows the child ViewPager to scroll.
public class CustomViewPager extends ViewPager {
public CustomViewPager(Context context) {
super(context);
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
try {
//Handle the issue only in lower versions of android
if (v != this && v instanceof ViewPager && CJRAppCommonUtility.getOSVersion() < android.os.Build.VERSION_CODES.HONEYCOMB) {
ViewPager viewPager = (ViewPager) v;
int currentPage = viewPager.getCurrentItem();
int size = viewPager.getAdapter().getCount();
//if ViewPager has reached its end and if user tries to swipe left allow the parent to scroll
if (currentPage == (size - 1) && dx < 0) {
return false;
}
//if ViewPager has reached its start and if user tries to swipe right allow the parent to scroll
else if (currentPage == 0 && dx > 0) {
return false;
}
//Allow the child to scroll hence blocking parent scroll
else {
return true;
}
}
return super.canScroll(v, checkV, dx, x, y);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
Android developers site has a nice explanation about handling touch events in a Viewgroup. You can refer it here: http://developer.android.com/training/gestures/viewgroup.html
Hope it helps!!
In older version of Android requestDisallowInterceptTouchEvent doesn't work that great. The solution here is to extend view pager and override the onInterceptTouchEvent and store a list of children that are scrollable. Then, when onInterceptTouchEvent is called you can iterate through the list of scrollable children, get their hit rect, and see if the touch event is inside the hit rect. If it is, you can just return false to not handle it and let the child take it.
Something like this:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
for (View view : scrollableChildren)
{
// get the hit rectangle for the view
Rect rect = new Rect();
view.getHitRect(rect);
// check to see if the click was inside this child
if (rect.contains((int) ev.getX(), (int) ev.getY()))
{
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
Related
I have a View Pager (VP) which contains a Horizontal Scroll View (HSV). If the HSV reaches one of its edges or is not able to scroll at all, on a new swipe in the blocked direction VP should take over scrolling to the next page. I hesitated to ask this question because I found similar ones like these:
Can I use Horizontal Scrollview Inside a Viewpager in Android?
or
horizontalscrollview inside viewpager
But the solution did not work for me. 'v instanceof HorizontalScrollView' gets true but viewPager does not scroll
Any other ideas how to achieve the desired behaviour?
public class MyViewPager extends ViewPager {
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
// Update 1
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
//return super.onInterceptTouchEvent(ev);
}
/**
* https://stackoverflow.com/questions/22781496/can-i-use-horizontal-scrollview-inside-a-viewpager-in-android
*/
#Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof HorizontalScrollView) {
return true;
}
return super.canScroll(v, checkV, dx, x, y);
}
}
child view: view_pager_page.xml:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<include layout="#layout/" />
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</FrameLayout>
</RelativeLayout>
parent view: view_pager.xml
<android.support.design.widget.CoordinatorLayout>
...
<LinearLayout>
<packagepath.MyViewPager
android:layout_width="match_parent"
android:layout_height="wrap_content">
</packagepath.MyViewPager>
</LinearLayout>
...
<android.support.design.widget.CoordinatorLayout>
Update 1: When overriding 'onInterceptTouchEvent' and let it always return true VP scrolls, but HSV doesn't. I think this must return true only if HSV reaches edges right? How can I figure out in this method if it is the case?
Update 2: I reconstructed the touch event mechanism of android hoping to get some insight of how to intercept the motion event flow. E.g. in HSV I can simply return false to let VP consume this and all subsequent motion events. Unfortunately I need two motion events of type MotionEvent.MOVE to decide if HSV or VP should scroll when reaching an edge (if HSV has reached right edge, a right swipe scrolls HSV back and a left swipe scrolls to next page of VP). But if I skip the MotionEvent.DOWN action neither HSV or VP starts scrolling... so hard to solve. Any ideas?
Touchevent Mechanism in Android
(Warning: Graphic is not complete and will contain mistakes, everyone is invited to correct it :-))
Update 3: Finally I got it working. Understanding the Touchevent mechanism helped a lot and also the first comment of ZeroOne. I will post my solution when I have time for it.
I solved this with a custom HorizontalScrollView. The key is to override the onTouchEvent() method and return false if you are at the left edge and swiping right, or the right edge and swiping left. Returning false means this view didn't consume the touch event and this event can bubble back up the view hierarchy to be handled by the ViewPager.
public class HorizontalScrollViewForViewPager extends HorizontalScrollView {
float old_x, old_y;
public HorizontalScrollViewForViewPager(Context context) {
super(context);
}
public HorizontalScrollViewForViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HorizontalScrollViewForViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
//Start of touch. Could be tap, could be drag.
old_x = ev.getX();
old_y = ev.getY();
} else if (action == MotionEvent.ACTION_MOVE) {
//Drag movement underway
float deltaX = ev.getX() - old_x;
float deltaY = ev.getY() - old_y;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
//scrolling more left/right than up/down
if (deltaX > 0 && getScrollX() == 0) {
//dragging left, at left edge of HorizontalScrollView. Don't handle this touch event, let it bubble up to ViewPager
return false;
} else {
//dragging right. Use first child to determine width of content inside HorizontalScrollView
int childWidth = getChildAt(0).getWidth();
if (deltaX < 0 && (this.getScrollX() + this.getWidth()) >= childWidth) {
//swiping left, and at right edge of HorizontalScrollView. Don't handle this touch event, let it bubble up to ViewPager
return false;
}
}
}
}
return super.onTouchEvent(ev);
}
}
1.Extend ViewPager Class:
public class ViewPagerContainingHorizontalScrollView extends ViewPager {
private Float x_old;
private boolean bDoIntercept = false;
private boolean bHsvRightEdge = false;
private boolean bHsvLeftEdge = true;
public ViewPagerContainingHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private float calculateDistanceSwipe(MotionEvent ev){
float distance = 0;
if (x_old == null) {
x_old = ev.getX();
} else {
distance = ev.getX() - x_old;
x_old = null;
}
return distance;
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
mDoIntercept = false;
if(ev.getAction() == MotionEvent.ACTION_MOVE) {
float distance = calculateDistanceSwipe(ev);
if (distance < 0) {//scrolling left direction
if (bHsvRightEdge) { //HSV right edge
bDoIntercept = true;
//When scrolling slow VP may not switch page.
//Then HSV snaps back into old position.
//To allow HSV to scroll into non blocked direction set following to false.
bHsvRightEdge = false;
}
bHsvLeftEdge = false;//scrolling left means left edge not reached
} else if (distance > 0) {//scrolling right direction
if (bHsvLeftEdge) { //HSV left edge
bDoIntercept = true;
//When scrolling slow VP may not switch page.
//Then HSV snaps back into old position.
//To allow HSV to scroll into non blocked direction set following to false.
bHsvLeftEdge = false;
}
bHsvRightEdge = false;//scrolling right means right edge not reached
}
}
return super.dispatchTouchEvent(ev);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(bDoIntercept){
return true;
}else{
return super.onInterceptTouchEvent(ev);
}
}
#Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof HorizontalScrollView) {
HorizontalScrollView hsv = (HorizontalScrollView) v;
int max_scrollX = hsv.getChildAt(0).getWidth() - hsv.getWidth();
int min_scrollX = 0;
int current_scroll_x = hsv.getScrollX();
if (current_scroll_x == max_scrollX) { //HSV right edge
bHsvRightEdge = true;
}
if (current_scroll_x == min_scrollX) { //HSV left edge
bHsvLeftEdge = true;
}
return true;
}
return super.canScroll(v, checkV, dx, x, y);
}
}
Use this custom VP in XML.
Enjoy nested HSV scrolling in VP :-)
Touch Event Mechanism Overview for this specific case
I want to implement a recyclerview within a vertical viewpager. My current layout looks like the following
VerticalViewpager
- Fragment1
- ImageView
- Fragment2
- RecyclerView
If I swipe from Fragment1 to Fragment2 everything works fine. I am able to scroll within the recyclerview up and down. The problem occurs if I try to swipe/scroll back to Fragment1.
If the user has scrolled to the top of the recyclerview, I would like to forward the next "Up"-Scroll event to the vertical viewpager. I have tried overriding the onInterceptTouchEvent method, but unfortunately still no success. Any ideas out there? Thanks for your help :)
You need do disable the scroll. Try using recyclerView.setNestedScrollingEnabled(false);
1) You need to use support library 23.2.0 (or) above
2) and recyclerView height will be wrap_content.
3) recyclerView.setNestedScrollingEnabled(false)
But by doing this the recycler pattern don't work. (i.e all the views will be loaded at once because wrap_content needs the height of complete recyclerView so it will draw all recycler views at once. No view will be recycled). Try not to use this pattern util unless it is really required.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled (RecyclerView recyclerView,int dx, int dy) {
int topRowVerticalPosition = (recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop();
if (topRowVerticalPosition >= 0) {
//top position
}
}
#Override
public void onScrollStateChanged (RecyclerView recyclerView,int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});
By using this code you can find whether the user is in top position. In the position you can make the view pager to move to your desired position.
If you have access VerticalViewPager from RecyclerView, you can extend RecyclerView and check canScrollVertically(-1) and forward touch event to viewpager
I would suggest migrating to ViewPager2 first, since it natively allows for vertical scrolling, and then using the solution provided by the official documentation to supported nested scrollable elements.
Essentially, you need to add the NestedScrollableHost to your project and then wrap your RecyclerView with it, similar to below:
<NestedScrollableHost
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</NestedScrollableHost>
Note that this solution only works for the layout your provided where the RecyclerView would be an immediate child of one of your ViewPager's screen. This solution won't work for other RecyclerView that are nested in the main RecyclerView.
You need to forbid nestedscroll in parent scrollview
i have new implement for this , you can try the below link https://github.com/liuxiaocong/VerticalViewpage
I had the same problem. The answer from Mr.India was helpful, but as your commented, it only worked sometimes.
What I did was
recyclerView.setOnTouchListener(new View.OnTouchListener() {
public float speed;
public VelocityTracker mVelocityTracker;
#Override
public boolean onTouch(View v, MotionEvent event) {
int index = event.getActionIndex();
int action = event.getActionMasked();
int pointerId = event.getPointerId(index);
switch (action) {
case MotionEvent.ACTION_DOWN:
if(mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
else {
mVelocityTracker.clear();
}
mVelocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
if(mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000);
speed = VelocityTrackerCompat.getYVelocity(mVelocityTracker, pointerId);
}
break;
case MotionEvent.ACTION_UP:
int topRowVerticalPosition = (recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop();
if(speed > 500 && topRowVerticalPosition >= 0) {
ReaderFragmentParent parent = (ReaderFragmentParent) getActivity();
ReaderPager pager = parent.getPager();
if(pager != null) {
pager.setCurrentItem(pager.getCurrentItem()-1);
}
}
return false;
case MotionEvent.ACTION_CANCEL:
mVelocityTracker.recycle();
break;
}
return false;
}
});
On the ACTION_UP I verify if the speed > 500, it means it's going up on a considered speed and the topRowVerticalPosition means it is on top of the recyclerview. Then I get the ViewPager and setCurrentItem to previous item.
it's a bit of a dirty hack, but it was the only way I found to make look like a good scrolling between pages on the Vertical ViewPager
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled (RecyclerView recyclerView,int dx, int dy) {
}
#Override
public void onScrollStateChanged (RecyclerView recyclerView,int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (!mRecyclerView.canScrollVertically(-1) && newState == 0) {
Log.e("canScrollVertically", mRecyclerView.canScrollVertically(-1)+"");
verticalViewPager.setCurrentItem(0, true);
}
}
});
I have a ViewPager that contains two fragments, which each contains listviews with swipeable list items. To make it easier for the user to swipe, I would like it so that if the user swipes left in the first fragment, the swipe is picked up by the listview. If the user swipes right in the second fragment, the swipe is picked up by the listview. Since there is only two fragments, it makes sense for the swipes to be picked up by the listview, and not the viewpager since there are no more fragments to scroll to after these two.
So, would it be possible to intercept the touch, and pass it to the listview based on the viewpager page being shown the and the direction of the swipe?
Here is the swipable listview library I am using: https://github.com/timroes/EnhancedListView
I think you can customize the ViewPager and control the behavior of ListView swipe in canScroll method of the ViewPager.
I tried a sample and it seems to be working fine. You can use this Custom ViewPager.
public class CustomViewPager extends ViewPager {
public CustomViewPager(Context context) {
super(context);
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof ViewPager) {
if (getChildAt(getCurrentItem()) != null) {
//get the ListView of current Fragment
EnhancedListView enhancedListView = (EnhancedListView) getChildAt(getCurrentItem()).findViewById(R.id.list);
//If the user is in first page and tries to swipe left, enable the ListView swipe
if (getCurrentItem() == 0 && dx > 0) {
enhancedListView.enableSwipeToDismiss();
}
//If the user is in second page and tries to swipe right, enable the ListView swipe
else if (getCurrentItem() == 1 && dx < 0) {
enhancedListView.enableSwipeToDismiss();
}
//Block the ListView swipe there by enabling the parent ViewPager swiping
else {
enhancedListView.disableSwipeToDismiss();
}
}
}
return super.canScroll(v, checkV, dx, x, y);
}
}
Also you have to do some changes in EnhancedListView library as well. Because canScroll method is called after ACTION_DOWN event, if we enable the swiping in canScroll method - it skips the logic defined forACTION_DOWN event and it might results in unexpected behavior. So block the swipe only if touch event is ACTION_MOVE. These are the changes in onTouchEvent of EnhancedListView library.
//EnhancedListView class
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (!mSwipeEnabled && (ev.getAction() == MotionEvent.ACTION_MOVE)) {
return super.onTouchEvent(ev);
}
.....
I am not sure if this is the perfect solution for the problem but it is working just fine.
Here are some screenshots of the sample app if the question or the answer is not clear.
App consists of ViewPager with two Fragment pages. Each page has an EnhanedListView (Which provides Swipe To Dismiss/Delete feature). Since both parent ViewPager and child List item can be swiped, it causes a conflict. By default swipe is picked up by the child ListItem which prevents the parent ViewPager from swiping.
Required solution:
If the user is in first page and swipes right then list item should be swiped.
If the user is in second page and swipes left then list item should be swiped.
In other cases the ViewPager should be swiped.
update : To fix the bug with SwipeRefreshLayout, here are the slight changes in custom ViewPager code.
public class ScrollLock extends ViewPager {
public ScrollLock(Context context) {
super(context);
}
public ScrollLock(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof ViewPager) {
if (getChildAt(getCurrentItem()) != null) {
//set it so it does not swipe to refresh while swiping away a list item
SwipeRefreshLayout swipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipe);
//get the ListView of current Fragment
EnhancedListView enhancedListView = (EnhancedListView) getChildAt(getCurrentItem()).findViewById(R.id.listView1);
//If the user is in first page and tries to swipe left, enable the ListView swipe
if (getCurrentItem() == 0 && dx > 0) {
enhancedListView.enableSwipeToDismiss();
swipeLayout.setEnabled(false);
return true;
}
//If the user is in second page and tries to swipe right, enable the ListView swipe
else if (getCurrentItem() == 1 && dx < 0) {
enhancedListView.enableSwipeToDismiss();
swipeLayout.setEnabled(false);
return true;
}
//Block the ListView swipe there by enabling the parent ViewPager swiping
else {
enhancedListView.disableSwipeToDismiss();
}
}
}
return super.canScroll(v, checkV, dx, x, y);
}
}
I use a SlidingDrawer as my main layout. Inside the content area I have a Fragment (which contains a ListView) When the activity first loads everything is great, the listview scrolls correctly.
When I start a different activity and then come back, the first scroll motion I try is intercepted by the SlidindDrawer, and either opens or closes it. As soon as you stop the scroll and pick up your finger, the ListView is again able to scroll.
I would like the ListView to be able to scroll when the activity resumes. And just generally be able to control whether the SlidingDrawer is the one getting focus.
UPDATE:
I have narrowed the issue down a little bit. I have extended the SLidingDrawer to allow for click on buttons in the handle with the following code.
Override
public boolean onInterceptTouchEvent(MotionEvent event) {
super.onInterceptTouchEvent(event);
if (mHandleLayout != null) {
int clickX = (int) (event.getX() - mHandleLayout.getLeft());
int clickY = (int) (event.getY() - mHandleLayout.getTop());
if (isAnyClickableChildHit(mHandleLayout, clickX, clickY))
return false;
}
return super.onInterceptTouchEvent(event);
}
private boolean isAnyClickableChildHit(ViewGroup viewGroup, int clickX, int clickY) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View childView = viewGroup.getChildAt(i);
if (TAG_CLICK_INTERCEPTED.equals(childView.getTag())) {
childView.getHitRect(mHitRect);
if (mHitRect.contains(clickX, clickY))
return true;
}
if (childView instanceof ViewGroup && isAnyClickableChildHit((ViewGroup) childView, clickX, clickY))
return true;
}
return false;
}
If I comment out the onInterceptTouchEvent function, everything seems to work normally.
I noticed that you are calling super.onInterceptTouchEvent(event) twice. Why?
That could be the reason for the issue.
I have read a few questions regarding this topic on SO but haven't really found a solid answer to it.
I have a framelayout that I stack multiple custom views on, however the onTouch event only works with the top view. (the custom views are all the same view with the same onTouch event, just multiple of them)
FrameLayout
customView[2] <--- this is the last view added and the only one that receives the event
customView[1]
customView[0]
I'm testing it on Android 2.2 and am wondering if there is any way for the other views below to know where the touch happened?
EDIT (Adding some code)
I'm adding some code to hopefully help explain where I'm running into issues. At first I just automatically had the onTouchEvent return true. This made it so that the last view (in my case customerView[2]) would be the only one generating a value.
However, once I added the method to set the onTouchEvent to return true or false, now the only view returning a generated value is customView[0].
I hope this clears up what I am asking. I'm rather new to this and I appreciate you taking the time to explain it (and of course I appreciate your patience).
Also, I realize that my TextView's don't update with the value on each touchEvent, I'm working on fixing that.
My Activity:
public class MyActivity extend Activity {
CustomView[] customView;
TextView[] textView;
int numViews 3;
//FrameLayout and Params created
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
for(int i = 0; i < numViews; i++) {
customView[i] = new CustomView(this, i);
//Allows the onTouch to be handled by all Views - View[0] is the bottom view
if(i == 0) {
customView[i].setTouchBool(true); //set view's onTouch to return true
} else {
customView[i].setTouchBool(false); //set view's onTouch to return false
}
//Set TextView to display the number generated by the CustomView
textView[i].setText(Double.toString(customView[i].getGeneratedNumber()));
//Add views to main layout
frame.addView(textView[i]);
frame.addView(customView[i]);
}
}
}
My View:
public class CustomView extends View {
boolean onTouchHandler = true;
int xVal = 0, yVal = 0;
int index;
double generatedNum = 0;
public CustomView(Context context) {
this(context, 0);
this.index = 0;
}
public CustomView(Context context, int index) {
super(context);
this.index = index;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch(action) {
case MotionEvent.ACTION_DOWN: {
//do logic
}
case MotionEvent.ACTION_MOVE: {
//do logic
}
case MotionEvent.ACTION_UP: {
xVal = (int) ev.getX();
yVal = (int) ev.getY();
generateNumber(xVal, yVal, index);
break;
}
}
return onTouchHandler;
}
private void generateNumber(int x, int y, int index) {
if(index == 0) {
generatedNum = (x / 2) * (y / 2) + 64;
} else {
generatedNum = (x / 2) * (y / 2) + (index * 128);
}
}
public double getGeneratedNumber() {
return generatedNum;
}
public boolean setTouchBool(boolean b) {
this.onTouchHandler = b;
}
}
Android will cascade down the views calling onTouchEvent on each one until it receives a true from one of them. If you want a touch event to be handled by all of them, then return false until it reaches the last one.
EDIT:
Ok. If I understand correctly, you have a single top view containing a bunch of child views one layer deep. My original answer was assuming that you had three custom views that were on top of each other in the ViewGroup's hierarchy (View3 is a child of View2. View2 is a child of View1. View1 is a child of ParentView). You want the user's touch event on the parent view to get sent to all of it's children.
If that's the case, AFAIK, there is no view in Android's API that allows that. So, you'll have to make a custom view that does it.
OK, I haven't tested this, so please tell me if it works and if it's what you're trying. Create a custom class that extends whatever object frame is, then override the onTouch method like so.
#Override
public boolean onTouchEvent(MotionEvent ev) {
for(int i = 0; i < this.getChildCount(); i++){
this.getChildAt(i).dispatchTouchEvent(ev);
}
return true;
}
Now, keep the same logic that your custom views have, except they should all return false because your parent view will not receive the onTouch event unless they do as stated in my previous answer
note: with this implementation, the child view that the user actually touches will fire twice because the logic will go
fire child touch event -> return false -> fire parent touch event -> fire child touch event again
I know this question is very old, but I had the same problem and solved it by creating my own Layout to determine which child is actually touched.
I therefore iterate over the children of my custom layout and check if the user actually clicked on the view. The collision detection is handled in the custom view's onTouch() method. (Collision detection is done by intersecting a Region() with the event's x,y coordinates. For me this was convennient because I drew the custom view with a Path())
Here is a kotlin code snippet from my custom layout for better understanding:
class CustomLayout(context: Context, attrs: AttributeSet) :
RelativeLayout(context, attrs){
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
if(ev.action != MotionEvent.ACTION_UP){
return true
}
//Iterate over child view and search for the right child that should handle this touch event
for (i in childCount - 1 downTo 0) {
val child = getChildAt(i)
if (!viewTouched(child, ev)) {
continue
}
//Do something
Timber.d("Touched view: ${child.id}")
}
return true
}
private fun viewTouched(child: View, ev: MotionEvent) : Boolean {
child as OnTouchListener
//onTouch() does the collision detection
return child.onTouch(child, ev)
}