问题
I have a RecyclerView
with a LinearLayoutManager
in orientation HORIZONTAL
. Each item in it can have interactive elements (including vertical ScrollView
s). Is there a way to easily make the RecyclerView
ignore any attempts by the user to scroll or fling the RecyclerView
horizontally without intercepting touch events to the children?
I am programmatically controlling the scroll of the RecyclerView
which works great until the user flings it.
I have tried something very simple where the idea is when I call smoothScrollToPosition in response to some event, I enable scrolling and disable touch events until the scroll settles. Like this:
private class NoScrollHorizontalLayoutManager extends LinearLayoutManager {
ScrollingTouchInterceptor interceptor = new ScrollingTouchInterceptor();
protected boolean canScroll;
public NoScrollHorizontalLayoutManager(Context ctx) {
super(ctx, LinearLayoutManager.HORIZONTAL, false);
}
public RecyclerView.OnItemTouchListener getInterceptor() {
return interceptor;
}
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
canScroll = true;
super.smoothScrollToPosition(recyclerView, state, position);
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if(state == RecyclerView.SCROLL_STATE_IDLE) {
canScroll = false;
}
}
@Override
public boolean canScrollHorizontally() {
return canScroll;
}
public class ScrollingTouchInterceptor implements RecyclerView.OnItemTouchListener {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
return canScrollHorizontally();
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
}
And use it like this..
NoScrollHorizontalLayoutManager layout = new NoScrollHorizontalLayoutManager(context);
recycler.setLayoutManager(layout);
recycler.addOnItemTouchListener(layout.getInterceptor());
which actually almost works... but I can still screw up the programmatic scroll by tapping the screen when the smooth scroll is in motion. clearly I'm missing something obvious or there is a much smarter way to do this.
UPDATE the non-RecyclerView solution was found here: How do disable paging by swiping with finger in ViewPager but still be able to swipe programmatically?
/**
* ViewPager that doesn't allow swiping.
*/
public class NonSwipeableViewPager extends ViewPager {
public NonSwipeableViewPager(Context context) {
super(context);
}
public NonSwipeableViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// Never allow swiping to switch between pages
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Never allow swiping to switch between pages
return false;
}
}
回答1:
Overriding onInterceptTouchEvent you avoid the stop on click behaviour.
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
return false;
}
回答2:
First thing, I don't think this is achievable non-programmatically.
Way 1: Simple - Disable the other view based on the touch.
If user touch the parent disable the child views scroll. vice versa as well
Piece of code to do this
recyclerView.setOnTouchListener(new View.OnTouchListener()
{
public boolean onTouch(View p_v, MotionEvent p_event)
{
yourChildView.getParent().requestDisallowInterceptTouchEvent(false);
return false;
}
});
Way 2: Since you are having small issues in your implementation. If you post it somebody can fix that as well.
回答3:
Here's a solution for horizontal scrolling that allows you to turn off scrolling, but still enable it via calling smoothScrollToPosition. It also lets the user interact with child views within the recycler view.
I found that the other solutions which enabled scrolling temporarily to be able to call smoothScrollToPosition had the side effect of letting the user interrupt scrolling, even when disabling touch events or adding another view on top of the recyclerview.
The key is to getting smoothScrollToPosition to work is to override LinearSmoothScroller.calculateDxToMakeVisible and remove the check to canScrollHorizontally()
class NoScrollHorizontalLayoutManager(context: Context) : LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) {
override fun canScrollHorizontally(): Boolean {
return false
}
override fun smoothScrollToPosition(recyclerView: RecyclerView, state: RecyclerView.State?, position: Int) {
val linearSmoothScroller = ForceHorizontalLinearSmoothScroller(recyclerView.context)
linearSmoothScroller.targetPosition = position
startSmoothScroll(linearSmoothScroller)
}
}
class ForceHorizontalLinearSmoothScroller(context: Context) : LinearSmoothScroller(context) {
override fun calculateDxToMakeVisible(view: android.view.View, snapPreference: Int): Int {
val layoutManager = layoutManager
if (layoutManager == null) {
return 0
}
val params = view.layoutParams as RecyclerView.LayoutParams
val left = layoutManager.getDecoratedLeft(view) - params.leftMargin
val right = layoutManager.getDecoratedRight(view) + params.rightMargin
val start = layoutManager.paddingLeft
val end = layoutManager.width - layoutManager.paddingRight
return calculateDtToFit(left, right, start, end, snapPreference)
}
}
EDIT:
I found that the user could still stop scrolling when tapping on the recycle view while the scroll was in progress. The fix was to override the RecycleView.onInterceptTouchEvent
and prevent events when the smooth scroll was in progress or when the scroll state was set to SCROLL_STATE_SETTLING
. (Using RecycleView.addOnItemTouchListener
for this wont work because the RecycleView stops scrolling then the listener returns true in RecyleView.onInterceptTouchEvent
.)
class NoScrollRecyclerView : RecyclerView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
override fun onInterceptTouchEvent(e : MotionEvent) : Boolean {
if (layoutManager?.isSmoothScrolling() == true || scrollState == SCROLL_STATE_SETTLING) {
return true
}
return super.onInterceptTouchEvent(e)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(e :MotionEvent) : Boolean {
if (layoutManager?.isSmoothScrolling() == true || scrollState == SCROLL_STATE_SETTLING) {
return true
}
return super.onTouchEvent(e)
}
}
回答4:
recyclerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
回答5:
This gives the effect of continuity.
recyclerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
recyclerView.smoothScrollToPosition(position);
return true;
} else if (action == MotionEvent.ACTION_UP) {
recyclerView.smoothScrollToPosition(position);
return true;
}
return false;
}
});
回答6:
android:nestedScrollingEnabled="false"
Do the above one in the xml file , in the RecyclerView TAG.
回答7:
recyclerView.setNestedScrollingEnabled(false);
For more info see documentation.
来源:https://stackoverflow.com/questions/32806263/how-to-disable-scrolling-of-recyclerview-except-programmatically