DiscreteScrollView icon indicating copy to clipboard operation
DiscreteScrollView copied to clipboard

Dragging more than one page at a time

Open pflammertsma opened this issue 6 years ago • 8 comments

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()?

pflammertsma avatar Dec 13 '17 19:12 pflammertsma

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.

yarolegovich avatar Feb 05 '18 18:02 yarolegovich

@pflammertsma Have you managed to find a way around the issue?

hendrep avatar Apr 20 '18 07:04 hendrep

No, I'm afraid not.

pflammertsma avatar Apr 20 '18 12:04 pflammertsma

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;
        }
    }

bernaferrari avatar May 13 '18 21:05 bernaferrari

+1

MRezaNasirloo avatar Aug 15 '18 09:08 MRezaNasirloo

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.

danielcrt avatar Oct 04 '18 13:10 danielcrt

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);
    }
} 

balamurugann avatar Jun 11 '20 02:06 balamurugann

@bernaferrari and @balamurugann Thank you so much for this. Works like i wanted.

dhruvitservice avatar Aug 17 '22 05:08 dhruvitservice