Programing

Android : ScrollView가 스크롤을 중지 할 때 감지

lottogame 2020. 9. 24. 08:11
반응형

Android : ScrollView가 스크롤을 중지 할 때 감지


나는 ScrollViewAndroid 에서을 사용 하고 있으며의 보이는 부분 ScrollViewScrollview. 모든 "셀"은 동일한 높이입니다. 그래서 내가하려는 ScrollView것은 스크롤 된 후 위치에 스냅하는 것입니다 .

현재 사용자가를 터치하고 ScrollView스크롤을 시작하고 거기에서 작업을 시작했을 때 감지 하고 있지만 버그가 상당히 많습니다. 또한 사용자가 그냥 튕겨서 스크롤 한 다음 감속 할 때도 작동해야합니다.

iPhone에는 비슷한 기능이 있으며 스크롤이 완료 didDecelerate되면 원하는 코드를 수행 할 수 있습니다 ScrollView. Android에 그런 것이 있습니까? 아니면 더 나은 방법을 찾기 위해 볼 수있는 코드가 있습니까?

Android 문서를 살펴 보았지만 그런 것을 찾을 수 없었습니다.


최근에 설명하신 기능을 구현해야했습니다. 내가 한 일은 onTouchEvent가 처음 트리거 될 때 getScrollY ()가 반환 한 값을 변수 newCheck에 정의 된 시간 후에 반환 된 값과 비교하여 ScrollView가 스크롤을 중지했는지 여부를 Runnable으로 확인하는 것입니다 .

아래 코드 (작동 솔루션)를 참조하십시오.

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");

        }
});

BTW 나는 내 앱에서 이것을하기 위해 다른 답글의 아이디어를 거의 사용하지 않았습니다. 도움이 되었기를 바랍니다. 질문이 있으시면 언제든지 물어보십시오. 건배.


다음은 ScrollView에서 OnEndScroll 이벤트 버그가 누락 된 IMHO에 대한 또 다른 수정 사항입니다.

해로운 대답에서 영감을 얻었습니다 . 이 클래스를 프로젝트에 드롭하고 (자신의 것과 일치하도록 패키지 변경) 아래 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>

(Horizontal) 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);
}

항상 마지막 onScrollChanged 이벤트의 차이 인 것처럼 보이기 때문에 SlowDownThreshold에 1 값을 사용했습니다.

천천히 드래그 할 때 올바르게 동작하도록하려면 다음을 수행해야합니다.

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

내 접근 방식은 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
    }

}

여기에 위의 답변에서 자연스럽게 영감을 얻은 매우 간단하고 깨끗한 또 다른 해결책이 있습니다. 기본적으로 사용자가 제스처를 종료하면 잠시 후 (여기서는 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;
    }
}

이 질문에 대한 나의 접근 방식은 타이머를 사용하여 다음 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);
    }
}

여기 StackOverflow 에서이 질문을 살펴보세요. 질문 과 정확히 같지는 않지만 .NET Framework의 스크롤 이벤트를 관리 할 수있는 방법에 대한 아이디어를 제공합니다 ScrollView.

기본적 CustomScrollView으로 확장 ScrollView하고 재정 의하여 직접 만들어야합니다 onScrollChanged(int x, int y, int oldx, int oldy). 그런 다음 .NET ScrollView과 같은 표준 대신 레이아웃 파일에서 이것을 참조해야합니다 com.mypackage.CustomScrollView.


설명한 것과 같은 간단한 사례의 경우 사용자 지정 스크롤 뷰에서 fling 메서드를 재정 의하여 벗어날 수 있습니다. 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. 사용자가 손가락을 올리기 전에 스크롤을 중지하면 Fling이 트리거되지 않습니다 (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;
    }
    

참고 이 작동하지만, 날뛰는 방법을 무시하는 것은 좋은 생각이 될 수 있습니다. 공개적이지만 서브 클래 싱을 위해 간신히 설계되었습니다. 지금은 3 가지 작업을 수행합니다. 개인용 mScroller에 대한 플링을 시작하고 가능한 포커스 변경을 처리하며 스크롤 막대를 표시합니다. 이는 향후 Android 릴리스에서 변경 될 수 있습니다. 예를 들어, 비공개 mScroller 인스턴스는 해당 클래스를 Scroller에서 OvershootScroller로 2.3과 3.0 사이에서 변경했습니다. 이 모든 작은 차이점을 명심해야합니다. 어쨌든 미래에 예상치 못한 결과에 대비하십시오.


스크롤 추적 및 스크롤 종료를 포함하는 솔루션은 다음과 같습니다.

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

}

나는 이것이 과거에 나온 것 같습니다. AFAIK, 당신은 그것을 쉽게 감지 할 수 없습니다 . 제 제안은 ScrollView.java(이것이 Android 랜드에서 일을하는 방법입니다 :)) 당신이 찾고있는 기능을 제공하기 위해 클래스를 확장 할 수있는 방법을 알아내는 것입니다. 이것이 내가 먼저 시도 할 것입니다.

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

내 솔루션은 Lin Yu Cheng의 훌륭한 솔루션의 변형이며 스크롤이 시작되고 중지 된시기도 감지합니다.

1 단계. HorizontalScrollView 및 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 단계. CustomHorizontalScrollView 클래스를 추가합니다.

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

이것은 오래된 스레드이지만 더 짧은 솔루션을 추가하고 싶습니다.

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

            handler.removeCallbacksAndMessages(null)

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

당연히 1000 밀리 초의 지연이 있습니다. 필요한 경우 조정하십시오.


ZeroG의 답변을 약간 개선했습니다. 주로 초과 작업 호출을 취소하고 모든 것을 개인 OnTouchListener로 구현하므로 모든 스크롤 감지 코드가 한곳에 있습니다.

다음 코드를 자신의 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() );

여기에 몇 가지 훌륭한 답변이 있지만 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);
        }
    });

이 검사 사이에 더 오래 또는 더 느리게 시간이 걸리고 싶을 수 있지만 스크롤 할 때이리스트 너가 정말 빨리 호출되어 매우 빠르게 작동합니다.


        this.getListView().setOnScrollListener(new OnScrollListener(){
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {}

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
                     if( firstVisibleItem + visibleItemCount >= totalItemCount ) 
                              // Last item is shown...

            }

스 니펫이 도움이되기를 바랍니다. :)

참고 URL : https://stackoverflow.com/questions/8181828/android-detect-when-scrollview-stops-scrolling

반응형