java iOS 喜欢 Android 上的滚动效果
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/40758635/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
iOS like over scroll effect on Android
提问by Star
I want to implement the iOS-like bounce overscroll effect in my app.
我想在我的应用程序中实现类似 iOS 的反弹过度滚动效果。
I came across this linkwhich suggests creating a custom ScrollView
. But the problem is that when I am scrolling up and down fast it's working fine but as soon as I pull the bottom or top of the screen it's just stuck and the effect is not working anymore.
我遇到了这个链接,它建议创建一个自定义ScrollView
. 但问题是,当我快速上下滚动时,它工作正常,但是一旦我拉动屏幕底部或顶部,它就会卡住,效果不再起作用。
As an example of the kind of animation I want to achieve you can look at this:
作为我想要实现的动画类型的示例,您可以看一下:
This is the code I currently have:
这是我目前拥有的代码:
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);
}
}
回答by Xaver Kapeller
I have quickly put together a simple solution based on a CoordinatorLayout.Behavior
. It's not perfect, you can maybe spend some time fine tuning it a bit, but it's not bad. Anyway the result should look something like this:
我很快就基于CoordinatorLayout.Behavior
. 它并不完美,您也许可以花一些时间对其进行微调,但还不错。无论如何,结果应该是这样的:
As a small side note before I start with the answer: I strongly recommend that you use the NestedScrollView
from the support library instead of a normal ScrollView
. They are identical in any way, but the NestedScrollView
implements correct nested scrolling behaviour on lower API levels.
在我开始回答之前,作为一个小小的旁注:我强烈建议您使用NestedScrollView
来自支持库的ScrollView
. 它们在任何方面都是相同的,但NestedScrollView
在较低的 API 级别上实现了正确的嵌套滚动行为。
Anyway let's start with my answer: The solution I came up with would work with any scrollable container, be it a ScrollView
, ListView
or RecyclerView
and you don't need to subclass any Views
to implement it.
无论如何,让我们用我的回答开始:我想出了解决方案将与任何可滚动容器中工作,无论是ScrollView
,ListView
还是RecyclerView
,你不需要任何的子类Views
来实现它。
First you need to add Google's Design Support Library to your project if you aren't already using it:
首先,如果您还没有使用 Google 的设计支持库,则需要将其添加到您的项目中:
compile 'com.android.support:design:25.0.1'
Remember that if you aren't targeting API level 25 (which you should by the way) then you need to include the newest version for your API level (eg. compile 'com.android.support:design:24.2.0'
for API level 24).
请记住,如果您的目标不是 API 级别 25(顺便说一下,您应该这样做),那么您需要包含 API 级别的最新版本(例如compile 'com.android.support:design:24.2.0'
API 级别 24)。
Whatever scrollable container you are using needs to wrapped in a CoordinatorLayout
in your layout. In my example I am using a NestedScrollView
:
您使用的任何可滚动容器都需要包含在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>
The CoordinatorLayout
allows you to assign a Behavior
to its direct child views. In this case we are going to assign a Behavior
to the NestedScrollView
which is going implement the overscroll bounce effect.
将CoordinatorLayout
允许你指定一个Behavior
到它的直接子视图。在这种情况下,我们将分配一个Behavior
来NestedScrollView
实现过度滚动反弹效果。
Let's just take a look at the code of the 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();
}
}
}
Explaining what a Behavior
is and how they work is beyond the scope of this answer so I am just going to quickly explain what the above code does. The Behavior
intercepts all scroll events that happen in the direct children of the CoordinatorLayout
. In the onStartNestedScroll()
method we return true
since we are interested in any scroll events. In onNestedScroll()
we look at the dyUnconsumed
parameter which tells us how much of the vertical scroll was not consumed by the scrolling container (in other words overscroll) and then translate the children of the scrolling container by that amount. Since we are just getting delta values we need to sum up all of them in the mOverscrollY
variable. onStopNestedScroll()
is called when the scrolling event stops. This is when we animate all children of the scrolling container back to their original position.
解释 aBehavior
是什么以及它们如何工作超出了本答案的范围,因此我将快速解释上述代码的作用。在Behavior
中截获的直接孩子发生的所有滚动事件CoordinatorLayout
。在onStartNestedScroll()
我们返回的方法中,true
因为我们对任何滚动事件感兴趣。在onNestedScroll()
我们看一下dyUnconsumed
它告诉我们多少垂直滚动的不是由滚动容器消耗参数通过量(即反弹时),然后转化滚动容器的孩子。由于我们只是获得 delta 值,因此我们需要在mOverscrollY
变量中对所有这些值求和。onStopNestedScroll()
当滚动事件停止时调用。这是当我们将滚动容器的所有子级动画回到它们的原始位置时。
To assign the Behavior
to the NestedScrollView
we need to use the layout_behavior
xml attribute and pass in the full class name of the Behavior
we want to use. In my example the above class is in the package com.github.wrdlbrnft.testapp
so I have to set com.github.wrdlbrnft.testapp.OverScrollBounceBehavior
as value. layout_behavior
is a custom attribute of the CoordinatorLayout
so we need to prefix it with the correct namespace:
要将 分配Behavior
给 ,NestedScrollView
我们需要使用layout_behavior
xml 属性并传入Behavior
我们要使用的完整类名。在我的例子中,上面的类在包中,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>
Notice the namespace I added on the CoordinatorLayout
and the app:layout_behavior
attribute I added on the NestedScrollView
.
注意我在 上添加的命名空间CoordinatorLayout
和app:layout_behavior
我在NestedScrollView
.
And that is all you have to do! While this answer turned out to be longer than I intended I skipped over some of the basics concering the CoordinatorLayout
and Behaviors
. So if you are unfamiliar with these or have any other further questions feel free to ask.
这就是你所要做的!虽然结果证明这个答案比我预期的要长,但我跳过了一些关于CoordinatorLayout
和的基础知识Behaviors
。因此,如果您不熟悉这些或有任何其他问题,请随时提出。
回答by lincollincol
Thanks to Xaver Kapeller, I have written my solution with overriding fling and little additions, using kotlinand androidx
感谢Xaver Kapeller,我使用kotlin和androidx编写了我的解决方案,其中包含覆盖fling和少量添加
Add coordinator dependency
添加协调器依赖
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
Create a new class that extends CoordinatorLayout.Behavior
创建一个扩展 CoordinatorLayout.Behavior 的新类
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
class OverScrollBehavior(context: Context, attributeSet: AttributeSet)
: CoordinatorLayout.Behavior<View>() {
companion object {
private const val OVER_SCROLL_AREA = 4
}
private var overScrollY = 0
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: View,
directTargetChild: View,
target: View,
axes: Int,
type: Int
): Boolean {
overScrollY = 0
return true
}
override fun onNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: View,
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int,
consumed: IntArray
) {
if (dyUnconsumed == 0) {
return
}
overScrollY -= (dyUnconsumed/OVER_SCROLL_AREA)
val group = target as ViewGroup
val count = group.childCount
for (i in 0 until count) {
val view = group.getChildAt(i)
view.translationY = overScrollY.toFloat()
}
}
override fun onStopNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: View,
target: View,
type: Int
) {
// Smooth animate to 0 when the user stops scrolling
moveToDefPosition(target)
}
override fun onNestedPreFling(
coordinatorLayout: CoordinatorLayout,
child: View,
target: View,
velocityX: Float,
velocityY: Float
): Boolean {
// Scroll view by inertia when current position equals to 0
if (overScrollY == 0) {
return false
}
// Smooth animate to 0 when user fling view
moveToDefPosition(target)
return true
}
private fun moveToDefPosition(target: View) {
val group = target as ViewGroup
val count = group.childCount
for (i in 0 until count) {
val view = group.getChildAt(i)
ViewCompat.animate(view)
.translationY(0f)
.setInterpolator(AccelerateDecelerateInterpolator())
.start()
}
}
}
Create XML file with CoordinatorLayout and NestedScrollView
使用 CoordinatorLayout 和 NestedScrollView 创建 XML 文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior=".OverScrollBehavior">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAlignment="center"
android:padding="10dp"
android:text="@string/Lorem"/>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And don't forget to add
并且不要忘记添加
app:layout_behavior=".OverScrollBehavior" // Or your file name
field to your NestedScrollView XML markup
字段到您的 NestedScrollView XML 标记
回答by Mohamed
Use this
用这个
Private ScrollView scrMain;
scrMain = (ScrollView) v.findViewbyId(R.id.scrMain);
OverScrollDecorHandler.setScrollView(scrMain);