Skip to content

Commit bc965cf

Browse files
authored
Merge pull request #26 from FastComments/nested-view-pager
Improved support for nested view pagers
2 parents 2a668a1 + 7294a88 commit bc965cf

File tree

2 files changed

+157
-4
lines changed

2 files changed

+157
-4
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package com.fastcomments.sdk;
2+
3+
import android.content.Context;
4+
import android.util.AttributeSet;
5+
import android.view.MotionEvent;
6+
import android.view.View;
7+
import android.view.ViewConfiguration;
8+
import android.widget.FrameLayout;
9+
10+
import androidx.annotation.NonNull;
11+
import androidx.annotation.Nullable;
12+
import androidx.viewpager2.widget.ViewPager2;
13+
14+
/**
15+
* Layout that prevents horizontal touch event conflicts when nesting a horizontal ViewPager2
16+
* inside another horizontal ViewPager2. This specifically handles the case where image galleries
17+
* need to work properly when the feed is embedded in a parent ViewPager2.
18+
*
19+
* Key behaviors:
20+
* - Only handles horizontal scroll conflicts (preserves vertical scrolling)
21+
* - Allows child ViewPager2 to consume horizontal swipes when it can scroll
22+
* - Delegates to parent when child reaches horizontal bounds
23+
* - Never interferes with vertical scrolling of the feed
24+
*
25+
* Based on Google's NestedScrollableHost but optimized for horizontal-only conflicts.
26+
*/
27+
public class NestedScrollableHost extends FrameLayout {
28+
private int touchSlop;
29+
private float initialX = 0f;
30+
private float initialY = 0f;
31+
32+
public NestedScrollableHost(@NonNull Context context) {
33+
super(context);
34+
init();
35+
}
36+
37+
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs) {
38+
super(context, attrs);
39+
init();
40+
}
41+
42+
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
43+
super(context, attrs, defStyleAttr);
44+
init();
45+
}
46+
47+
private void init() {
48+
touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
49+
}
50+
51+
@Override
52+
public boolean onInterceptTouchEvent(MotionEvent ev) {
53+
handleInterceptTouchEvent(ev);
54+
return super.onInterceptTouchEvent(ev);
55+
}
56+
57+
private void handleInterceptTouchEvent(MotionEvent ev) {
58+
switch (ev.getAction()) {
59+
case MotionEvent.ACTION_DOWN:
60+
initialX = ev.getX();
61+
initialY = ev.getY();
62+
// Don't block parent initially - let them know we might want to handle horizontals
63+
break;
64+
65+
case MotionEvent.ACTION_MOVE:
66+
float deltaX = Math.abs(ev.getX() - initialX);
67+
float deltaY = Math.abs(ev.getY() - initialY);
68+
69+
// Only handle horizontal scroll conflicts when parent is horizontal ViewPager2
70+
if (isParentHorizontalViewPager() && deltaX > touchSlop && deltaX > deltaY) {
71+
// This is primarily a horizontal swipe
72+
boolean swipeLeft = (ev.getX() - initialX) < 0;
73+
74+
// Check if our child ViewPager2 can scroll in the swipe direction
75+
if (canChildScrollHorizontally(swipeLeft)) {
76+
// Child can handle this horizontal scroll, block parent
77+
getParent().requestDisallowInterceptTouchEvent(true);
78+
} else {
79+
// Child can't scroll further horizontally, let parent handle
80+
getParent().requestDisallowInterceptTouchEvent(false);
81+
}
82+
} else if (deltaY > touchSlop && deltaY > deltaX) {
83+
// This is primarily a vertical swipe - always let parent handle
84+
// (for feed scrolling)
85+
getParent().requestDisallowInterceptTouchEvent(false);
86+
}
87+
break;
88+
89+
case MotionEvent.ACTION_UP:
90+
case MotionEvent.ACTION_CANCEL:
91+
// Reset state
92+
getParent().requestDisallowInterceptTouchEvent(false);
93+
break;
94+
}
95+
}
96+
97+
/**
98+
* Check if the child view can scroll horizontally in the specified direction
99+
* @param swipeLeft true if swiping left, false if swiping right
100+
* @return true if the child can scroll in the specified direction
101+
*/
102+
private boolean canChildScrollHorizontally(boolean swipeLeft) {
103+
View child = getChildAt(0);
104+
if (child == null) {
105+
return false;
106+
}
107+
108+
if (swipeLeft) {
109+
// Swiping left means scrolling to show next item (positive direction)
110+
return child.canScrollHorizontally(1);
111+
} else {
112+
// Swiping right means scrolling to show previous item (negative direction)
113+
return child.canScrollHorizontally(-1);
114+
}
115+
}
116+
117+
/**
118+
* Check if there's a parent ViewPager2 that's oriented horizontally
119+
* We only need to handle conflicts with horizontal parent ViewPagers
120+
*/
121+
private boolean isParentHorizontalViewPager() {
122+
ViewPager2 parentVp = findParentViewPager2();
123+
if (parentVp != null) {
124+
return parentVp.getOrientation() == ViewPager2.ORIENTATION_HORIZONTAL;
125+
}
126+
// If no parent ViewPager2, no conflict to handle
127+
return false;
128+
}
129+
130+
/**
131+
* Find the parent ViewPager2 in the view hierarchy
132+
*/
133+
private ViewPager2 findParentViewPager2() {
134+
View parent = (View) getParent();
135+
while (parent != null) {
136+
if (parent instanceof ViewPager2) {
137+
return (ViewPager2) parent;
138+
}
139+
if (parent.getParent() instanceof View) {
140+
parent = (View) parent.getParent();
141+
} else {
142+
break;
143+
}
144+
}
145+
return null;
146+
}
147+
}

libraries/sdk/src/main/res/layout/feed_post_item_multi_image.xml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,17 @@
152152
</LinearLayout>
153153
</LinearLayout>
154154

155-
<!-- ViewPager for 4+ images -->
156-
<androidx.viewpager2.widget.ViewPager2
157-
android:id="@+id/imageViewPager"
155+
<!-- ViewPager for 2+ images wrapped in NestedScrollableHost -->
156+
<com.fastcomments.sdk.NestedScrollableHost
158157
android:layout_width="match_parent"
159-
android:layout_height="300dp" />
158+
android:layout_height="300dp">
159+
160+
<androidx.viewpager2.widget.ViewPager2
161+
android:id="@+id/imageViewPager"
162+
android:layout_width="match_parent"
163+
android:layout_height="match_parent" />
164+
165+
</com.fastcomments.sdk.NestedScrollableHost>
160166

161167
<!-- Image Counter (smaller size) -->
162168
<TextView

0 commit comments

Comments
 (0)