5

我想在我的應用中實現類似iOS的反彈超滾效果。iOS喜歡在Android上過度滾動效果

我碰到這個link,這表明創建了一個自定義ScrollView。但問題是,當我快速上下滾動時,它工作正常,但只要我拉下屏幕的頂部或底部,它就會卡住,效果不再起作用。

由於該種動畫的一個例子,我想實現你可以看看這個:

這是我目前擁有的代碼:

public class ObservableScrollView extends ScrollView 
{ 
    private static final int MAX_Y_OVERSCROLL_DISTANCE = 150; 

    private Context mContext; 
    private int mMaxYOverscrollDistance; 

    public ObservableScrollView(Context context) 
    { 
     super(context); 
     mContext = context; 
     initBounceScrollView(); 
    } 

    public ObservableScrollView(Context context, AttributeSet attrs) 
    { 
     super(context, attrs); 
     mContext = context; 
     initBounceScrollView(); 
    } 

    public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) 
    { 
     super(context, attrs, defStyle); 
     mContext = context; 
     initBounceScrollView(); 
    } 

    private void initBounceScrollView() 
    { 
     //get the density of the screen and do some maths with it on the max overscroll distance 
     //variable so that you get similar behaviors no matter what the screen size 

     final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); 
     final float density = metrics.density; 

     mMaxYOverscrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE); 
    } 

    @Override 
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) 
    { 
     //This is where the magic happens, we have replaced the incoming maxOverScrollY with our own custom variable mMaxYOverscrollDistance; 
     return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxYOverscrollDistance, isTouchEvent); 
    } 
} 

回答

13

我已經趕緊把一起基於一個簡單的解決方案CoordinatorLayout.Behavior。這並不完美,你可以花一些時間微調一下,但這並不壞。但無論如何,結果應該是這個樣子:

enter image description here

作爲一個小側面說明之前,我開始尋找答案:我強烈建議您從支持庫,而不是一個正常的ScrollView使用NestedScrollView。它們以任何方式相同,但NestedScrollView在較低API級別上實現了正確的嵌套滾動行爲。

不管怎樣,讓我們​​開始對我的回答:我想出瞭解決方案將與任何可滾動容器中工作,無論是ScrollViewListViewRecyclerView,你不需要任何的子類來Views實現它。

首先,你需要谷歌的設計支持庫添加到您的項目,如果你是不是已經在使用它:

compile 'com.android.support:design:25.0.1' 

請記住,如果你不是的方式瞄準API 25級(你應該),那麼您需要包含API級別的最新版本(例如,針對API級別24的compile 'com.android.support:design:24.2.0')。

無論滾動容器您使用的需要裹在你的佈局CoordinatorLayout。在我的例子我使用的是NestedScrollView

<?xml version="1.0" encoding="utf-8"?> 
<android.support.design.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent"> 

    <android.support.v4.widget.NestedScrollView 
     android:layout_width="match_parent" 
     android:layout_height="match_parent"> 

     <!-- content --> 

    </android.support.v4.widget.NestedScrollView> 

</android.support.design.widget.CoordinatorLayout> 

CoordinatorLayout允許您將Behavior分配給其直接子視圖。在這種情況下,我們將爲NestedScrollView分配一個Behavior,這將實施超滾動反彈效果。

就讓我們來看看在Behavior的代碼:

public class OverScrollBounceBehavior extends CoordinatorLayout.Behavior<View> { 

    private int mOverScrollY; 

    public OverScrollBounceBehavior() { 
    } 

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

    @Override 
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { 
     mOverScrollY = 0; 
     return true; 
    } 

    @Override 
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { 
     if (dyUnconsumed == 0) { 
      return; 
     } 

     mOverScrollY -= dyUnconsumed; 
     final ViewGroup group = (ViewGroup) target; 
     final int count = group.getChildCount(); 
     for (int i = 0; i < count; i++) { 
      final View view = group.getChildAt(i); 
      view.setTranslationY(mOverScrollY); 
     } 
    } 

    @Override 
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) { 
     final ViewGroup group = (ViewGroup) target; 
     final int count = group.getChildCount(); 
     for (int i = 0; i < count; i++) { 
      final View view = group.getChildAt(i); 
      ViewCompat.animate(view).translationY(0).start(); 
     } 
    } 
} 

解釋Behavior是什麼,以及它們是如何工作超出了這個答案的範圍,所以我只是要趕緊解釋上面什麼代碼呢。 Behavior攔截在CoordinatorLayout的直接子女中發生的所有滾動事件。在onStartNestedScroll()方法中,我們返回true,因爲我們對任何滾動事件感興趣。在onNestedScroll()中,我們看看dyUnconsumed參數,它告訴我們滾動容器沒有消耗多少垂直滾動(換句話說是滾動滾動),然後將滾動容器的子代翻譯爲該數量。由於我們只是獲得delta值,因此我們需要在mOverscrollY變量中總結所有這些值。當滾動事件停止時調用onStopNestedScroll()。這是當我們將滾動容器的所有孩子恢復到原始位置時的動畫。

要分配給Behavior我們需要使用layout_behavior XML屬性,並傳入我們要使用的Behavior的全類名NestedScrollView。在我的例子中,上面的類是包com.github.wrdlbrnft.testapp,所以我必須設置com.github.wrdlbrnft.testapp.OverScrollBounceBehavior作爲值。 layout_behavior是的CoordinatorLayout所以我們需要用正確的命名空間前綴是一個自定義屬性:

<?xml version="1.0" encoding="utf-8"?> 
<android.support.design.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res-auto" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent"> 

    <android.support.v4.widget.NestedScrollView 
     android:layout_width="match_parent" 
     android:layout_height="match_parent" 
     app:layout_behavior="com.github.wrdlbrnft.testapp.OverScrollBounceBehavior"> 

     <!-- content --> 

    </android.support.v4.widget.NestedScrollView> 

</android.support.design.widget.CoordinatorLayout> 

通知我的CoordinatorLayout添加的命名空間和app:layout_behavior屬性我在NestedScrollView增加。

這就是你所要做的!雖然這個答案結果比我想要的要長,但我跳過了一些涵蓋了CoordinatorLayoutBehaviors的基礎知識。所以如果你對這些不熟悉或者有任何其他問題可以隨意問。

+0

嗨@Xaver Kapler感謝您的更新。還有一個疑問是,在這個我們手動拉下來彈跳。但在所附的屏幕gif中,如果我們滾動並且到達底部,它會反彈回來。同樣在頂部也通過下載應用程序進行檢查。在scrollview中如何實現它? – Star

+0

@明星我不明白你在說什麼。我的答案已經包含了一個解決方案,在您的答案中複製gif。你還想要什麼? –

+0

@Shadow肯定是。所有你需要的是相同的數學來以指數方式來限制過度滾動。 –

0

使用此

Private ScrollView scrMain; 

scrMain = (ScrollView) v.findViewbyId(R.id.scrMain); 

OverScrollDecorHandler.setScrollView(scrMain);