Android ScrollView監聽滑動到頂部和底部的兩種方式(你可能不知道的細節)


Android ScrollView監聽滑動到頂部和底部,雖然網上很多資料都有說,但是不全,而且有些細節沒說清楚


 

使用場景:

1. 做一些復雜動畫的時候,需要動態判斷當前的ScrollView是否滾動到底部或者頂部

2. ScrollView滾動到頂部或者底部時主動觸發一些操作(典型的就是滾動到底部觸發自動加載操作)

 

兩種方式:

1. onScrollChanged方式,自己計算

2. onOverScrolled使用系統計算的結果,api >= 9才支持

可能忽視的細節1:

如果是手勢滑動,上面兩種方式都對,但是如果是調用ScrollViewsmoothScrollToscrollTo方法來滾動的話,

只有onScrollChanged監聽對,onOverScrolled監聽不對,因為通過代碼來滾動話是精確滾動,onOverScrolled方法沒處理這種情況

 

兩種方式如何選擇?

一般來說如果系統有實現的東西,就用系統的,我們畢竟是基於Android系統來做開發,別人做的考慮很多適配兼容問題

所以原則就是系統有就用它的,沒有才用我們自己的,具體實現仔細看代碼,記得看注釋

如果不考慮smoothScrollTo和scrollTo滾動,上面這個原則就是對的,如果要考慮的話,這里只能使用onScrollChanged

 

滾動到頂部和底部時對應的計算關系:

 

 

備注:無padding的情況可以轉換為有padding的情況,即tp,bp=0

mScrollY + H – tp – bp = h ===> mScrollY + H = h 

 

代碼實現,自定義View,可以直接拷貝就可以使用

下面代碼不考慮smoothScrollTo和scrollTo方法的影響,要考慮的話,去掉onOverScrolled方法,去掉onScrollChanged的api版本條件限制即可

import android.content.Context; import android.util.AttributeSet; import android.widget.ScrollView; /** * 監聽ScrollView滾動到頂部或者底部做相關事件攔截 */
public class SmartScrollView extends ScrollView { private boolean isScrolledToTop = true;  // 初始化的時候設置一下值 private boolean isScrolledToBottom = false; public ScanScrollView(Context context, AttributeSet attrs) { super(context, attrs); } private ISmartScrollChangedListener mSmartScrollChangedListener; /** 定義監聽接口 */
    public interface ISmartScrollChangedListener { void onScrolledToBottom(); void onScrolledToTop(); } public void setScanScrollChangedListener(ISmartScrollChangedListener smartScrollChangedListener) { mSmartScrollChangedListener = smartScrollChangedListener; } @Override protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { super.onOverScrolled(scrollX, scrollY, clampedX, clampedY); if (scrollY == 0) { isScrolledToTop = clampedY; isScrolledToBottom = false; } else { isScrolledToTop = false; isScrolledToBottom = clampedY; } notifyScrollChangedListeners(); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (android.os.Build.VERSION.SDK_INT < 9) {  // API 9及之后走onOverScrolled方法監聽
            if (getScrollY() == 0) { // 小心踩坑1: 這里不能是getScrollY() <= 0 isScrolledToTop = true; isScrolledToBottom = false; } else if (getScrollY() + getHeight() - getPaddingTop()-getPaddingBottom() == getChildAt(0).getHeight()) { // 小心踩坑2: 這里不能是 >= 
         // 小心踩坑3(可能忽視的細節2):這里最容易忽視的就是ScrollView上下的padding  isScrolledToBottom = true; isScrolledToTop = false; } else { isScrolledToTop = false; isScrolledToBottom = false
; } notifyScrollChangedListeners(); } // 有時候寫代碼習慣了,為了兼容一些邊界奇葩情況,上面的代碼就會寫成<=,>=的情況,結果就出bug了 // 我寫的時候寫成這樣:getScrollY() + getHeight() >= getChildAt(0).getHeight() // 結果發現快滑動到底部但是還沒到時,會發現上面的條件成立了,導致判斷錯誤 // 原因:getScrollY()值不是絕對靠譜的,它會超過邊界值,但是它自己會恢復正確,導致上面的計算條件不成立 // 仔細想想也感覺想得通,系統的ScrollView在處理滾動的時候動態計算那個scrollY的時候也會出現超過邊界再修正的情況 } private void notifyScrollChangedListeners() { if (isScrolledToTop) { if (mSmartScrollChangedListener != null) { mSmartScrollChangedListener.onScrolledToTop(); } } else if (isScrolledToBottom) { if (mSmartScrollChangedListener != null) { mSmartScrollChangedListener.onScrolledToBottom(); } } } public boolean isScrolledToTop() { return isScrolledToTop; } public boolean isScrolledToBottom() { return isScrolledToBottom; } }

 

至此已經介紹完了,為了形象的表示smoothScrollTo和scrollTo方法調用時兩種監聽方式的結果下面加上log,這里不想看的可以不往下看了

@Override protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { super.onOverScrolled(scrollX, scrollY, clampedX, clampedY); if (scrollY == 0) { isScrolledToTop = clampedY; isScrolledToBottom = false;  System.out.println("onOverScrolled isScrolledToTop:" + isScrolledToTop); } else { isScrolledToTop = false; isScrolledToBottom = clampedY; System.out.println("onOverScrolled isScrolledToBottom:" + isScrolledToBottom); } notifyScrollChangedListeners(); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt);
   // 這個log可以研究ScrollView的上下padding對結果的影響 System.out.println(
"onScrollChanged getScrollY():" + getScrollY() + " t: " + t + " paddingTop: " + getPaddingTop()); if (getScrollY() == 0) { isScrolledToTop = true; isScrolledToBottom = false; System.out.println("onScrollChanged isScrolledToTop:" + isScrolledToTop); } else if (getScrollY() + getHeight() - getPaddingTop() - getPaddingBottom() == getChildAt(0).getHeight()) { isScrolledToBottom = true; System.out.println("onScrollChanged isScrolledToBottom:" + isScrolledToBottom); isScrolledToTop = false; } else { isScrolledToTop = false; isScrolledToBottom = false; } notifyScrollChangedListeners(); }

下面我們看下具體的執行結果:

1. 手動滑動到底部的情況--->兩種方式都監聽到了

  

2. 手動滑動到頂部的情況--->兩種方式都監聽到了

  

3. 調用smoothScrollTo(0, Integer.MAX_VALUE)或者scrollTo(0, Integer.MAX_VALUE)滑動到底部的情況

    --->只有onScrollChanged方法監聽到滑動到底部

  

4.  調用smoothScrollTo(0, 0)或者scrollTo(0, 0)滑動到頂部的情況

    --->只有onScrollChanged方法監聽到滑動到底部

  

 

感悟:

  很多細小的知識,我們平時總是因為開發的時候太忙來不及去深究,但是作為開發者我們還是要對技術保持嚴謹,需要通過自己的實戰形成自己的經驗,有些很細小的知識可能在關鍵時候起到意向不到的作用,如果平時注意積累,到時候會事半功倍。

  文章寫得不專業,如果讀者覺得有用,記得點贊,也歡迎指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM