自定义 CoordinatorLayout.Behavior 和 RecyclerView 滚动问题
Custom CoordinatorLayout.Behavior and RecyclerView scroll issue
我有一个 CoordinatorLayout
和两个 children,一个 View
作为 header 和一个 RecyclerView
:
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
app:layout_behavior="some.package.AlphaBehavior">
<ImageView
android:id="@+id/header_iv"
style="@style/some_style"/>
<TextView
android:id="@+id/header_retails_tv"
style="@style/some_style_tv"
android:text="@string/some_text"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
</android.support.design.widget.CoordinatorLayout>
我将填充动态设置为 RecyclerView
,大小为 header,并将 clipToPadding
设置为 false
,因此显示 RecyclerView
header 下方,当用户向上滚动时,RecyclerView
显示在 header 视图上方。
我做了一个自定义 CoordinatorLayout.Behavior
,以便在用户向上滚动 list
时淡出视图,并在 header 必须可见时淡入同样,AlphaBehavior
:
public class AlphaBehavior extends CoordinatorLayout.Behavior {
private float alpha = 1.0f;
private float scrolly = 0.f;
private int headerSize = 0;
private Animation defaultFadeInAnimation;
public AlphaBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
defaultFadeInAnimation = AnimationUtils.loadAnimation(context, android.R.anim.fade_in);
}
public void setHeaderSize(int headerSize) {
this.headerSize = headerSize;
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof RecyclerView;
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
scrolly += dyConsumed;
Log.d(Constants.TAG, dyConsumed + "/" + dyUnconsumed + "/" + scrolly);
float totalScrollY = ((RecyclerView)target).computeVerticalScrollOffset();
Log.d(Constants.TAG, "totalScrollY:" + totalScrollY);
alpha = (headerSize - totalScrollY) / headerSize;
if (alpha < 0.f) alpha = 0.f;
if (alpha > 1.0f) alpha = 1.f;
if (dyConsumed < 0 && totalScrollY > headerSize) {
alpha = 0.f;
}
Log.d(Constants.TAG, "alpha:" + alpha);
child.setAlpha(alpha);
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
int pos = ((LinearLayoutManager)((RecyclerView)target).getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Log.d(Constants.TAG, "pos:" + pos);
if (pos == 0 && child.getAlpha() == 0.f) {
child.startAnimation(defaultFadeInAnimation);
}
}
// overriding this in case we don't the other events are not called
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
}
但我遇到了一个问题:如果用户滚动得非常快,则行为事件不会正确调用。 scrollY
成员不是总卷轴的 well-related 并且 totalScrollY
成员(从 RecyclerView
计算卷轴获得)不正确。即使我试图在 onStopNestedScroll
事件中找到 firstCompletelyVisibleItem
,但是当 recyclerView
到达列表的开头时它返回位置 2 或 3。
最后,我使用 OnScrollListener
而不是 CoordinatorLayout.Behavior
解决了这个问题,而且效果很好。我放了代码,也许对某人有用:
隐藏视图的自定义onScrollListener:
public class HideViewOnScrollListener extends RecyclerView.OnScrollListener {
private float alpha = 1.f;
private float scrolly = 0.f;
private int heightViewToHide;
private final View viewToHide;
public HideViewOnScrollListener(View viewToHide) {
this.viewToHide = viewToHide;
heightViewToHide = viewToHide.getHeight();
if (heightViewToHide == 0) {
ViewTreeObserver viewTreeObserver = viewToHide.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
heightViewToHide = viewToHide.getHeight();
if (heightViewToHide > 0)
viewToHide.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
scrolly += dy;
alpha = (heightViewToHide - scrolly) / heightViewToHide;
if (alpha < 0.f) alpha = 0.f;
if (alpha > 1.0f) alpha = 1.f;
if (dy < 0 && scrolly > heightViewToHide) {
alpha = 0.f;
}
viewToHide.setAlpha(alpha);
}
}
您可以通过这种方式添加到 RecyclerView:
recyclerView.addOnScrollListener(new HideViewOnScrollListener(viewToHide));
我有一个 CoordinatorLayout
和两个 children,一个 View
作为 header 和一个 RecyclerView
:
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
app:layout_behavior="some.package.AlphaBehavior">
<ImageView
android:id="@+id/header_iv"
style="@style/some_style"/>
<TextView
android:id="@+id/header_retails_tv"
style="@style/some_style_tv"
android:text="@string/some_text"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
</android.support.design.widget.CoordinatorLayout>
我将填充动态设置为 RecyclerView
,大小为 header,并将 clipToPadding
设置为 false
,因此显示 RecyclerView
header 下方,当用户向上滚动时,RecyclerView
显示在 header 视图上方。
我做了一个自定义 CoordinatorLayout.Behavior
,以便在用户向上滚动 list
时淡出视图,并在 header 必须可见时淡入同样,AlphaBehavior
:
public class AlphaBehavior extends CoordinatorLayout.Behavior {
private float alpha = 1.0f;
private float scrolly = 0.f;
private int headerSize = 0;
private Animation defaultFadeInAnimation;
public AlphaBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
defaultFadeInAnimation = AnimationUtils.loadAnimation(context, android.R.anim.fade_in);
}
public void setHeaderSize(int headerSize) {
this.headerSize = headerSize;
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof RecyclerView;
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
scrolly += dyConsumed;
Log.d(Constants.TAG, dyConsumed + "/" + dyUnconsumed + "/" + scrolly);
float totalScrollY = ((RecyclerView)target).computeVerticalScrollOffset();
Log.d(Constants.TAG, "totalScrollY:" + totalScrollY);
alpha = (headerSize - totalScrollY) / headerSize;
if (alpha < 0.f) alpha = 0.f;
if (alpha > 1.0f) alpha = 1.f;
if (dyConsumed < 0 && totalScrollY > headerSize) {
alpha = 0.f;
}
Log.d(Constants.TAG, "alpha:" + alpha);
child.setAlpha(alpha);
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
int pos = ((LinearLayoutManager)((RecyclerView)target).getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Log.d(Constants.TAG, "pos:" + pos);
if (pos == 0 && child.getAlpha() == 0.f) {
child.startAnimation(defaultFadeInAnimation);
}
}
// overriding this in case we don't the other events are not called
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
}
但我遇到了一个问题:如果用户滚动得非常快,则行为事件不会正确调用。 scrollY
成员不是总卷轴的 well-related 并且 totalScrollY
成员(从 RecyclerView
计算卷轴获得)不正确。即使我试图在 onStopNestedScroll
事件中找到 firstCompletelyVisibleItem
,但是当 recyclerView
到达列表的开头时它返回位置 2 或 3。
最后,我使用 OnScrollListener
而不是 CoordinatorLayout.Behavior
解决了这个问题,而且效果很好。我放了代码,也许对某人有用:
隐藏视图的自定义onScrollListener:
public class HideViewOnScrollListener extends RecyclerView.OnScrollListener {
private float alpha = 1.f;
private float scrolly = 0.f;
private int heightViewToHide;
private final View viewToHide;
public HideViewOnScrollListener(View viewToHide) {
this.viewToHide = viewToHide;
heightViewToHide = viewToHide.getHeight();
if (heightViewToHide == 0) {
ViewTreeObserver viewTreeObserver = viewToHide.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
heightViewToHide = viewToHide.getHeight();
if (heightViewToHide > 0)
viewToHide.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
scrolly += dy;
alpha = (heightViewToHide - scrolly) / heightViewToHide;
if (alpha < 0.f) alpha = 0.f;
if (alpha > 1.0f) alpha = 1.f;
if (dy < 0 && scrolly > heightViewToHide) {
alpha = 0.f;
}
viewToHide.setAlpha(alpha);
}
}
您可以通过这种方式添加到 RecyclerView:
recyclerView.addOnScrollListener(new HideViewOnScrollListener(viewToHide));