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