2013-04-19 13 views
12

Próbuję zsynchronizować dwa ViewPager s, jak najwyraźniej mają sporo ludzi przede mną, a ja mam tak daleko, jak to:Synchronizacja dwa ViewPagers wykorzystujące OnPageChangeListener

private ViewPager mNavPager; 

private ViewPager mMainPager; 

private final OnPageChangeListener mNavPagerListener = new OnPageChangeListener() { 

    private boolean mNavDragging; 
    private int mScrollPosition; 

    @Override 
    public void onPageSelected(int position) { 
     mScrollPosition = position; 
    } 

    @Override 
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 
     if(mNavDragging) 
      mMainPager.scrollTo(positionOffsetPixels, 0); 
    } 

    @Override 
    public void onPageScrollStateChanged(int state) { 
     switch(state) { 
     case ViewPager.SCROLL_STATE_DRAGGING: 
     case ViewPager.SCROLL_STATE_SETTLING: 
      mNavDragging = true; 
      break; 
     case ViewPager.SCROLL_STATE_IDLE: 
      mNavDragging = false; 
      break; 
     } 
    } 
}; 

private OnPageChangeListener mMainPagerListener = new OnPageChangeListener() { 

    private boolean mMainDragging; 
    private int mScrollPosition; 

    @Override 
    public void onPageSelected(int position) { 
     mScrollPosition = position; 
    } 

    @Override 
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 
     if(mMainDragging) 
      mNavPager.scrollTo(positionOffsetPixels, 0); 
    } 

    @Override 
    public void onPageScrollStateChanged(int state) { 
     switch(state) { 
     case ViewPager.SCROLL_STATE_DRAGGING: 
     case ViewPager.SCROLL_STATE_SETTLING: 
      mMainDragging = true; 
      break; 
     case ViewPager.SCROLL_STATE_IDLE: 
      mMainDragging = false; 
      break; 
     } 
    } 
}; 

Jeśli któraś z nich jest przewijany ręcznie, drugi jest podporządkowany mu przy użyciu właściwości stanu przewijania. Działa pięknie, dopóki przedmioty nie osiągną ostatecznego położenia; w tym momencie niewolny pager błyskawicznie wraca do poprzednio wybranego elementu, tak jakby przewijanie nie miało miejsca.

Próbowałem wywoływać ViewPager#setCurrentItem(mScrolledPosition) z wieloma różnymi ograniczeniami logicznymi, ale to też nie działa, chociaż czasami pogarsza to. Czuję, że musi być coś, co można z tym zrobić, ale nie jestem pewien co.

Jak mogę ustawić, aby podrzędny pager pozostał we właściwej pozycji?

Odpowiedz

2

To robi wszystko dobrze z wyjątkiem czasem brakuje bardzo szybkich ruchów na niewolniczym widoku. Z pewnych powodów, w tym fałszywych zdarzeń przeciągania podczas fazy ustalania, powoduje prawdziwe problemy.

private ViewPager mNavPager; 

private ViewPager mMainPager; 

private final OnPageChangeListener mNavPagerListener = new OnPageChangeListener() { 

    private int mLastScrollPosition; 
    private int mLastPagePosition; 

    @Override 
    public void onPageSelected(int position) { 
     mLastPagePosition = position; 
    } 

    @Override 
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 
     if(mMainPager.isFakeDragging()) { 
      int absoluteOffsetPixels = positionOffsetPixels; 
      if(mLastPagePosition!=position) { 
       absoluteOffsetPixels += (position - mLastPagePosition) * mMainPager.getWidth(); 
       mLastPagePosition = position; 
      } 
      Log.d(TAG, "fake nav drag by " + (mLastScrollPosition - absoluteOffsetPixels)); 
      mMainPager.fakeDragBy(mLastScrollPosition - absoluteOffsetPixels); 
      mLastScrollPosition = positionOffsetPixels; 
     } 
    } 

    @Override 
    public void onPageScrollStateChanged(int state) { 
     if(!mNavPager.isFakeDragging()) { 
      switch(state) { 
      case ViewPager.SCROLL_STATE_DRAGGING: 
       if(!mMainPager.isFakeDragging()) 
        mMainPager.beginFakeDrag(); 
       break; 
      case ViewPager.SCROLL_STATE_SETTLING: 
      case ViewPager.SCROLL_STATE_IDLE: 
       if(mMainPager.isFakeDragging()) { 
        mMainPager.endFakeDrag(); 
        mLastScrollPosition = 0; 
       } 
       break; 
      } 
     } 
    } 
}; 

private OnPageChangeListener mMainPagerListener = new OnPageChangeListener() { 

    private int mLastScrollPosition; 
    private int mLastPagePosition; 

    @Override 
    public void onPageSelected(int position) { 
     mLastPagePosition = position; 
    } 

    @Override 
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 
     if(mNavPager.isFakeDragging()) { 
      int absoluteOffsetPixels = positionOffsetPixels; 
      if(mLastPagePosition!=position) { 
       absoluteOffsetPixels += (position - mLastPagePosition) * mMainPager.getWidth(); 
       mLastPagePosition = position; 
      } 
      Log.d(TAG, "fake nav drag by " + (mLastScrollPosition - absoluteOffsetPixels)); 
      mNavPager.fakeDragBy(mLastScrollPosition - absoluteOffsetPixels); 
      mLastScrollPosition = positionOffsetPixels; 
     } 
    } 

    @Override 
    public void onPageScrollStateChanged(int state) { 
     if(!mMainPager.isFakeDragging()) { 
      switch(state) { 
      case ViewPager.SCROLL_STATE_DRAGGING: 
       if(!mNavPager.isFakeDragging()) 
        mNavPager.beginFakeDrag(); 
       break; 
      case ViewPager.SCROLL_STATE_SETTLING: 
      case ViewPager.SCROLL_STATE_IDLE: 
       if(mNavPager.isFakeDragging()) { 
        mNavPager.endFakeDrag(); 
        mLastScrollPosition = 0; 
       } 
       break; 
      } 
     } 
    } 
}; 

EDIT

Teraz wierzę, że jest to niemożliwe bez pewnego dość znacznej kodu niestandardowego. Powodem jest to, że oba mają ViewPager s wewnątrz, które kontrolują przewijanie. Ponieważ przekazanie MotionEvent nie może być przekazane do VelocityTracker w tych samych względnych czasach dla każdego pagera, trackery będą od czasu do czasu osiągać różne wnioski dotyczące sposobu reagowania.

Jednak jest możliwe użycie zmodyfikowanej PagerTitleStrip uzyskać precyzyjne śledzenie na ViewPager i przesłać dotykowych zdarzenia uchwycone przez taśmy bezpośrednio do ViewPager.

The source for PagerTitleStrip is here.

Ogólnie, co należy zrobić, aby ta praca jest w następujący sposób: zastąpić mPrevText, mCurrText i mNextText z widokiem typu chcesz używać; usuń funkcje onAttachedToWindow() i onDetachedFromWindow(); usuń wywołania do PagerAdapter, które dotyczą obserwatorów zbioru danych i dodaj OnTouchListener do zmodyfikowanego paska, który to fałszywy przeciąga główny pager. Będziesz także musiał dodać zmodyfikowany pasek tytułu jako OnPageChangeListener do ViewPager, ponieważ wewnętrzne detektory nie są widoczne poza pakietem.

Czy to gigantyczny ból? Tak. Ale działa. Wkrótce opiszę to bardziej szczegółowo.

+0

skończyłaś pisać je bardziej szczegółowo? –

5

Rozwiązałem problem bez korzystania z odbiornika. Możesz więc użyć detektora do innych rzeczy i sprawisz, że kod będzie wyglądał czysto.

Znam to pytanie zadane dawno temu. Szukałem rozwiązania i sam go rozwiązałem.

ten sposób mojego kodu klienta ViewPager jest

public class CustomPager extends ViewPager { 
CustomPager mCustomPager; 
private boolean forSuper; 

public CustomPager(Context context) { 
    super(context); 
} 

public CustomPager(Context context, AttributeSet attrs) { 
    super(context, attrs); 
} 

@Override 
public boolean onInterceptTouchEvent(MotionEvent arg0) { 
    if (!forSuper) { 

     mCustomPager.forSuper(true); 
     mCustomPager.onInterceptTouchEvent(arg0); 
     mCustomPager.forSuper(false); 
    } 
    return super.onInterceptTouchEvent(arg0); 
} 

@Override 
public boolean onTouchEvent(MotionEvent arg0) { 
    if (!forSuper) { 
     mCustomPager.forSuper(true); 
     mCustomPager.onTouchEvent(arg0); 
     mCustomPager.forSuper(false); 
    } 
    return super.onTouchEvent(arg0); 
} 

public void setViewPager(CustomPager customPager) { 
    mCustomPager = customPager; 
} 

public void forSuper(boolean forSuper) { 
    this.forSuper = forSuper; 
} 

@Override 
public void setCurrentItem(int item, boolean smoothScroll) { 
    if (!forSuper) { 
     mCustomPager.forSuper(true); 
     mCustomPager.setCurrentItem(item, smoothScroll); 
     mCustomPager.forSuper(false); 
    } 
    super.setCurrentItem(item, smoothScroll); 
} 

@Override 
public void setCurrentItem(int item) { 
    if (!forSuper) { 
     mCustomPager.forSuper(true); 
     mCustomPager.setCurrentItem(item); 
     mCustomPager.forSuper(false); 
    } 
    super.setCurrentItem(item); 

} 

} 

I trzeba ustawić pagery jak to wcześniej adaptera jest ustawiona i tylko po uzyskaniu odniesienia za pomocą identyfikatora.

protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_main); 
    backPager=(CustomPager) findViewById(R.id.BackPager); 
    frontPager = (CustomPager) findViewById(R.id.frontPager); 
    frontPager.setViewPager(backPager); 
    backPager.setViewPager(frontPager); 
    backPager.setAdapter(your Adapter); 
    frontPager.setAdapter(your Adapter); 
} 

Należy ustawić ViewPagers przed przypisaniem adaptera.

+0

Wygląda to podobnie do czegoś, co próbowałem - będę musiał to zrobić, dziękuję! –

+0

Używam go w mojej aplikacji. Jakie są twoje przemyślenia na temat tego. Czy to działa poprawnie? –

+0

@Andrew Wyld Próbuję osiągnąć ten sam efekt przeglądania. Czy odniósł w tym jakiś sukces? – user2095470

12

I rozwiązać ten problem w znacznie łatwiejsze (i czystsze) sposób z użyciem OnPageChangeListener:

mViewPager1.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { 

     private int mScrollState = ViewPager.SCROLL_STATE_IDLE; 

     @Override 
     public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) { 
     if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { 
      return; 
     } 
     mViewPager2.scrollTo(mViewPager1.getScrollX(), mViewPager2.getScrollY()); 
     } 

     @Override 
     public void onPageSelected(final int position) { 

     } 

     @Override 
     public void onPageScrollStateChanged(final int state) { 
     mScrollState = state; 
     if (state == ViewPager.SCROLL_STATE_IDLE) { 
      mViewPager2.setCurrentItem(mViewPager1.getCurrentItem(), false); 
     } 
     } 
}); 

mViewPager2.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { 

     private int mScrollState = ViewPager.SCROLL_STATE_IDLE; 

     @Override 
     public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) { 
     if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { 
      return; 
     } 
     mViewPager1.scrollTo(mViewPager2.getScrollX(), mViewPager1.getScrollY()); 
     } 

     @Override 
     public void onPageSelected(final int position) { 

     } 

     @Override 
     public void onPageScrollStateChanged(final int state) { 
     mScrollState = state; 
     if (state == ViewPager.SCROLL_STATE_IDLE) { 
      mViewPager1.setCurrentItem(mViewPager2.getCurrentItem(), false); 
     } 
     } 
}); 
+0

Próbowałem czegoś takiego i stwierdziłem, że działa tylko częściowo: niewielkie różnice w taktowaniu powodowały, że prędkość niewolonego pagera czasami się przerastała. –

+0

Do tej pory nigdy nie miałem problemów. Wszystko działa sprawnie. –

+0

Powinieneś zmienić bieżący element w 'onPageScrollStateChanged()', gdy 'stan' to także' SCROLL_STATE_SETTLING'. Właśnie wtedy Android to robi. –

0

wszystkie odpowiedzi są w przybliżeniu currect w jakiejś sytuacji. Tutaj daję jeszcze jedną odpowiedź, która będzie używana do szkiełku zarówno ViewPagers jednocześnie, czy rozmiar jest taki sam, czy nie:

viewPagerBanner.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 
    private int scrollState = ViewPager.SCROLL_STATE_IDLE; 
    // Indicates that the pager is in an idle, settled state. 
    // The current page is fully in view and no animation is in progress. 

    @Override 
    public void onPageScrolled(int position, float positionOffset, 
           int positionOffsetPixels) { 
     if (scrollState == ViewPager.SCROLL_STATE_IDLE) { 
      return; 
     } 
     viewPagerTitle.scrollTo(viewPagerBanner.getScrollX()* 
           viewPagerTitle.getWidth()/ 
           viewPagerBanner.getWidth(), 0); 
     // We are not interested in Y axis position 
    } 

    @Override 
    public void onPageSelected(int position) {} 

    @Override 
    public void onPageScrollStateChanged(int state) { 
     scrollState = state; 
     if (state == ViewPager.SCROLL_STATE_IDLE) { 
      viewPagerTitle.setCurrentItem(viewPagerBanner.getCurrentItem(), false); 
     } 
    } 
}); 
1

z pomocą wszystkich innych odpowiedzi I utworzeniu klasy, więc może być łatwo wdrożony.

public class OnPageChangeListernerSync implements ViewPager.OnPageChangeListener { 

    private ViewPager master; 
    private ViewPager slave; 
    private int mScrollState = ViewPager.SCROLL_STATE_IDLE; 

    public OnPageChangeListernerSync(ViewPager master, ViewPager slave){ 
     this.master = master; 
     this.slave = slave; 
    } 

    @Override 
    public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) { 
     if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { 
      return; 
     } 
     this.slave.scrollTo(this.master.getScrollX()* 
       this.slave.getWidth()/ 
       this.master.getWidth(), 0); 
    } 

    @Override 
    public void onPageSelected(final int position) { 

    } 

    @Override 
    public void onPageScrollStateChanged(final int state) { 
     mScrollState = state; 
     if (state == ViewPager.SCROLL_STATE_IDLE) { 
      this.slave.setCurrentItem(this.master 
        .getCurrentItem(), false); 
     } 
    } 
} 

...

// In your activity 
this.upperPager.addOnPageChangeListener(new OnPageChangeListernerSync(this.upperPager, this.lowerPager)); 

this.lowerPager.addOnPageChangeListener(new OnPageChangeListernerSync(this.lowerPager, this.upperPager)); 
1

Miałem podobny problem, również potrzebne, aby połączyć moje synchronizację z animacjami w PageTransformer.

Początkowo pisał tę odpowiedź tutaj: https://stackoverflow.com/a/43638796/2867886

Ale będę go wkleić tutaj dla wygody.

Rozwiązaniem, które najlepiej sprawdziło się, było przekazanie MotionEvent w OnTouchListener między instancjami ViewPager. Próbowałem fałszywego przeciągania, ale zawsze było ono lagged i buggy - potrzebowałem gładkiego, podobnego do paralaksy efektu.

Tak, moja rada jest wprowadzenie View.OnTouchListener. Model MotionEvent musi zostać przeskalowany, aby zrekompensować różnicę szerokości.

public class SyncScrollOnTouchListener implements View.OnTouchListener { 

private final View syncedView; 

public SyncScrollOnTouchListener(@NonNull View syncedView) { 
    this.syncedView = syncedView; 
} 

@Override 
public boolean onTouch(View view, MotionEvent motionEvent) { 
    MotionEvent syncEvent = MotionEvent.obtain(motionEvent); 
    float width1 = view.getWidth(); 
    float width2 = syncedView.getWidth(); 

    //sync motion of two view pagers by simulating a touch event 
    //offset by its X position, and scaled by width ratio 
    syncEvent.setLocation(syncedView.getX() + motionEvent.getX() * width2/width1, 
      motionEvent.getY()); 
    syncedView.onTouchEvent(syncEvent); 
    return false; 
} 
} 

Następnie ustawić go do ViewPager

sourcePager.setOnTouchListener(new SyncScrollOnTouchListener(targetPager)); 

Zauważ, że to rozwiązanie będzie działać tylko wtedy, gdy oba pagery mają tę samą orientację. Jeśli potrzebujesz go do pracy w różnych orientacjach - zmień współrzędną syncEvent zamiast X.

Jest jeszcze jeden problem, który musimy wziąć pod uwagę - minimalna prędkość rzutu i odległość, która może spowodować, że tylko jeden pagarek zmieni stronę.

To może być łatwo ustalony przez dodanie OnPageChangeListener do naszego pager

sourcePager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 
     @Override 
     public void onPageScrolled(int position, float positionOffset, 
            int positionOffsetPixels) { 
      //no-op 
     } 

     @Override 
     public void onPageSelected(int position) { 
      targetPager.setCurrentItem(position, true); 
     } 

     @Override 
     public void onPageScrollStateChanged(int state) { 
      //no-op 
     } 
    }); 
+0

niesamowite! Dziękuję Ci! :) i o wiele więcej niż rozwiązanie z OnPageChangeListener – Katharina