summaryrefslogtreecommitdiff
path: root/app/src/main/java/de/danoeh/antennapod/view/NestedScrollableHost.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod/view/NestedScrollableHost.java')
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/NestedScrollableHost.java165
1 files changed, 127 insertions, 38 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/view/NestedScrollableHost.java b/app/src/main/java/de/danoeh/antennapod/view/NestedScrollableHost.java
index 0e1846f1c..ff52df71f 100644
--- a/app/src/main/java/de/danoeh/antennapod/view/NestedScrollableHost.java
+++ b/app/src/main/java/de/danoeh/antennapod/view/NestedScrollableHost.java
@@ -20,92 +20,181 @@
package de.danoeh.antennapod.view;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.ViewParent;
+import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.viewpager2.widget.ViewPager2;
+import de.danoeh.antennapod.R;
+
import static androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL;
import static androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL;
+
/**
* Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
* where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
* ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
*
- * <p>This solution has limitations when using multiple levels of nested scrollable elements
+ * This solution has limitations when using multiple levels of nested scrollable elements
* (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
- */
-class NestedScrollableHost extends FrameLayout {
+ */ // KhaledAlharthi/NestedScrollableHost.java
+public class NestedScrollableHost extends FrameLayout {
+
+ private ViewPager2 parentViewPager;
+ private int touchSlop = 0;
+ private float initialX = 0f;
+ private float initialY = 0f;
+ private int preferVertical = 1;
+ private int preferHorizontal = 1;
+ private int scrollDirection = 0;
public NestedScrollableHost(@NonNull Context context) {
super(context);
- touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ init(context);
}
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
+ init(context);
+ setAttributes(context, attrs);
+ }
+
+ public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context);
+ setAttributes(context, attrs);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context);
+ setAttributes(context, attrs);
+ }
+
+ private void setAttributes(@NonNull Context context, @Nullable AttributeSet attrs) {
+ TypedArray a = context.getTheme().obtainStyledAttributes(
+ attrs,
+ R.styleable.NestedScrollableHost,
+ 0, 0);
+
+ try {
+ preferHorizontal = a.getInteger(R.styleable.NestedScrollableHost_preferHorizontal, 1);
+ preferVertical = a.getInteger(R.styleable.NestedScrollableHost_preferVertical, 1);
+ scrollDirection = a.getInteger(R.styleable.NestedScrollableHost_scrollDirection, 0);
+ } finally {
+ a.recycle();
+ }
+
+ }
+
+ private void init(Context context) {
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+
+
+ getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ View v = (View) getParent();
+ while (v != null && !(v instanceof ViewPager2) || isntSameDirection(v)) {
+ v = (View) v.getParent();
+ }
+ parentViewPager = (ViewPager2) v;
+
+ getViewTreeObserver().removeOnPreDrawListener(this);
+ return false;
+ }
+ });
}
- private int touchSlop;
- private float initialX = 0f;
- private float initialY = 0f;
+ private Boolean isntSameDirection(View v) {
+ int orientation = 0;
+ switch (scrollDirection) {
+ default:
+ case 0:
+ return false;
+ case 1:
+ orientation = ORIENTATION_VERTICAL;
+ break;
+ case 2:
+ orientation = ORIENTATION_HORIZONTAL;
+ break;
+ }
+ return ((v instanceof ViewPager2) && ((ViewPager2) v).getOrientation() != orientation);
+ }
+
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ handleInterceptTouchEvent(ev);
+ return super.onInterceptTouchEvent(ev);
+ }
- private ViewPager2 getParentViewPager() {
- View v = (View) getParent();
- while (v != null && !(v instanceof ViewPager2)) {
- v = (View) v.getParent();
+
+ private boolean canChildScroll(int orientation, float delta) {
+ int direction = (int) -delta;
+ View child = getChildAt(0);
+ if (orientation == 0) {
+ return child.canScrollHorizontally(direction);
+ } else if (orientation == 1) {
+ return child.canScrollVertically(direction);
+ } else {
+ throw new IllegalArgumentException();
}
- return v == null ? null : (ViewPager2) v;
}
- public boolean onInterceptTouchEvent(MotionEvent e) {
- ViewPager2 parentViewPager = getParentViewPager();
+ private void handleInterceptTouchEvent(MotionEvent e) {
if (parentViewPager == null) {
- return super.onInterceptTouchEvent(e);
+ return;
}
-
- ViewParent parent = getParent();
int orientation = parentViewPager.getOrientation();
+ boolean preferedDirection = preferHorizontal + preferVertical > 2;
+
+ // Early return if child can't scroll in same direction as parent and theres no prefered scroll direction
+ if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f) && !preferedDirection) {
+ return;
+ }
+
if (e.getAction() == MotionEvent.ACTION_DOWN) {
initialX = e.getX();
initialY = e.getY();
- parent.requestDisallowInterceptTouchEvent(true);
+ getParent().requestDisallowInterceptTouchEvent(true);
} else if (e.getAction() == MotionEvent.ACTION_MOVE) {
- int dx = (int) (e.getX() - initialX);
- int dy = (int) (e.getY() - initialY);
- boolean isVpHorizontal = orientation == ORIENTATION_HORIZONTAL;
+ float dx = e.getX() - initialX;
+ float dy = e.getY() - initialY;
+ boolean isVpHorizontal = orientation == ViewPager2.ORIENTATION_HORIZONTAL;
// assuming ViewPager2 touch-slop is 2x touch-slop of child
- float scaledDx = Math.abs(dx) * (isVpHorizontal ? .5f : 1f);
- float scaledDy = Math.abs(dy) * (isVpHorizontal ? 1f : .5f);
+ float scaledDx = Math.abs(dx) * (isVpHorizontal ? 1f : 0.5f) * preferHorizontal;
+ float scaledDy = Math.abs(dy) * (isVpHorizontal ? 0.5f : 1f) * preferVertical;
if (scaledDx > touchSlop || scaledDy > touchSlop) {
- int value = isVpHorizontal ? dy : dx;
if (isVpHorizontal == (scaledDy > scaledDx)) {
- // Gesture is perpendicular
- orientation = orientation == ORIENTATION_VERTICAL
- ? ORIENTATION_HORIZONTAL : ORIENTATION_VERTICAL;
- value = isVpHorizontal ? dy : dx;
- }
-
- int direction = (int) -Math.copySign(1, value);
- View child = getChildAt(0);
- if (orientation == ORIENTATION_HORIZONTAL) {
- parent.requestDisallowInterceptTouchEvent(child.canScrollHorizontally(direction));
+ // Gesture is perpendicular, allow all parents to intercept
+ getParent().requestDisallowInterceptTouchEvent(preferedDirection);
} else {
- parent.requestDisallowInterceptTouchEvent(child.canScrollVertically(direction));
+ // Gesture is parallel, query child if movement in that direction is possible
+ if (canChildScroll(orientation, isVpHorizontal ? dx : dy)) {
+ // Child can scroll, disallow all parents to intercept
+ getParent().requestDisallowInterceptTouchEvent(true);
+ } else {
+ // Child cannot scroll, allow all parents to intercept
+ getParent().requestDisallowInterceptTouchEvent(false);
+ }
}
}
- }
- return super.onInterceptTouchEvent(e);
+ }
}
}