DiscreteScrollView
DiscreteScrollView copied to clipboard
Dragging more than one page at a time
It appears that DiscreteScrollLayoutManager.calculateAllowedScrollIn()
restricts the amount of pages that may be scrolled in such a way that the user cannot drag beyond one page to the left or right of the current selection.
It would seem to me that by enabling setSlideOnFling(true)
, the user should be able drag for instance from page 0 to page 3 in a single gesture (without flinging).
What are your thoughts on this? With some debugging I found that perhaps the place to look is either in the aforementioned method, or perhaps after it's invoked in DiscreteScrollLayoutManager.scrollBy()
?
I'm not going to implement this , but if someone decides to make a PR, I'll be glad to review the code and merge it.
@pflammertsma Have you managed to find a way around the issue?
No, I'm afraid not.
I did some progress. You can swipe, everything works fine. The only problem (for now) is how to correctly calculate getHowMuchIsLeftToScroll, which determines what happens when you lift the finger out.
I mapped four cases: When scrollToChangeCurrent = 324 and scrolled is (323, 325, -323, -325) or (647, 649, -647, -649).
protected int calculateAllowedScrollIn(Direction direction) {
if (pendingScroll != 0) {
return Math.abs(pendingScroll);
}
int allowedScroll;
boolean isBoundReached;
boolean isScrollDirectionAsBefore = direction.applyTo(scrolled) > 0;
if (direction == Direction.START && currentPosition == 0) {
//We can scroll to the left when currentPosition == 0 only if we scrolled to the right before
isBoundReached = scrolled == 0;
allowedScroll = isBoundReached ? 0 : Math.abs(scrolled);
} else if (direction == Direction.END && currentPosition == recyclerViewProxy.getItemCount() - 1) {
//We can scroll to the right when currentPosition == last only if we scrolled to the left before
isBoundReached = scrolled == 0;
allowedScroll = isBoundReached ? 0 : Math.abs(scrolled);
} else {
isBoundReached = false;
if (isInBounds(currentPosition)) {
int diff;
if (direction == Direction.START) {
diff = currentPosition;
} else {
// The -1 is to avoid the maximum.
diff = recyclerViewProxy.getItemCount() - currentPosition - 1;
}
allowedScroll = isScrollDirectionAsBefore ?
scrollToChangeCurrent * diff - Math.abs(scrolled) :
scrollToChangeCurrent * diff + Math.abs(scrolled);
} else {
// Same as before
allowedScroll = isScrollDirectionAsBefore ?
scrollToChangeCurrent - Math.abs(scrolled) :
scrollToChangeCurrent + Math.abs(scrolled);
}
}
scrollStateListener.onIsBoundReachedFlagChange(isBoundReached);
return allowedScroll;
}
// THIS WORKS SOMETIMES, SOMETIMES IT DOESN'T. I HAVE NO IDEA WHY.
private int getHowMuchIsLeftToScroll(int dx) {
if (scrolled <= scrollToChangeCurrent){
return Direction.fromDelta(dx).applyTo(scrollToChangeCurrent - Math.abs(scrolled));
} else if (Math.abs(scrolled) % scrollToChangeCurrent == 0){
return 0;
}
int div = Math.abs(scrolled) % scrollToChangeCurrent;
int mult = (scrolled > 0) ? 1 : -1;
if (div < scrollToChangeCurrent / 2) {
return -mult*div;
} else {
return mult*(scrollToChangeCurrent - div);
}
}
/**
* @return true if scroll is ended and we don't need to settle items
*/
private boolean onScrollEnd() {
if (pendingPosition != NO_POSITION) {
currentPosition = pendingPosition;
pendingPosition = NO_POSITION;
scrolled = 0;
}
int round = Math.round(scrolled/scrollToChangeCurrent);
if (round >= recyclerViewProxy.getItemCount()){
round -= 1;
}
Direction scrollDirection = Direction.fromDelta(scrolled);
if (Math.abs(scrolled) == scrollToChangeCurrent * round) {
currentPosition += scrollDirection.applyTo(round);
scrolled = 0;
}
if (false){ // Previous implementation
if (Math.abs(scrolled) == scrollToChangeCurrent) {
currentPosition += scrollDirection.applyTo(1);
scrolled = 0;
}
}
if (isAnotherItemCloserThanCurrent2()) {
pendingScroll = getHowMuchIsLeftToScroll2(scrolled);
} else {
pendingScroll = -scrolled;
}
if (pendingScroll == 0) {
return true;
} else {
startSmoothPendingScroll();
return false;
}
}
+1
If the swipe move is wider than the recycler view's height/width (depends on orientation) all recycler view's children are detached and all views will disappear. I think you also have to adjust the endBound variable from: protected void fill(RecyclerView.Recycler recycler) so you can cache all "pending "views". I will come with an edit if I figure out how to deal with this.
Thanks for your code @bernaferrari
I added small changes in your code and its working for dragging more than one page,
/** * @return true if scroll is ended and we don't need to settle items */ private boolean onScrollEnd() { if (pendingPosition != NO_POSITION) { currentPosition = pendingPosition; pendingPosition = NO_POSITION; scrolled = 0; } int round = Math.round(scrolled/scrollToChangeCurrent); round = round == -1? 1: round; if (round >= recyclerViewProxy.getItemCount()){ round -= 1; }
Direction scrollDirection = Direction.fromDelta(scrolled);
if (Math.abs(scrolled) == scrollToChangeCurrent * round) {
currentPosition += scrollDirection.applyTo(round);
scrolled = 0;
}
if (isAnotherItemCloserThanCurrent()) {
pendingScroll = getHowMuchIsLeftToScroll(scrolled);
// Direction direction = Direction.fromDelta(scrolled); // currentPosition += direction.applyTo(1); // scrolled = -getHowMuchIsLeftToScroll(scrolled); } else { pendingScroll = -scrolled; } if (pendingScroll == 0) { return true; } else { boolean isScrollingThroughMultiplePositions = Math.abs(scrolled) > scrollToChangeCurrent; if (isScrollingThroughMultiplePositions) { int scrolledPositions = scrolled / scrollToChangeCurrent; currentPosition += scrolledPositions; scrolled -= scrolledPositions * scrollToChangeCurrent; Log.d("CURRENT_POSITION", currentPosition + ""); }
startSmoothPendingScroll();
return false;
}
}
private int getHowMuchIsLeftToScroll(int dx) { // return Direction.fromDelta(dx).applyTo(scrollToChangeCurrent - Math.abs(scrolled)); // if (scrolled <= scrollToChangeCurrent){ // Log.d("SCROLLBALA", "scrolled <= scrollToChangeCurrent ==>" +Direction.fromDelta(dx).applyTo(scrollToChangeCurrent - Math.abs(scrolled))); // return Direction.fromDelta(dx).applyTo(scrollToChangeCurrent - Math.abs(scrolled)); // } // // else if (Math.abs(scrolled) % scrollToChangeCurrent == 0){ // Log.d("SCROLLBALA", "Math.abs(scrolled) % scrollToChangeCurrent == 0"); // // return 0; // }
int div = Math.abs(scrolled) % scrollToChangeCurrent;
int mult = (scrolled > 0) ? 1 : -1;
if (div < scrollToChangeCurrent / 2) {
return -mult*div;
} else {
return mult*(scrollToChangeCurrent - div);
}
}
@bernaferrari and @balamurugann Thank you so much for this. Works like i wanted.