web-dev-qa-db-ja.com

ScrollViewの終了を検出

ScrollViewを使用するアクティビティを持つアプリがあります。ユーザーがScrollViewの一番下に到達したことを検出する必要があります。私はいくつかのグーグル検索を行いましたが、私は このページ が説明されています。しかし、例では、その人はScrollViewを拡張します。先ほど言ったように、アクティビティを拡張する必要があります。

だから、「OK、ScrollViewを拡張するカスタムクラスを作成し、onScrollChanged()メソッドをオーバーライドして、スクロールの終わりを検出し、それに応じて行動しよう」と言いました。

私はやったが、この行で:

scroll = (ScrollViewExt) findViewById(R.id.scrollView1);

Java.lang.ClassCastExceptionをスローします。 XMLの<ScrollView>タグを変更しましたが、明らかに機能しません。私の質問は:なぜ、ScrollViewExtScrollViewを拡張する場合、私の顔にClassCastExceptionを投げるのですか?あまり混乱することなくスクロールの終了を検出する方法はありますか?

人々に感謝します。

編集:約束されたように、ここに私の重要なXMLの一部があります:

<ScrollView
        Android:id="@+id/scrollView1"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent" >


        <WebView
            Android:id="@+id/textterms"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:gravity="center_horizontal"
            Android:textColor="@Android:color/black" />

    </ScrollView>

内部のテキストを正当化できるように、TextViewからWebViewに変更しました。私が達成したいのは、「契約の条件が完全に読まれるまで、「Accept」ボタンがアクティブにならない」ということです。私の拡張クラスはScrollViewExtと呼ばれます。 ScrollViewのタグScrollViewExtを変更すると、

Android.view.InflateException: Binary XML file line #44: Error inflating class ScrollViewExt

タグScrollViewExを理解しないためです。私はそれが解決策を持っているとは思わない...

ご回答ありがとうございます!

63
Fustigador

それをやった!

Alexandreが提供してくれた修正は別として、インターフェイスを作成する必要がありました。

public interface ScrollViewListener {
    void onScrollChanged(ScrollViewExt scrollView, 
                         int x, int y, int oldx, int oldy);
}

次に、ScrollViewExtのScrollViewからOnScrollChangedメソッドをオーバーライドする必要がありました。

public class ScrollViewExt extends ScrollView {
    private ScrollViewListener scrollViewListener = null;
    public ScrollViewExt(Context context) {
        super(context);
    }

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

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

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

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

今、アレクサンドルが言ったように、パッケージ名をXMLタグに入れて(私の障害)、Activityクラスに前に作成したインターフェイスを実装させ、それをすべてまとめます:

scroll = (ScrollViewExt) findViewById(R.id.scrollView1);
scroll.setScrollViewListener(this);

そして、OnScrollChangedメソッドでは、インターフェースから...

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {
    // We take the last son in the scrollview
    View view = (View) scrollView.getChildAt(scrollView.getChildCount() - 1);
    int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));

    // if diff is zero, then the bottom has been reached
    if (diff == 0) {
        // do stuff
    }
}

そしてそれはうまくいきました!

アレクサンドル、助けてくれてありがとう!

104
Fustigador

これを検出する簡単な方法を見つけました:

   scrollView.getViewTreeObserver()
       .addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
                if (scrollView.getChildAt(0).getBottom()
                     <= (scrollView.getHeight() + scrollView.getScrollY())) {
                    //scroll view is at bottom
                } else {
                    //scroll view is not at bottom
                }
            }
        });
  • ScrollViewをカスタマイズする必要はありません。
  • Scrollviewは、直接の子を1つだけホストできます。したがって、scrollView.getChildAt(0)は問題ありません。
  • このソリューションは、スクロールビューの直接の子の高さがmatch_parentまたはwrap_contentであっても正しいです。
56

これらの答えはすべて非常に複雑ですが、これを実現する簡単な組み込みメソッドがあります。 canScrollVertically(int)

例えば:

_@Override
public void onScrollChanged() {
    if (!scrollView.canScrollVertically(1)) {
        // bottom of scroll view
    }
    if (!scrollView.canScrollVertically(-1)) {
        // top of scroll view
    }
}
_

メソッドはViewに実装されているため、RecyclerView、ListView、および実際には他のビューでも機能します。

水平のScrollViewがある場合、 canScrollHorizontally(int) で同じことが実現できます。

30
yuval

Fustigadorの答えは素晴らしかったが、計算後にいくつかのデバイス(Samsung Galaxy Note Vなど)が0に到達せず、2ポイント残っていることがわかった。以下のような小さなバッファーを追加することをお勧めします。

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {
    // We take the last son in the scrollview
    View view = (View) scrollView.getChildAt(scrollView.getChildCount() - 1);
    int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));

    // if diff is zero, then the bottom has been reached
    if (diff <= 10) {
        // do stuff
    }
}
16
Will

[〜#〜] edit [〜#〜]

XMLのコンテンツを見ると、ScrollViewを使用していることがわかります。カスタムビューを使用する場合は、com.your.packagename.ScrollViewExtを記述する必要があり、コードで使用できるようになります。

<com.your.packagename.ScrollViewExt
    Android:id="@+id/scrollView1"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent" >

    <WebView
        Android:id="@+id/textterms"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:gravity="center_horizontal"
        Android:textColor="@Android:color/black" />

</com.your.packagename.ScrollViewExt>

編集終了

Xmlコンテンツを投稿できますか?

スクロールリスナーを追加して、表示された最後の項目がリストビューの最後の項目であるかどうかを確認できると思います。

mListView.setOnScrollListener(new OnScrollListener() {      
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // TODO Auto-generated method stub      
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        // TODO Auto-generated method stub
        if(view.getLastVisiblePosition()==(totalItemCount-1)){
            //dosomething
        }
    }
});
10
Alexandre B.

サポートライブラリのNestedScrollViewNestedScrollView.OnScrollChangeListenerインターフェイス。

https://developer.Android.com/reference/Android/support/v4/widget/NestedScrollView.html

または、アプリがAPI 23以上をターゲットにしている場合、ScrollViewで次のメソッドを使用できます。

View.setOnScrollChangeListener(OnScrollChangeListener listener) 

次に、@ Fustigadorが答えで説明した例に従ってください。ただし、@ Willが説明したように、何らかの理由でユーザーまたはシステムがリストの一番下に到達できない場合に備えて、小さなバッファーを追加することを検討してください。

また、スクロール変更リスナーは、負の値またはビューの高さより大きい値で呼び出されることもあります。おそらく、これらの値はスクロールアクションの「運動量」を表します。ただし、適切に処理しない限り(床/腹筋)、ビューが範囲の上部または下部にスクロールされると、スクロール方向の検出に問題が発生する可能性があります。

https://developer.Android.com/reference/Android/view/View.html#setOnScrollChangeListener(Android.view.View.OnScrollChangeListener)

4
TheIT

スクロールビューの一部はXMLファイルにパディングがあり、動作しない場合があるため、常にscrollView.getPaddingBottom()を追加してスクロールビューの全高に一致させる必要があります。

scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            if (scrollView != null) {
               View view = scrollView.getChildAt(scrollView.getChildCount()-1);
               int diff = (view.getBottom()+scrollView.getPaddingBottom()-(scrollView.getHeight()+scrollView.getScrollY()));

          // if diff is zero, then the bottom has been reached
               if (diff == 0) {
               // do stuff
                }
            }
        }
    });
3
Pranav
scrollView = (ScrollView) findViewById(R.id.scrollView);

    scrollView.getViewTreeObserver()
            .addOnScrollChangedListener(new 
            ViewTreeObserver.OnScrollChangedListener() {
                @Override
                public void onScrollChanged() {

                    if (!scrollView.canScrollVertically(1)) {
                        // bottom of scroll view
                    }
                    if (!scrollView.canScrollVertically(-1)) {
                        // top of scroll view


                    }
                }
            });
3

ほとんどの答えは実際、uが一番下までスクロールすると、リスナーが数回トリガーされる、これは望ましくないです。この動作を回避するために、メソッドを再度呼び出す前にスクロール位置が変更されたかどうかをチェックするフラグscrollPositionChangedを追加しました。

public class EndDetectingScrollView extends ScrollView {
    private boolean scrollPositionChanged = true;

    private ScrollEndingListener scrollEndingListener;

    public interface ScrollEndingListener {
        void onScrolledToEnd();
    }

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

        View view = this.getChildAt(this.getChildCount() - 1);
        int diff = (view.getBottom() - (this.getHeight() + this.getScrollY()));
        if (diff <= 0) {
            if (scrollPositionChanged) {
                scrollPositionChanged = false;
                if (scrollEndingListener != null) {
                    scrollEndingListener.onScrolledToEnd();
                }
            }
        } else {
            scrollPositionChanged = true;
        }
    }

    public void setScrollEndingListener(ScrollEndingListener scrollEndingListener) {
        this.scrollEndingListener = scrollEndingListener;
    }
}

次に、リスナーを設定します

scrollView.setScrollEndingListener(new EndDetectingScrollView.ScrollEndingListener() {
    @Override
    public void onScrolledToEnd() {
        //do your stuff here    
    }
});

あなたが好きなら同じことをするかもしれません

scrollView.getViewTreeObserver().addOnScrollChangedListener(...)

ただし、このリスナーを追加するクラスからフラグを提供する必要があります。

2
Phatee P

カスタムScrollViewの最後にいるかどうかを判断するには、メンバー変数を使用して最後のy位置を保存することもできます。次に、最後のy位置を現在のスクロール位置と比較できます。

private int scrollViewPos;

...

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {

   //reached end of scrollview
   if (y > 0 && scrollViewPos == y){
      //do something
   }

   scrollViewPos = y;
}
1
Willi

私はスクロールビューの一番下の前にオフセットを持つFABを表示/非表示にしたかった。これは私が思いついた解決策です(Kotlin):

scrollview.viewTreeObserver.addOnScrollChangedListener {
    if (scrollview.scrollY < scrollview.computeVerticalScrollRange() - scrollview.height - offset) {
        // fab.hide()
    } else {
        // fab.show()
    }
}
0
G00fY