Add view that can lock swipe-to-close gesture (#1140)

Slidr works by adding its own view in the hierarchy and listening to touch
events in `onInterceptTouchEvent`. Once it detects movement in the correct
direction, it returns `true` and handles all the events itself.

Adding scrollable view detection to Slidr would solve the problem, but it is
not possible and would probably have performance impact.

Fortunately Slidr does not intercept the very first event, which is
ACTION_DOWN, and it reaches scrollable view. So the scrollable view itself can
decide if it should disallow the swipe.

This also has a performance benefit over `OnScrollChangedListener` because
the listener is triggered for every scroll of every view even if the child we
are interested in did not scroll. On the other hand `on(Intercept)TouchEvent`
is triggered only when the view is touched.

There is a possibility that swipe won't be unlocked if view never receives
ACTION_UP or ACTION_CANCEL. However the docs say nothing about the probability
of this happening. Anyways, one possible solution is to post a runnable that
will unlock swipe soon after locking.
This commit is contained in:
Sergei Kozelko 2022-10-22 13:37:31 +07:00 committed by GitHub
parent d11fb884c2
commit 5e3eaafe26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 114 additions and 43 deletions

View File

@ -42,6 +42,7 @@ import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager;
import ml.docilealligator.infinityforreddit.customviews.SwipeLockScrollView;
import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent;
import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
@ -150,16 +151,16 @@ public class FullMarkdownActivity extends BaseActivity {
miscPlugin, markdownColor, spoilerBackgroundColor, null);
MarkwonAdapter markwonAdapter = MarkdownUtils.createTablesAdapter();
LinearLayoutManagerBugFixed linearLayoutManager = new MarkwonLinearLayoutManager(this, new MarkwonLinearLayoutManager.HorizontalScrollViewScrolledListener() {
LinearLayoutManagerBugFixed linearLayoutManager = new MarkwonLinearLayoutManager(this, new SwipeLockScrollView.SwipeLockInterface() {
@Override
public void onScrolledLeft() {
public void lockSwipe() {
if (mSlidrInterface != null) {
mSlidrInterface.lock();
}
}
@Override
public void onScrolledRight() {
public void unlockSwipe() {
if (mSlidrInterface != null) {
mSlidrInterface.unlock();
}

View File

@ -51,6 +51,7 @@ import ml.docilealligator.infinityforreddit.bottomsheetfragments.UrlMenuBottomSh
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager;
import ml.docilealligator.infinityforreddit.customviews.SwipeLockScrollView;
import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent;
import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils;
import ml.docilealligator.infinityforreddit.utils.JSONUtils;
@ -183,16 +184,16 @@ public class WikiActivity extends BaseActivity {
miscPlugin, markdownColor, spoilerBackgroundColor, onLinkLongClickListener);
markwonAdapter = MarkdownUtils.createTablesAdapter();
LinearLayoutManagerBugFixed linearLayoutManager = new MarkwonLinearLayoutManager(this, new MarkwonLinearLayoutManager.HorizontalScrollViewScrolledListener() {
LinearLayoutManagerBugFixed linearLayoutManager = new MarkwonLinearLayoutManager(this, new SwipeLockScrollView.SwipeLockInterface() {
@Override
public void onScrolledLeft() {
public void lockSwipe() {
if (mSlidrInterface != null) {
mSlidrInterface.lock();
}
}
@Override
public void onScrolledRight() {
public void unlockSwipe() {
if (mSlidrInterface != null) {
mSlidrInterface.unlock();
}

View File

@ -55,6 +55,7 @@ import ml.docilealligator.infinityforreddit.customviews.CustomMarkwonAdapter;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager;
import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView;
import ml.docilealligator.infinityforreddit.customviews.SwipeLockScrollView;
import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
@ -507,9 +508,9 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter<Comment
});
commentMarkdownView.setRecycledViewPool(recycledViewPool);
LinearLayoutManagerBugFixed linearLayoutManager = new MarkwonLinearLayoutManager(mActivity, new MarkwonLinearLayoutManager.HorizontalScrollViewScrolledListener() {
LinearLayoutManagerBugFixed linearLayoutManager = new MarkwonLinearLayoutManager(mActivity, new SwipeLockScrollView.SwipeLockInterface() {
@Override
public void onScrolledLeft() {
public void lockSwipe() {
if (mActivity instanceof AccountSavedThingActivity) {
((AccountSavedThingActivity) mActivity).lockSwipeRightToGoBack();
} else {
@ -518,7 +519,7 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter<Comment
}
@Override
public void onScrolledRight() {
public void unlockSwipe() {
if (mActivity instanceof AccountSavedThingActivity) {
((AccountSavedThingActivity) mActivity).unlockSwipeRightToGoBack();
} else {

View File

@ -64,6 +64,7 @@ import ml.docilealligator.infinityforreddit.customviews.CustomMarkwonAdapter;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager;
import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView;
import ml.docilealligator.infinityforreddit.customviews.SwipeLockScrollView;
import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment;
import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils;
import ml.docilealligator.infinityforreddit.post.Post;
@ -1240,14 +1241,14 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi
}
commentMarkdownView.setRecycledViewPool(recycledViewPool);
LinearLayoutManagerBugFixed linearLayoutManager = new MarkwonLinearLayoutManager(mActivity, new MarkwonLinearLayoutManager.HorizontalScrollViewScrolledListener() {
LinearLayoutManagerBugFixed linearLayoutManager = new MarkwonLinearLayoutManager(mActivity, new SwipeLockScrollView.SwipeLockInterface() {
@Override
public void onScrolledLeft() {
public void lockSwipe() {
((ViewPostDetailActivity) mActivity).lockSwipeRightToGoBack();
}
@Override
public void onScrolledRight() {
public void unlockSwipe() {
((ViewPostDetailActivity) mActivity).unlockSwipeRightToGoBack();
}
});

View File

@ -89,6 +89,7 @@ import ml.docilealligator.infinityforreddit.bottomsheetfragments.UrlMenuBottomSh
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.AspectRatioGifImageView;
import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager;
import ml.docilealligator.infinityforreddit.customviews.SwipeLockScrollView;
import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment;
import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils;
import ml.docilealligator.infinityforreddit.post.Post;
@ -1194,14 +1195,14 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler
mActivity.startActivity(intent);
});
mContentMarkdownView.setLayoutManager(new MarkwonLinearLayoutManager(mActivity, new MarkwonLinearLayoutManager.HorizontalScrollViewScrolledListener() {
mContentMarkdownView.setLayoutManager(new MarkwonLinearLayoutManager(mActivity, new SwipeLockScrollView.SwipeLockInterface() {
@Override
public void onScrolledLeft() {
public void lockSwipe() {
((ViewPostDetailActivity) mActivity).lockSwipeRightToGoBack();
}
@Override
public void onScrolledRight() {
public void unlockSwipe() {
((ViewPostDetailActivity) mActivity).unlockSwipeRightToGoBack();
}
}));

View File

@ -33,6 +33,7 @@ import ml.docilealligator.infinityforreddit.bottomsheetfragments.UrlMenuBottomSh
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager;
import ml.docilealligator.infinityforreddit.customviews.SwipeLockScrollView;
import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils;
import ml.docilealligator.infinityforreddit.utils.Utils;
@ -146,16 +147,16 @@ public class RulesRecyclerViewAdapter extends RecyclerView.Adapter<RulesRecycler
}
markwonAdapter = MarkdownUtils.createTablesAdapter();
LinearLayoutManagerBugFixed linearLayoutManager = new MarkwonLinearLayoutManager(activity,
new MarkwonLinearLayoutManager.HorizontalScrollViewScrolledListener() {
new SwipeLockScrollView.SwipeLockInterface() {
@Override
public void onScrolledLeft() {
public void lockSwipe() {
if (slidrInterface != null) {
slidrInterface.lock();
}
}
@Override
public void onScrolledRight() {
public void unlockSwipe() {
if (slidrInterface != null) {
slidrInterface.unlock();
}

View File

@ -2,39 +2,25 @@ package ml.docilealligator.infinityforreddit.customviews;
import android.content.Context;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.HorizontalScrollView;
import androidx.annotation.Nullable;
public class MarkwonLinearLayoutManager extends LinearLayoutManagerBugFixed {
public interface HorizontalScrollViewScrolledListener {
void onScrolledLeft();
void onScrolledRight();
}
private HorizontalScrollViewScrolledListener horizontalScrollViewScrolledListener;
@Nullable
private final SwipeLockScrollView.SwipeLockInterface swipeLockInterface;
public MarkwonLinearLayoutManager(Context context, HorizontalScrollViewScrolledListener horizontalScrollViewScrolledListener) {
public MarkwonLinearLayoutManager(Context context,
@Nullable SwipeLockScrollView.SwipeLockInterface swipeLockInterface) {
super(context);
this.horizontalScrollViewScrolledListener = horizontalScrollViewScrolledListener;
this.swipeLockInterface = swipeLockInterface;
}
@Override
public void addView(View child) {
super.addView(child);
if (child instanceof HorizontalScrollView) {
child.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
private int x = 0;
@Override
public void onScrollChanged() {
if (child.getScrollX() < x) {
horizontalScrollViewScrolledListener.onScrolledLeft();
} else {
horizontalScrollViewScrolledListener.onScrolledRight();
}
x = child.getScrollX();
}
});
if (child instanceof SwipeLockScrollView) {
((SwipeLockScrollView) child).setSwipeLockInterface(swipeLockInterface);
}
}
}

View File

@ -0,0 +1,78 @@
package ml.docilealligator.infinityforreddit.customviews;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;
import androidx.annotation.Nullable;
/** {@link HorizontalScrollView} that listens for touch events and locks swipes
* if it can be scrolled to the right. {@link SwipeLockInterface} must be set for
* locking to work.
*/
public class SwipeLockScrollView extends HorizontalScrollView {
@Nullable
private SwipeLockInterface swipeLockInterface = null;
private boolean locked = false;
public SwipeLockScrollView(Context context) {
super(context);
}
public SwipeLockScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SwipeLockScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setSwipeLockInterface(@Nullable SwipeLockInterface swipeLockInterface) {
this.swipeLockInterface = swipeLockInterface;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
updateSwipeLock(ev);
return super.onInterceptTouchEvent(ev);
}
@SuppressLint("ClickableViewAccessibility") // we are just listening to touch events
@Override
public boolean onTouchEvent(MotionEvent ev) {
updateSwipeLock(ev);
return super.onTouchEvent(ev);
}
/**
* Unlocks swipe if the view cannot be scrolled right anymore or if {@code ev} is
* {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL}
*/
private void updateSwipeLock(MotionEvent ev) {
if (swipeLockInterface != null) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_CANCEL ||
getScrollX() == 0) {
// calling SlidrInterface#unlock aborts the swipe
// so don't call unlock if it is already unlocked
if (locked) {
swipeLockInterface.unlockSwipe();
locked = false;
}
} else {
if (!locked) {
swipeLockInterface.lockSwipe();
locked = true;
}
}
}
}
public interface SwipeLockInterface {
void lockSwipe();
void unlockSwipe();
}
}

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<ml.docilealligator.infinityforreddit.customviews.SwipeLockScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@ -12,4 +13,4 @@
android:layout_height="wrap_content"
android:stretchColumns="*" />
</HorizontalScrollView>
</ml.docilealligator.infinityforreddit.customviews.SwipeLockScrollView>