Posso ter onScrollListener para um ScrollView?

140

Estou usando um HorizontalScrollViewem um layout e preciso identificar se o usuário atingiu o ponto inicial e final do pergaminho.

Pois ListVieweu tentei um onScrollListenere é possível encontrar o ponto inicial e final do scroll.

Eu tentei fazer o mesmo no meu, Scrollviewmas parece não possível. Existem outras maneiras possíveis de conseguir o que eu preciso.

ARNP
fonte
3
É possível. Consulte a resposta do usuário2695685. Em resumo, o seguinte em onStartfará o truque: hsv.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {@Override public void onScrollChanged() {Log.i(TAG,"scroll:"+hsv.getScrollX());}});em onStart (), onde hsvestá um HorizontalScrollViewtrabalho.
JohnnyLambada
aceitar qualquer resposta útil .. se outra pessoa postar sua própria resposta ..
Ranjith Kumar
12
Por que a detecção de um evento de rolagem com um ScrollView é tão difícil no Android? Isso é loucura.
trabalhou

Respostas:

389

Cada instância do View chama getViewTreeObserver(). Agora, quando segurando uma instância ViewTreeObserver, você pode adicionar um OnScrollChangedListener()a ele usando o método addOnScrollChangedListener().

Você pode ver mais informações sobre esta classe aqui .

Ele permite que você esteja ciente de todos os eventos de rolagem - mas sem as coordenadas. Você pode obtê-los usando getScrollY()ou getScrollX()de dentro do ouvinte.

scrollView.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() {
    @Override
    public void onScrollChanged() {
        int scrollY = rootScrollView.getScrollY(); // For ScrollView
        int scrollX = rootScrollView.getScrollX(); // For HorizontalScrollView
        // DO SOMETHING WITH THE SCROLL COORDINATES
    }
});
Zbun
fonte
6
Esta resposta deve ser marcada como correta. hsv.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {@Override public void onScrollChanged() {Log.i(TAG,"scroll:"+hsv.getScrollX());}});em onStart () onde hsvestá um HorizontalScrollViewtrabalho. Eu suspeito que o mesmo funcionará para um ScrollView também.
JohnnyLambada
3
exatamente. Esta é a melhor resposta. não é necessário estender o HorizontalScrollView. Apenas testado com um ScrollView, funciona muito bem: final ScrollView sv = (ScrollView) findViewById (R.id.scrollViewArticle); sv.getViewTreeObserver (). addOnScrollChangedListener (new ViewTreeObserver.OnScrollChangedListener () {@Override public void onScrollChanged () {Log.d ("onScrollChanged Y", String.valueOf (sv.getScrollY ()));
Raphael C
5
Você deve verificar primeiro o ViewTreeObserver.isAlive ()
M.Salomaa
6
O código de exemplo na resposta apresenta um vazamento de memória. Como o método é adde não set, todos os ouvintes serão retidos até serem removidos explicitamente. Portanto, a classe anônima usada como uma implementação de ouvinte na amostra vazará (junto com tudo o que estiver fazendo referência, ou seja, a classe externa).
Brett Duncavage
7
Provavelmente uma pergunta idiota, mas o que é rootScrollView?
Iqbal
49

Isso pode ser muito útil. Use em NestedScrollViewvez de ScrollView. A Biblioteca de suporte 23.1 introduziu um OnScrollChangeListenerpara NestedScrollView. Então você pode fazer algo assim.

 myScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
        @Override
        public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
            Log.d("ScrollView","scrollX_"+scrollX+"_scrollY_"+scrollY+"_oldScrollX_"+oldScrollX+"_oldScrollY_"+oldScrollY);
            //Do something
        }
    });
Midhun Vijayakumar
fonte
2
Definitivamente, é mais fácil do que controlar se você adicionou o ouvinte antes com getViewTreeObserver (). Obrigado!
Anthony Mills
Mas como usar o NestedScrollView como exibição de rolagem horizontal? Cannt encontrar qualquer recurso
GensaGames
E se eu quiser usar a rolagem horizontal?
Nikita Axyonov
7
O @ dazed'n'confused NestedScrollViewfaz parte da biblioteca de suporte, seu setOnScrollChangeListenermétodo não possui requisitos de versão mínima.
Tom
4
Não deve ser confundido com Viewo setOnScrollChangeListenerque requer nível 23 da API
Anders Ullnæss 13/02/19
18

Aqui está um HorizontalScrollView derivado que escrevi para manipular notificações sobre rolagem e final de rolagem. Ele lida adequadamente quando um usuário parou de rolar ativamente e quando desacelera completamente depois que o usuário solta:

public class ObservableHorizontalScrollView extends HorizontalScrollView {
    public interface OnScrollListener {
        public void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldX, int oldY);
        public void onEndScroll(ObservableHorizontalScrollView scrollView);
    }

    private boolean mIsScrolling;
    private boolean mIsTouching;
    private Runnable mScrollingRunnable;
    private OnScrollListener mOnScrollListener;

    public ObservableHorizontalScrollView(Context context) {
        this(context, null, 0);
    }

    public ObservableHorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();

        if (action == MotionEvent.ACTION_MOVE) {
            mIsTouching = true;
            mIsScrolling = true;
        } else if (action == MotionEvent.ACTION_UP) {
            if (mIsTouching && !mIsScrolling) {
                if (mOnScrollListener != null) {
                    mOnScrollListener.onEndScroll(this);
                }
            }

            mIsTouching = false;
        }

        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldX, int oldY) {
        super.onScrollChanged(x, y, oldX, oldY);

        if (Math.abs(oldX - x) > 0) {
            if (mScrollingRunnable != null) {
                removeCallbacks(mScrollingRunnable);
            }

            mScrollingRunnable = new Runnable() {
                public void run() {
                    if (mIsScrolling && !mIsTouching) {
                        if (mOnScrollListener != null) {
                            mOnScrollListener.onEndScroll(ObservableHorizontalScrollView.this);
                        }
                    }

                    mIsScrolling = false;
                    mScrollingRunnable = null;
                }
            };

            postDelayed(mScrollingRunnable, 200);
        }

        if (mOnScrollListener != null) {
            mOnScrollListener.onScrollChanged(this, x, y, oldX, oldY);
        }
    }

    public OnScrollListener getOnScrollListener() {
        return mOnScrollListener;
    }

    public void setOnScrollListener(OnScrollListener mOnEndScrollListener) {
        this.mOnScrollListener = mOnEndScrollListener;
    }

}
ZaBlanc
fonte
1
Parece que isso é melhor do que usar o método ViewTreeObserver. Só porque quando você tem uma atividade em que vários fragmentos são carregados (por exemplo, 3 fragmentos com guias deslizantes) com ListViews e ScrollViews e é necessário registrar eventos para uma exibição específica. Enquanto o ViewTreeObserver estiver registrado, ele disparará eventos, mesmo que não seja a visualização ativa.
Torsten Ojaperv
3
@ZaBlanc - Você pode me dizer como inicializar esta classe e os ouvintes da minha Atividade, que contém o ScrollView? Eu o implementei muito bem, mas os ouvintes ainda não retornarão nenhum valor.
Edmond Tamas
Isso realmente funciona! e sem necessidade de SDK> 23 para ele :)
Matan Dahan
5

Se você deseja conhecer a posição de rolagem de uma exibição, poderá usar a seguinte função de extensão na classe View:

fun View?.onScroll(callback: (x: Int, y: Int) -> Unit) {
    var oldX = 0
    var oldY = 0
    this?.viewTreeObserver?.addOnScrollChangedListener {
        if (oldX != scrollX || oldY != scrollY) {
            callback(scrollX, scrollY)
            oldX = scrollX
            oldY = scrollY
        }
    }
}
mcspayne
fonte
5

Você pode usar em NestedScrollViewvez de ScrollView. No entanto, ao usar um Kotlin Lambda, ele não saberá que você deseja NestedScrollView, em setOnScrollChangeListenervez daquele no View (que é o nível 23 da API). Você pode corrigir isso especificando o primeiro parâmetro como um NestedScrollView.

nestedScrollView.setOnScrollChangeListener { _: NestedScrollView, scrollX: Int, scrollY: Int, _: Int, _: Int ->
    Log.d("ScrollView", "Scrolled to $scrollX, $scrollY")
}
Cristan
fonte
Essa deve ser a resposta aceita, porque o método que usa um viewTreeObserver causa vazamentos de memória e deve ser removido manualmente, caso contrário, vários observadores de rolagem serão adicionados.
Ray Li
1
    // --------Start Scroll Bar Slide--------
    final HorizontalScrollView xHorizontalScrollViewHeader = (HorizontalScrollView) findViewById(R.id.HorizontalScrollViewHeader);
    final HorizontalScrollView xHorizontalScrollViewData = (HorizontalScrollView) findViewById(R.id.HorizontalScrollViewData);
    xHorizontalScrollViewData.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            int scrollX; int scrollY;
            scrollX=xHorizontalScrollViewData.getScrollX();
            scrollY=xHorizontalScrollViewData.getScrollY();
            xHorizontalScrollViewHeader.scrollTo(scrollX, scrollY);
        }
    });
    // ---------End Scroll Bar Slide---------
Wirote W.
fonte
por favor, tente adicionar alguma descrição para apoiá-lo código que fará com que todos entendam massa
Aniruddha Das
Este código é muito semelhante a outra resposta , não é?
Sergii 28/08
1

você pode definir uma classe ScrollView personalizada e adicionar uma interface a ser chamada ao rolar assim:

public class ScrollChangeListenerScrollView extends HorizontalScrollView {


private MyScrollListener mMyScrollListener;

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

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

public ScrollChangeListenerScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}


public void setOnMyScrollListener(MyScrollListener myScrollListener){
    this.mMyScrollListener = myScrollListener;
}


@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    super.onScrollChanged(l, t, oldl, oldt);
    if(mMyScrollListener!=null){
        mMyScrollListener.onScrollChange(this,l,t,oldl,oldt);
    }

}

public interface MyScrollListener {
    void onScrollChange(View view,int scrollX,int scrollY,int oldScrollX, int oldScrollY);
}

}
Chuanxing Zhao
fonte