Differentiating between user scroll and programatic page change in ViewPager

后端 未结 3 1924
野的像风
野的像风 2021-01-30 05:25

I have a android.support.v4.view.ViewPager in my application and I would like to differentiate between a programmatically-initiated smooth scroll and a user-initiat

相关标签:
3条回答
  • 2021-01-30 05:54

    I have based myself on the answer mark as correct and in the comments below.

    First I analyze how the complete listener behave:

    USER
    onPageScrollStateChanged:        1             SCROLL_STATE_DRAGGING
    onPageScrollStateChanged:        2             SCROLL_STATE_SETTLING
    onPageSelected:              SELECTION     
    onPageScrollStateChanged:        0             SCROLL_STATE_IDLE
    
    PROGRAMATIC
    onPageScrollStateChanged:        2             SCROLL_STATE_SETTLING
    onPageSelected:              SELECTION
    onPageScrollStateChanged:        0             SCROLL_STATE_IDLE  
    

    Findings:

    • As you can see in both cases the events end when the onPageScrollStateChanged move to SCROLL_STATE_IDLE, this means idle is the end of the cycle

    • The user event is SCROLL_STATE_DRAGGING and then SCROLL_STATE_SETTLING which are 2 states different than only 1 state for the programmatic event SCROLL_STATE_SETTLING

    • onPageSelected happens before the end of the cycle, but after we are able to determinate if the change was triggered by the user or programmatically, so whatever happened before will tell us in that point if it was the user or not

    Solution:

    So I use a List<Integer> that is reset every time the cycle ends, and to be able to know if the user triggered the event in the onPageSelected method I check the size of the List. If the size is 2 that means the user scroll the pager.

    abstract class PagerListenerActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener {
    
        private static final int USER_SCROLL = 2;
        private List<Integer> validator = new ArrayList<>();
    
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        }
    
        @Override
        public void onPageSelected(int position) {
            if (validator.size() == USER_SCROLL) {
                userScroll(position);
            }
        }
    
        @Override
        public void onPageScrollStateChanged(int state) {
            validator.add(state);
            if (ViewPager.SCROLL_STATE_IDLE == state) {
                validator.clear();
            }
    
        }
    
        protected abstract void userScroll(int position);
    }
    

    Now, this class can conveniently be inherited by another that need it.

    0 讨论(0)
  • 2021-01-30 05:57

    You are right about using ViewPager.OnPageChangeListener:

    @Override
    public void onPageSelected(int arg0) {
        // programmatically-initiated                           
    }
    
    @Override
    public void onPageScrolled(int arg0, float arg1, int arg2) {
    
    }
    
    @Override
    public void onPageScrollStateChanged(int arg0) {
        // user-initiated touch scroll      
    }
    

    Alternatively, you can use boolean flags to differentiate between programmatically-initiated smooth scroll and a user-initiated touch scroll. For example, if you use setCurrentItem(int item) to programmatically change the page, try:

    boolean progChange = false;
    
    ....
    ....
    ....
    
    progChange = true;
    setCurrentItem(somePageId);     // Set progChange = true every time
    
    ....
    ....
    ....
    

    Inside your ViewPager.OnPageChangeListener:

    @Override
    public void onPageSelected(int arg0) {
    
    }
    
    @Override
    public void onPageScrolled(int arg0, float arg1, int arg2) {
        if (progChange) {
            // programmatically-initiated
        } else {
            // user-initiated touch scroll
        }
    
        // Set progChange to false;
        progChange = false;                 
    }
    
    @Override
    public void onPageScrollStateChanged(int arg0) {
    
    }
    
    0 讨论(0)
  • 2021-01-30 06:04

    OK, so it turns out that I was right about the answer lying in ViewPager.onPageChangeListener. In particular it lies in using onPageScrollStateChanged(int state). Essentially there are three states that a page in a ViewPager can be in:

    1. Dragging: Indicates that the pager is currently being dragged by the user.
    2. Idle: Indicates that the pager is in an idle, settled state.
    3. Settling: Indicates that the pager is in the process of settling to a final position.

    So the dragging state only occurs when the current page is being physically dragged by the user. Thus when the user has swiped a page the states occur in the following order: Dragging -> Settling -> Idle. Now, the onPageSelected(int position) method is called between the "Settling" and "Idle" states. Thus, in order to determine whether or not a page change was caused by a user scroll one just needs to check that the previous state was "dragging" and that the current state is "Settling". You can then keep a boolean variable to track whether or not the page change was user initiated or not and check it in your onPageSelected(int position) method.

    Here is my onPageScrollStateChanged method

    public void onPageScrollStateChanged(int state) 
    {
        if (previousState == ViewPager.SCROLL_STATE_DRAGGING
                && state == ViewPager.SCROLL_STATE_SETTLING)
            userScrollChange = true;
    
        else if (previousState == ViewPager.SCROLL_STATE_SETTLING
                && state == ViewPager.SCROLL_STATE_IDLE)
            userScrollChange = false;
    
        previousState = state;
    }
    

    The if and else if statements need not be so explicit but I did so for clarity.

    0 讨论(0)
提交回复
热议问题