Sincronizar posições de rolagem ScrollView - android

95

Eu tenho 2 ScrollViews em meu layout de Android. Como posso sincronizar suas posições de rolagem?

Tim
fonte

Respostas:

289

Existe um método em ScrollView ...

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

Infelizmente, o Google nunca pensou que precisaríamos acessá-lo, e é por isso que eles o protegeram e não adicionaram um gancho "setOnScrollChangedListener". Portanto, teremos que fazer isso por nós mesmos.

Primeiro, precisamos de uma interface.

package com.test;

public interface ScrollViewListener {

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

}

Em seguida, precisamos substituir a classe ScrollView, para fornecer o gancho 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);
        }
    }

}

E devemos especificar esta nova classe ObservableScrollView no layout, em vez das tags ScrollView existentes.

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

    ...

</com.test.ObservableScrollView>

Finalmente, colocamos tudo junto na classe 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);
        }
    }

}

O código scrollTo () cuida de quaisquer condições de loop para nós, então não precisamos nos preocupar com isso. A única ressalva é que essa solução não tem garantia de funcionar em versões futuras do Android, porque estamos substituindo um método protegido.

Andy
fonte
Olá, Andy. Obrigado por dedicar seu tempo para colocar esta resposta - me motivou. Obrigado novamente. Tim
Tim de
-Andy Eu também estou usando o código, mas ele está me jogando a expecção de ponteiro nulo, você pode me ajudar
hemant
@hemant Você pode apontar em qual linha o NullPointer é lançado e com qual versão do Android você está trabalhando?
sulai
Herói do dia: salvou minha vida! = D
Pedrão
Oi Andy, obrigado pelo código. Funciona muito bem. Gostaria de saber uma coisa [se você ou any1 tem ans. para isso] - Quando eu lanço meu scrollView, o método onscrollchanged não registra todas as coordenadas X e Y enquanto o scrollview se move. Isso só me dá a posição inicial e a última posição. Digamos por ex- eu começo o arremesso de Y = 10 e saio em Y = 30 e então a velocidade do arremesso leva para Y = 50 e então para. Assim, o onscrollchanged apenas registradores - talvez 10, 11, 12..30 e então 49, 50. Como posso fazer isso registrar também todas as localizações intermediárias e obter todas as coordenadas de 10 a 50 ??
alquimista
11

Uma melhoria na solução de Andy: em seu código, ele usa scrollTo, o problema é que, se você lançar um scrollview em uma direção e, em seguida, outro em outra direção, você notará que o primeiro não interrompe o lançamento anterior movimento.

Isso se deve ao fato de que scrollView usa computeScroll () para fazer seus gestos de arremesso e entra em conflito com scrollTo.

Para evitar isso, basta programar onScrollChanged desta forma:

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

com interceptScroll, um booleano estático inicializado como verdadeiro. (isso ajuda a evitar loops infinitos em ScrollChanged)

onOverScrolled é a única função que descobri que poderia ser usada para impedir o lançamento de scrollView (mas pode haver outras que eu perdi!)

Para acessar esta função (que é protegida), você deve adicioná-la ao seu ObservableScrollViewer

public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
    super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
}
Taiko
fonte
2
Que bela captura, filho!
FranciscoBouza
5

Porque não apenas implementa OnTouchListenerna sua atividade. Em seguida, substitua o método onTouch, obtenha a posição de rolagem do primeiro ScrollViewOne.getScrollY()e atualizeScrollViewTwo.scrollTo(0, ScrollViewOne.getScrollY());

Só mais uma ideia ... :)

Aaron Newton
fonte
7
Isso pode funcionar, mas quando você toca e solta, a visualização de rolagem pode rolar um pouco mais depois que você solta o que não é capturado.
richy de
Como posso consultar / obter a referência do scrollView ScrollViewOne neste caso em java?
Coelho Coelho
Existem também outras maneiras de rolar (onTrackballEvent () por exemplo)
surfealokesea
É uma ideia muito boa, mas em vez de obter e definir a posição de rolagem y, é mais fácil apenas despachar o evento de toque para a segunda visualização de rolagem. Isso também manterá o "lance" na segunda visualização de rolagem. MotionEvent newEvent = MotionEvent.obtain (evento); documentsScrollView.dispatchTouchEvent (newEvent);
Eduard Mossinkoff
3

No pacote Android support-v4, o Android fornece uma nova classe chamada NestedScrollView.

podemos substituir o <ScrollView>nó por <android.support.v4.widget.NestedScrollView>no layout xml e implementá-lo NestedScrollView.OnScrollChangeListenerem Java para lidar com a rolagem.

Isso torna as coisas mais fáceis.

Wangzhangjian
fonte