如何在 android 中使用滚动视图实现 'quick return'
How to implement 'quick return' with scrollview in android
我想用 ScrollView 实现 'Quick return header'。我的页眉不是工具栏,是FrameLayout..
基本上你需要使用Nested Scrolling
(添加在API 21)。此功能允许子级(滚动的)与其父级(通常是 CoordinatorLayout
)通信。但是,为了拥有嵌套滚动,您需要:
- 父视图必须实现 NestedScrollingParent 接口
- 子视图必须实现 NestedScrollingChild 接口
CoordinatorLayout
实现了NestedScrollingParent
,但不幸的是,ScrollView
没有实现NestedScrollingChild
接口。
因此,您无法使用 SCROLLVIEW 进行嵌套滚动(在 API 21 之前:查看底部的编辑部分)。但是,您可以使用基本上是实现 NestedScrollingChild
接口的 ScrollView
的 NestedScrollView
。
之后,您需要创建自定义 CoordinatorLayout.Behavior
并覆盖方法 onStartNestedScroll
、onNestedPreScroll
和 onNestedScroll
。您可以在下面看到符合您需要的代码(我从 this example 中获取它并进行了一些更改)。
假设您查看的是 FramLayout
您可以在您的布局中做这样的事情:
<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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="84dp"
android:background="#FF0000"
app:layout_behavior=".QuickHideBehavior">
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
如您所见,自定义行为已附加到 FrameLayout
。那么你需要这样修改上面的QuickHideBehavior例子:
public class QuickHideBehavior extends CoordinatorLayout.Behavior<View> {
private static final int DIRECTION_UP = 1;
private static final int DIRECTION_DOWN = -1;
/* Tracking direction of user motion */
private int mScrollingDirection;
/* Tracking last threshold crossed */
private int mScrollTrigger;
/* Accumulated scroll distance */
private int mScrollDistance;
/* Distance threshold to trigger animation */
private int mScrollThreshold;
private ObjectAnimator mAnimator;
//Required to instantiate as a default behavior
public QuickHideBehavior() {
}
//Required to attach behavior via XML
public QuickHideBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme()
.obtainStyledAttributes(new int[] {R.attr.actionBarSize});
//Use half the standard action bar height
mScrollThreshold = a.getDimensionPixelSize(0, 0) / 2;
a.recycle();
}
//Called before a nested scroll event. Return true to declare interest
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
View child, View directTargetChild, View target,
int nestedScrollAxes) {
//We have to declare interest in the scroll to receive further events
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
//Called before the scrolling child consumes the event
// We can steal all/part of the event by filling in the consumed array
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout,
View child, View target,
int dx, int dy,
int[] consumed) {
//Determine direction changes here
if (dy > 0 && mScrollingDirection != DIRECTION_UP) {
mScrollingDirection = DIRECTION_UP;
mScrollDistance = 0;
} else if (dy < 0 && mScrollingDirection != DIRECTION_DOWN) {
mScrollingDirection = DIRECTION_DOWN;
mScrollDistance = 0;
}
}
//Called after the scrolling child consumes the event, with amount consumed
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout,
View child, View target,
int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
//Consumed distance is the actual distance traveled by the scrolling view
mScrollDistance += dyConsumed;
if (mScrollDistance > mScrollThreshold
&& mScrollTrigger != DIRECTION_UP) {
//Hide the target view
mScrollTrigger = DIRECTION_UP;
restartAnimator(child, getTargetHideValue(coordinatorLayout, child));
} else if (mScrollDistance < -mScrollThreshold
&& mScrollTrigger != DIRECTION_DOWN) {
//Return the target view
mScrollTrigger = DIRECTION_DOWN;
restartAnimator(child, 0f);
}
}
//Called after the scrolling child handles the fling
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout,
View child, View target,
float velocityX, float velocityY,
boolean consumed) {
//We only care when the target view is already handling the fling
if (consumed) {
if (velocityY > 0 && mScrollTrigger != DIRECTION_UP) {
mScrollTrigger = DIRECTION_UP;
restartAnimator(child, getTargetHideValue(coordinatorLayout, child));
} else if (velocityY < 0 && mScrollTrigger != DIRECTION_DOWN) {
mScrollTrigger = DIRECTION_DOWN;
restartAnimator(child, 0f);
}
}
return false;
}
/* Helper Methods */
//Helper to trigger hide/show animation
private void restartAnimator(View target, float value) {
if (mAnimator != null) {
mAnimator.cancel();
mAnimator = null;
}
mAnimator = ObjectAnimator
.ofFloat(target, View.TRANSLATION_Y, value)
.setDuration(250);
/*mAnimator = ObjectAnimator.ofFloat(target, "alpha", 0f, 1f).setDuration(250);*/
mAnimator.start();
}
private float getTargetHideValue(ViewGroup parent, View target) {
if(target instanceof FrameLayout)
return -target.getHeight();
return 0f;
}
}
大功告成。
我建议你看一下 here for more examples or this video 两个 Dave Smith
编辑: 实际上,自API 21,ScrollView
(或ListView
)支持嵌套滚动,但您需要启用它:
scrollView.setNestedScrollingEnabled(true);
你当然需要设置
minSdkVersion > 20
我想用 ScrollView 实现 'Quick return header'。我的页眉不是工具栏,是FrameLayout..
基本上你需要使用Nested Scrolling
(添加在API 21)。此功能允许子级(滚动的)与其父级(通常是 CoordinatorLayout
)通信。但是,为了拥有嵌套滚动,您需要:
- 父视图必须实现 NestedScrollingParent 接口
- 子视图必须实现 NestedScrollingChild 接口
CoordinatorLayout
实现了NestedScrollingParent
,但不幸的是,ScrollView
没有实现NestedScrollingChild
接口。
因此,您无法使用 SCROLLVIEW 进行嵌套滚动(在 API 21 之前:查看底部的编辑部分)。但是,您可以使用基本上是实现 NestedScrollingChild
接口的 ScrollView
的 NestedScrollView
。
之后,您需要创建自定义 CoordinatorLayout.Behavior
并覆盖方法 onStartNestedScroll
、onNestedPreScroll
和 onNestedScroll
。您可以在下面看到符合您需要的代码(我从 this example 中获取它并进行了一些更改)。
假设您查看的是 FramLayout
您可以在您的布局中做这样的事情:
<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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="84dp"
android:background="#FF0000"
app:layout_behavior=".QuickHideBehavior">
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
如您所见,自定义行为已附加到 FrameLayout
。那么你需要这样修改上面的QuickHideBehavior例子:
public class QuickHideBehavior extends CoordinatorLayout.Behavior<View> {
private static final int DIRECTION_UP = 1;
private static final int DIRECTION_DOWN = -1;
/* Tracking direction of user motion */
private int mScrollingDirection;
/* Tracking last threshold crossed */
private int mScrollTrigger;
/* Accumulated scroll distance */
private int mScrollDistance;
/* Distance threshold to trigger animation */
private int mScrollThreshold;
private ObjectAnimator mAnimator;
//Required to instantiate as a default behavior
public QuickHideBehavior() {
}
//Required to attach behavior via XML
public QuickHideBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme()
.obtainStyledAttributes(new int[] {R.attr.actionBarSize});
//Use half the standard action bar height
mScrollThreshold = a.getDimensionPixelSize(0, 0) / 2;
a.recycle();
}
//Called before a nested scroll event. Return true to declare interest
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
View child, View directTargetChild, View target,
int nestedScrollAxes) {
//We have to declare interest in the scroll to receive further events
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
//Called before the scrolling child consumes the event
// We can steal all/part of the event by filling in the consumed array
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout,
View child, View target,
int dx, int dy,
int[] consumed) {
//Determine direction changes here
if (dy > 0 && mScrollingDirection != DIRECTION_UP) {
mScrollingDirection = DIRECTION_UP;
mScrollDistance = 0;
} else if (dy < 0 && mScrollingDirection != DIRECTION_DOWN) {
mScrollingDirection = DIRECTION_DOWN;
mScrollDistance = 0;
}
}
//Called after the scrolling child consumes the event, with amount consumed
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout,
View child, View target,
int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
//Consumed distance is the actual distance traveled by the scrolling view
mScrollDistance += dyConsumed;
if (mScrollDistance > mScrollThreshold
&& mScrollTrigger != DIRECTION_UP) {
//Hide the target view
mScrollTrigger = DIRECTION_UP;
restartAnimator(child, getTargetHideValue(coordinatorLayout, child));
} else if (mScrollDistance < -mScrollThreshold
&& mScrollTrigger != DIRECTION_DOWN) {
//Return the target view
mScrollTrigger = DIRECTION_DOWN;
restartAnimator(child, 0f);
}
}
//Called after the scrolling child handles the fling
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout,
View child, View target,
float velocityX, float velocityY,
boolean consumed) {
//We only care when the target view is already handling the fling
if (consumed) {
if (velocityY > 0 && mScrollTrigger != DIRECTION_UP) {
mScrollTrigger = DIRECTION_UP;
restartAnimator(child, getTargetHideValue(coordinatorLayout, child));
} else if (velocityY < 0 && mScrollTrigger != DIRECTION_DOWN) {
mScrollTrigger = DIRECTION_DOWN;
restartAnimator(child, 0f);
}
}
return false;
}
/* Helper Methods */
//Helper to trigger hide/show animation
private void restartAnimator(View target, float value) {
if (mAnimator != null) {
mAnimator.cancel();
mAnimator = null;
}
mAnimator = ObjectAnimator
.ofFloat(target, View.TRANSLATION_Y, value)
.setDuration(250);
/*mAnimator = ObjectAnimator.ofFloat(target, "alpha", 0f, 1f).setDuration(250);*/
mAnimator.start();
}
private float getTargetHideValue(ViewGroup parent, View target) {
if(target instanceof FrameLayout)
return -target.getHeight();
return 0f;
}
}
大功告成。
我建议你看一下 here for more examples or this video 两个 Dave Smith
编辑: 实际上,自API 21,ScrollView
(或ListView
)支持嵌套滚动,但您需要启用它:
scrollView.setNestedScrollingEnabled(true);
你当然需要设置
minSdkVersion > 20