Synchronizuj pozycje przewijania ScrollView - Android

95

Mam 2 ScrollViews w moim układzie Androida. Jak mogę zsynchronizować ich pozycje przewijania?

Tim
źródło

Odpowiedzi:

289

W ScrollView istnieje metoda ...

protected void onScrollChanged(int x, int y, int oldx, int oldy)

Niestety Google nigdy nie pomyślał, że będziemy musieli uzyskać do niego dostęp, dlatego zabezpieczyli go i nie dodali hooka „setOnScrollChangedListener”. Więc będziemy musieli to zrobić dla siebie.

Najpierw potrzebujemy interfejsu.

package com.test;

public interface ScrollViewListener {

    void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy);

}

Następnie musimy zastąpić klasę ScrollView, aby zapewnić punkt zaczepienia ScrollViewListener.

package com.test;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;

public class ObservableScrollView extends ScrollView {

    private ScrollViewListener scrollViewListener = null;

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

    public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

    public void setScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldx, int oldy) {
        super.onScrollChanged(x, y, oldx, oldy);
        if(scrollViewListener != null) {
            scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
        }
    }

}

I powinniśmy określić tę nową klasę ObservableScrollView w układzie zamiast istniejących tagów ScrollView.

<com.test.ObservableScrollView
    android:id="@+id/scrollview1"
    ... >

    ...

</com.test.ObservableScrollView>

Na koniec zebraliśmy to wszystko razem w klasie Layout.

package com.test;

import android.app.Activity;
import android.os.Bundle;

public class Q3948934 extends Activity implements ScrollViewListener {

    private ObservableScrollView scrollView1 = null;
    private ObservableScrollView scrollView2 = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.q3948934);

        scrollView1 = (ObservableScrollView) findViewById(R.id.scrollview1);
        scrollView1.setScrollViewListener(this);
        scrollView2 = (ObservableScrollView) findViewById(R.id.scrollview2);
        scrollView2.setScrollViewListener(this);
    }

    public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
        if(scrollView == scrollView1) {
            scrollView2.scrollTo(x, y);
        } else if(scrollView == scrollView2) {
            scrollView1.scrollTo(x, y);
        }
    }

}

Kod scrollTo () dba o wszelkie warunki pętli za nas, więc nie musimy się tym martwić. Jedynym zastrzeżeniem jest to, że nie ma gwarancji, że to rozwiązanie będzie działać w przyszłych wersjach Androida, ponieważ zastępujemy metodę chronioną.

Andy
źródło
Cześć Andy Dzięki za poświęcenie czasu na przedstawienie tej odpowiedzi - przesunęło mnie do przodu. Dzięki jeszcze raz. Tim
Tim
-Andy, też używam kodu, ale rzuca mi on oczekiwanie na pustą wskazówkę, czy możesz mi pomóc
hemant
@hemant Czy możesz wskazać, w której linii jest wyrzucany NullPointer i z jaką wersją Androida pracujesz?
sulai
Bohater dnia: Uratował mi życie! = D
Pedrão,
Cześć Andy, dzięki za kod. Działa świetnie. Chciałbym wiedzieć jedną rzecz [jeśli ty lub any1 ma ans. do tego] - Kiedy rzucam mój scrollView, metoda onscrollchanged nie rejestruje wszystkich współrzędnych X i Y podczas przesuwania widoku scrollview. Daje mi tylko pozycję startową i ostatnią pozycję. Powiedz na przykład - zaczynam odrzut od Y = 10 i wychodzę z Y = 30, a następnie prędkość lotu prowadzi do Y = 50 i zatrzymuje się. Więc przewijane tylko rejestry - być może 10, 11, 12..30, a następnie 49, 50. Jak mogę to zrobić, aby zarejestrować również wszystkie lokalizacje pośrednie i uzyskać wszystkie współrzędne od 10 do 50?
alchemik
11

Ulepszenie rozwiązania Andy'ego: w swoim kodzie używa scrollTo, problem polega na tym, że jeśli przerzucisz jeden widok scroll w jednym kierunku, a potem inny w innym, zauważysz, że pierwszy nie zatrzymuje swojego poprzedniego rzutu ruch.

Wynika to z faktu, że scrollView używa computeScroll () do wykonywania swoich flingujących gestów i wchodzi w konflikt z scrollTo.

Aby temu zapobiec, po prostu zaprogramuj onScrollChanged w ten sposób:

    public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
    if(interceptScroll){
        interceptScroll=false;
        if(scrollView == scrollView1) {
            scrollView2.onOverScrolled(x,y,true,true);
        } else if(scrollView == scrollView2) {
            scrollView1.onOverScrolled(x,y,true,true);
        }
        interceptScroll=true;
    }
}

with interceptScroll statyczną wartość logiczną zainicjowaną na true. (pomaga to uniknąć nieskończonych pętli w ScrollChanged)

onOverScrolled to jedyna funkcja, którą znalazłem, której można użyć do zatrzymania przewijania widoku scrollView (ale mogą być inne, które przegapiłem!)

Aby uzyskać dostęp do tej funkcji (która jest chroniona), musisz dodać ją do swojego ObservableScrollViewer

public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
    super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
}
Taiko
źródło
2
Co za niezły chwyt, synu!
FranciscoBouza
5

Dlaczego nie po prostu wdraża OnTouchListenerw swojej działalności. Następnie zastąp metodę onTouch, a następnie pobierz pozycję przewijania pierwszej ScrollViewOne.getScrollY()i zaktualizujScrollViewTwo.scrollTo(0, ScrollViewOne.getScrollY());

Kolejny pomysł ... :)

Aaron Newton
źródło
7
To może zadziałać, ale gdy dotkniesz i puścisz, widok przewijania może przewinąć się nieco dłużej po puszczeniu, co nie jest przechwytywane.
richy
Jak mogę odwołać się / uzyskać referencję scrollView ScrollViewOne w tym przypadku w java?
Bunny Rabbit
Istnieją również inne sposoby przewijania (na przykład onTrackballEvent ())
surfealokesea
To bardzo dobry pomysł, ale zamiast pobierać i ustawiać pozycję scroll y, łatwiej jest po prostu wysłać zdarzenie dotykowe do drugiego widoku scroll. Dzięki temu „rzut” będzie kontynuowany w drugim widoku przewijania. MotionEvent newEvent = MotionEvent.obtain (zdarzenie); documentsScrollView.dispatchTouchEvent (newEvent);
Eduard Mossinkoff
3

W pakiecie Android support-v4 system Android udostępnia nową klasę o nazwie NestedScrollView.

możemy zamienić <ScrollView>węzeł na <android.support.v4.widget.NestedScrollView>w układzie XML i zaimplementować go NestedScrollView.OnScrollChangeListenerw Javie do obsługi przewijania.

To ułatwia sprawę.

wangzhangjian
źródło