web-dev-qa-db-ja.com

Android:ScrollViewがスクロールを停止したことを検出

AndroidでScrollViewを使用していますが、ScrollViewの表示部分はScrollview内のセルの1つと同じサイズです。すべての「セル」は同じ高さです。したがって、私がやろうとしているのは、ScrollViewがスクロールされた後の位置にスナップすることです。

現在、ユーザーがScrollViewに触れたときと、スクロールしてそこから作業し始めたときを検出していますが、かなりバグがあります。また、ユーザーが単にフリックしてスクロールしてから減速するときにも動作する必要があります。

IPhoneにはdidDecelerateのような関数があり、ScrollViewのスクロールが完了したときに必要なコードを実行できます。 Androidにはそのようなことがありますか?または、それを行うためのより良い方法を見つけるために見ることができるコードがありますか?

Androidのドキュメントを確認しましたが、そのようなものは見つかりませんでした。

82
user1053691

私は最近、あなたが説明した機能を実装しなければなりませんでした。私がやったことは、onTouchEventが最初にトリガーされたときにgetScrollY()によって返された値と変数で定義された時間後に返された値を比較することにより、ScrollViewがスクロールを停止したかどうかをRunnableチェックアウトすることでしたnewCheck

以下のコードを参照してください(実用的なソリューション):

public class MyScrollView extends ScrollView{

private Runnable scrollerTask;
private int initialPosition;

private int newCheck = 100;
private static final String TAG = "MyScrollView";

public interface OnScrollStoppedListener{
    void onScrollStopped();
}

private OnScrollStoppedListener onScrollStoppedListener;

public MyScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);

    scrollerTask = new Runnable() {

        public void run() {

            int newPosition = getScrollY();
            if(initialPosition - newPosition == 0){//has stopped

                if(onScrollStoppedListener!=null){

                    onScrollStoppedListener.onScrollStopped();
                }
            }else{
                initialPosition = getScrollY();
                MyScrollView.this.postDelayed(scrollerTask, newCheck);
            }
        }
    };
}

public void setOnScrollStoppedListener(MyScrollView.OnScrollStoppedListener listener){
    onScrollStoppedListener = listener;
}

public void startScrollerTask(){

    initialPosition = getScrollY();
    MyScrollView.this.postDelayed(scrollerTask, newCheck);
}

}

で、〜がある:

scroll.setOnTouchListener(new OnTouchListener() {

        public boolean onTouch(View v, MotionEvent event) {

            if (event.getAction() == MotionEvent.ACTION_UP) {

                scroll.startScrollerTask();
            }

            return false;
        }
});
scroll.setOnScrollStoppedListener(new OnScrollStoppedListener() {

        public void onScrollStopped() {

            Log.i(TAG, "stopped");

        }
});

ところで、私はアプリでこれを行うために他の返信からのいくつかのアイデアを使用しました。お役に立てれば。ご質問はお気軽にお尋ねください。乾杯。

93
tulio84z

私見、ScrollViewにOnEndScrollイベントのバグがないという、もう1つの修正があります。

hambonious answerに触発されました。このクラスをプロジェクトにドロップし(独自のパッケージに変更する)、以下のxmlを使用します

package com.thecrag.components.ui;

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

public class ResponsiveScrollView extends ScrollView {

    public interface OnEndScrollListener {
        public void onEndScroll();
    }

    private boolean mIsFling;
    private OnEndScrollListener mOnEndScrollListener;

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

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

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

    @Override
    public void fling(int velocityY) {
        super.fling(velocityY);
        mIsFling = true;
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldX, int oldY) {
        super.onScrollChanged(x, y, oldX, oldY);
        if (mIsFling) {
            if (Math.abs(y - oldY) < 2 || y >= getMeasuredHeight() || y == 0) {
                if (mOnEndScrollListener != null) {
                    mOnEndScrollListener.onEndScroll();
                }
                mIsFling = false;
            }
        }
    }

    public OnEndScrollListener getOnEndScrollListener() {
        return mOnEndScrollListener;
    }

    public void setOnEndScrollListener(OnEndScrollListener mOnEndScrollListener) {
        this.mOnEndScrollListener = mOnEndScrollListener;
    }

}

プロジェクトに合わせてパッケージ名を再度変更します

<com.thecrag.components.ui.ResponsiveScrollView
        Android:id="@+id/welcome_scroller"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_above="@+id/welcome_scroll_command_help_container"
        Android:layout_alignParentLeft="true"
        Android:layout_alignParentRight="true"
        Android:layout_below="@+id/welcome_header_text_thecrag"
        Android:layout_margin="6dp">
    ....
</com.thecrag.components.ui.ResponsiveScrollView>
22
concept

私は(Horizo​​ntal)ScrollViewをサブクラス化し、次のようなことをしました:

@Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
    if (Math.abs(x - oldX) > SlowDownThreshold) {  
        currentlyScrolling = true;
    } else {
        currentlyScrolling = false;
        if (!currentlyTouching) {
            //scrolling stopped...handle here
        }
    }
    super.onScrollChanged(x, y, oldX, oldY);
}

SlowDownThresholdには1の値を使用しました。これは常に最後のonScrollChangedイベントの違いと思われるためです。

ゆっくりドラッグしたときにこれを正しく動作させるために、私はこれをしなければなりませんでした:

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            currentlyTouching = true;
    }
    return super.onInterceptTouchEvent(event);
}

@Override
public boolean onTouch(View view, MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            currentlyTouching = false;
            if (!currentlyScrolling) {
                //I handle the release from a drag here
                return true;
            }
    }
    return false;
}
9
Ross Hambrick

私のアプローチは、onScrollChanged()が呼び出されるたびに変更されるタイムスタンプによってスクロール状態を決定することです。スクロールの開始時と終了時を判断するのは非常に簡単です。感度を修正するために、しきい値を変更することもできます(私は100msを使用します)。

public class CustomScrollView extends ScrollView {
    private long lastScrollUpdate = -1;

    private class ScrollStateHandler implements Runnable {

        @Override
        public void run() {
            long currentTime = System.currentTimeMillis();
            if ((currentTime - lastScrollUpdate) > 100) {
                lastScrollUpdate = -1;
                onScrollEnd();
            } else {
                postDelayed(this, 100);
            }
        }
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (lastScrollUpdate == -1) {
            onScrollStart();
            postDelayed(new ScrollStateHandler(), 100);
        }

        lastScrollUpdate = System.currentTimeMillis();
    }

    private void onScrollStart() {
        // do something
    }

    private void onScrollEnd() {
        // do something
    }

}
7
Lin Yu Cheng

上記の回答に自然に触発された、私の意見では非常にシンプルでクリーンな別のソリューションがあります。基本的に、短い遅延(ここでは50ms)の後、getScrollY()がまだ変更されている場合、ユーザーがジェスチャチェックを終了します。

public class ScrollViewWithOnStopListener extends ScrollView {

    OnScrollStopListener listener;

    public interface OnScrollStopListener {
        void onScrollStopped(int y);
    }

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

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

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_UP:
                checkIfScrollStopped();
        }

        return super.onTouchEvent(ev);
    }

    int initialY = 0;

    private void checkIfScrollStopped() {
        initialY = getScrollY();
        this.postDelayed(new Runnable() {
            @Override
            public void run() {
                int updatedY = getScrollY();
                if (updatedY == initialY) {
                    //we've stopped
                    if (listener != null) {
                        listener.onScrollStopped(getScrollY());
                    }
                } else {
                    initialY = updatedY;
                    checkIfScrollStopped();
                }
            }
        }, 50);
    }

    public void setOnScrollStoppedListener(OnScrollStopListener yListener) {
        listener = yListener;
    }
}
5
Aleksandarf

この質問に対する私のアプローチは、タイマーを使用して、次の2つの「イベント」をチェックすることです。

1)onScrollChanged()の呼び出しが停止しました

2)ユーザーの指がスクロールビューから離れている

public class CustomScrollView extends HorizontalScrollView {

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

Timer ntimer = new Timer();
MotionEvent event;

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) 
{
    checkAgain();
    super.onScrollChanged(l, t, oldl, oldt);
}

public void checkAgain(){
    try{
        ntimer.cancel();
        ntimer.purge();
    }
    catch(Exception e){}
    ntimer = new Timer();
    ntimer.schedule(new TimerTask() {

        @Override
        public void run() {

            if(event.getAction() == MotionEvent.ACTION_UP){
                // ScrollView Stopped Scrolling and Finger is not on the ScrollView
            }
            else{
                // ScrollView Stopped Scrolling But Finger is still on the ScrollView
                checkAgain();
            }
        }
    },100);

}

@Override
public boolean onTouchEvent(MotionEvent event) {
    this.event = event; 
    return super.onTouchEvent(event);
    }
}
4
ZeroG

スクロール追跡とスクロール終了を含む私のソリューションは次のとおりです。

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

}
2
ZaBlanc

私のソリューションは、Lin Yu Chengの優れたソリューションのバリエーションであり、スクロールの開始と停止を検出します。

手順1. Horizo​​ntalScrollViewおよびOnScrollChangedListenerを定義します。

CustomHorizontalScrollView scrollView = (CustomHorizontalScrollView) findViewById(R.id.horizontalScrollView);

horizontalScrollListener = new CustomHorizontalScrollView.OnScrollChangedListener() {
    @Override
    public void onScrollStart() {
        // Scrolling has started. Insert your code here...
    }

    @Override
    public void onScrollEnd() {
         // Scrolling has stopped. Insert your code here...
    }
};

scrollView.setOnScrollChangedListener(horizontalScrollListener);

ステップ2. CustomHorizo​​ntalScrollViewクラスを追加します。

public class CustomHorizontalScrollView extends HorizontalScrollView {
    public interface OnScrollChangedListener {
        // Developer must implement these methods.
        void onScrollStart();
        void onScrollEnd();
    }

    private long lastScrollUpdate = -1;
    private int scrollTaskInterval = 100;
    private Runnable mScrollingRunnable;
    public OnScrollChangedListener mOnScrollListener;

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

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

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

    private void init(Context context) {
        // Check for scrolling every scrollTaskInterval milliseconds
        mScrollingRunnable = new Runnable() {
            public void run() {
                if ((System.currentTimeMillis() - lastScrollUpdate) > scrollTaskInterval) {
                    // Scrolling has stopped.
                    lastScrollUpdate = -1;
                    //CustomHorizontalScrollView.this.onScrollEnd();
                    mOnScrollListener.onScrollEnd();
                } else {
                    // Still scrolling - Check again in scrollTaskInterval milliseconds...
                    postDelayed(this, scrollTaskInterval);
                }
            }
        };
    }

    public void setOnScrollChangedListener(OnScrollChangedListener onScrollChangedListener) {
        this.mOnScrollListener = onScrollChangedListener;
    }

    public void setScrollTaskInterval(int scrollTaskInterval) {
        this.scrollTaskInterval = scrollTaskInterval;
    }

    //void onScrollStart() {
    //    System.out.println("Scroll started...");
    //}

    //void onScrollEnd() {
    //    System.out.println("Scroll ended...");
    //}

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (mOnScrollListener != null) {
            if (lastScrollUpdate == -1) {
                //CustomHorizontalScrollView.this.onScrollStart();
                mOnScrollListener.onScrollStart();
                postDelayed(mScrollingRunnable, scrollTaskInterval);
            }

            lastScrollUpdate = System.currentTimeMillis();
        }
    }
}
2
D. Clarke

StackOverflowの この質問 を見てみてください-それはあなたの質問とまったく同じではありませんが、ScrollViewのスクロールイベントを管理する方法についてのアイデアを提供します。

基本的に、CustomScrollViewを拡張してonScrollChanged(int x, int y, int oldx, int oldy)をオーバーライドすることにより、独自のScrollViewを作成する必要があります。次に、標準のScrollViewのようなcom.mypackage.CustomScrollViewの代わりに、レイアウトファイルでこれを参照する必要があります。

2
kaspermoerch

これは古いスレッドですが、私が思いついた短いソリューションを追加したいと思います:

 buttonsScrollView.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->

            handler.removeCallbacksAndMessages(null)

            handler.postDelayed({
                  //YOUR CODE TO BE EXECUTED HERE
                },1000)
        }

当然、1000ミリ秒の遅延があります。必要に応じて調整してください。

2
pepinobr

あなたが説明したような単純なケースでは、カスタムスクロールビューでオーバーライドメソッドを無効にすることでおそらく逃げることができます。 Flingメソッドは、ユーザーが画面から指を上げるたびに「減速」を実行するために呼び出されます。

だからあなたがすべきことは次のようなものです:

  1. ScrollViewのサブクラス。

    public class MyScrollView extends ScrollView { 
    
        private Scroller scroller;  
        private Runnable scrollerTask; 
    
        //...
    
        public MyScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            scroller = new Scroller(getContext()); //or OverScroller for 3.0+
            scrollerTask = new Runnable() {
                @Override
                public void run() {
                    scroller.computeScrollOffset();
                    scrollTo(0, scroller.getCurrY());
    
                    if (!scroller.isFinished()) {
                        MyScrollView.this.post(this);
                    } else {
                        //deceleration ends here, do your code
                    }
                }
            };
            //...
        }
    }
    
  2. サブクラスflingメソッド。スーパークラス実装を呼び出さないでください。

    @Override  
    public void fling(int velocityY) {  
        scroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0, container.getHeight());
        post(scrollerTask);  
    
        //add any extra functions you need from Android source code:
        //show scroll bars
        //change focus
        //etc.
    }
    
  3. ユーザーが指を上げる前にスクロールを停止した場合、速度はトリガーされません(velocityY == 0)。この種のイベントもインターセプトする場合は、onTouchEventをオーバーライドします。

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean eventConsumed = super.onTouchEvent(ev);
        if (eventConsumed && ev.getAction() == MotionEvent.ACTION_UP) {
            if (scroller.isFinished()) {
                //do your code
            }
        }
        return eventConsumed;
    }
    

NOTEこれは機能しますが、flingメソッドをオーバーライドするのは悪い考えかもしれません。これはパブリックですが、サブクラス用にほとんど設計されていません。現在、3つのことを行います-プライベートmScrollerのフリングを開始し、可能なフォーカス変更を処理し、スクロールバーを表示します。これは将来のAndroidリリースで変更される可能性があります。たとえば、プライベートmScrollerインスタンスは、クラスをScrollerからOvershootScrollerに2.3と3.0の間で変更しました。この小さな違いをすべて覚えておく必要があります。いずれにせよ、将来の予期しない結果に備えてください。

2
Alexey

これは過去に起こったと思います。知る限りでは、簡単にそれを検出することはできません。私の提案は、あなたが ScrollView.Java を見ることです(それがAndroid land :))そして、クラスを拡張して、探している機能を提供する方法を見つけます。これは私が最初に試みることです:

 @Override
 protected void onScrollChanged(int l, int t, int oldl, int oldt) {
     if (mScroller.isFinished()) {
         // do something, for example call a listener
     }
 }
2
Felix

ZeroGの回答にいくつかの改善を加えました。主に過剰なタスク呼び出しをキャンセルし、プライベートなOnTouchListenerとして全体を実装するため、すべてのスクロール検出コードが1か所になります。

独自のScrollView実装に次のコードを貼り付けます。

private class ScrollFinishHandler implements OnTouchListener
{
        private static final int SCROLL_TASK_INTERVAL = 100;

        private Runnable    mScrollerTask;
        private int         mInitialPosition = 0;

        public ScrollFinishHandler()
        {
            mScrollerTask = new Runnable() {

                public void run() {

                    int newPosition = getScrollY();
                    if(mInitialPosition - newPosition == 0)
                    {//has stopped

                       onScrollStopped(); // Implement this on your main ScrollView class
                    }else{
                        mInitialPosition = getScrollY();
                        ExpandingLinearLayout.this.postDelayed(mScrollerTask, SCROLL_TASK_INTERVAL);
                    }
                }
            };
        }

        @Override
        public boolean onTouch(View v, MotionEvent event) 
        {
            if (event.getAction() == MotionEvent.ACTION_UP) 
            {

                startScrollerTask();
            }
            else
            {
                stopScrollerTask();
            }

            return false;
        }

}

そして、ScrollView実装で:

setOnTouchListener( new ScrollFinishHandler() );
1
Vaiden

ここにはいくつかの素晴らしい答えがありますが、私のコードはScrollViewクラスを拡張することなくスクロールが停止したことを検出できます。すべてのビューインスタンスはgetViewTreeObserver()を呼び出すことができます。 ViewTreeObserverのこのインスタンスを保持するとき、関数addOnScrollChangedListener()を使用してOnScrollChangedListenerを追加できます。

以下を宣言します。

private ScrollView scrollListener;
private volatile long milesec;

private Handler scrollStopDetector;
private Thread scrollcalled = new Thread() {

    @Override
    public void run() { 
        if (System.currentTimeMillis() - milesec > 200) {
            //scroll stopped - put your code here
        }
    }
}; 

onCreate(または別の場所)に以下を追加します。

    scrollListener = (ScrollView) findViewById(R.id.scroll);

    scrollListener.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() {

        @Override
        public void onScrollChanged() {
            milesec = System.currentTimeMillis();
            scrollStopDetector.postDelayed(scrollcalled, 200);
        }
    });

このチェックの間隔を長くしたり遅くしたりすることもできますが、スクロールするとこのリスターが非常に速く呼び出されるため、非常に高速に動作します。

1
Zbun