CollapsingToolbarLayout无法识别滚动条

我创build了一个简单的CollapsingToolbarLayout ,它像一个魅力。 我的问题是,如果我尝试在嵌套滚动视图上使用滚动滚动,当我松开手指时它会停止。 正常的滚动工作就像它应该。

我的活动代码不变=>自动生成的空活动。 (我只是在android studio中点击创build新的空行为,然后编辑XML)。

我在这里读到,图像视图本身的滚动手势是越野车,但不是,滚动本身是越野车: 看到这里 。

我尝试通过Java代码激活“平滑滚动” 。 似乎如果我滚动足够远,imageview不再可见,扔手势然后被识别。

TLDR:只要imageview可见,为什么投掷手势不工作? 我的XML代码如下所示:

<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:fitsSystemWindows="true"> <android.support.design.widget.AppBarLayout android:id="@+id/profile_app_bar_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" android:fitsSystemWindows="true"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/profile_collapsing_toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll|exitUntilCollapsed" app:contentScrim="?attr/colorPrimary" app:expandedTitleMarginStart="48dp" app:expandedTitleMarginEnd="64dp" android:fitsSystemWindows="true"> <ImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="420dp" android:scaleType="centerCrop" android:fitsSystemWindows="true" android:src="@drawable/headerbg" android:maxHeight="192dp" app:layout_collapseMode="parallax"/> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:layout_collapseMode="pin" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" app:layout_anchor="@id/profile_app_bar_layout" app:layout_anchorGravity="bottom|right|end" android:layout_height="@dimen/fab_size_normal" android:layout_width="@dimen/fab_size_normal" app:elevation="2dp" app:pressedTranslationZ="12dp" android:layout_marginRight="8dp" android:layout_marginEnd="8dp"/> <android.support.v4.widget.NestedScrollView android:id="@+id/profile_content_scroll" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:layout_gravity="fill_vertical" android:minHeight="192dp" android:overScrollMode="ifContentScrolls" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/LoremIpsum"/> </RelativeLayout> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout> 

我用CollapsingToolbarLayoutImageView内部和NestedScrollView完全相同的问题。 当手指被释放时,投掷滚动停止。

不过,我注意到了一些奇怪的东西。 如果你用OnClickListener(例如button)从视图开始滚动,那么投影滚动就完美了。

所以我用一个奇怪的解决scheme来修复它。 在NestedScrollView的直接子节点上设置OnClickListener(不执行任何操作)。 那就完美了!

 <android.support.v4.widget.NestedScrollView 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" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:id="@+id/content_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <!-- Page Content --> </LinearLayout> </android.support.v4.widget.NestedScrollView> 

给直接子(LinearLayout)一个id并在Activity中设置OnClickListener

 ViewGroup mContentContainer = (ViewGroup) findViewById(R.id.content_container); mContentContainer.setOnClickListener(this); @Override public void onClick(View view) { int viewId = view.getId(); } 

笔记:

使用Support Design Library 25.0.1进行testing

CollapsingToolbarLayout with scrollFlags =“scroll | enterAlwaysCollapsed”

我知道这个问题在一年前被问过,但是这个问题似乎还没有在支持/devise库中得到解决。 您可以为此问题加注星标,以便在优先级队列中向前移动。

也就是说,我尝试了大部分的解决scheme,包括patrick-iv的解决scheme,但都没有成功。 我能够开始工作的唯一方法就是模仿onPreNestedScroll() ,并在onPreNestedScroll()中检测到一定的条件集的情况下以编程方式调用它。 在我debugging的几个小时内,我注意到onNestedFling()从来没有被调用向上(向下滚动)一扔,似乎过早消耗。 我不能100%肯定地说,这对于100%的实现是有效的,但是对于我的使用来说,它的工作效果已经足够好了,所以我最终为此付出了沉重的代价,即使它非常黑客,而且绝对不是我想要做的。

 public class NestedScrollViewBehavior extends AppBarLayout.Behavior { // Lower value means fling action is more easily triggered static final int MIN_DY_DELTA = 4; // Lower values mean less velocity, higher means higher velocity static final int FLING_FACTOR = 20; int mTotalDy; int mPreviousDy; WeakReference<AppBarLayout> mPreScrollChildRef; @Override public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) { super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); // Reset the total fling delta distance if the user starts scrolling back up if(dy < 0) { mTotalDy = 0; } // Only track move distance if the movement is positive (since the bug is only present // in upward flings), equal to the consumed value and the move distance is greater // than the minimum difference value if(dy > 0 && consumed[1] == dy && MIN_DY_DELTA < Math.abs(mPreviousDy - dy)) { mPreScrollChildRef = new WeakReference<>(child); mTotalDy += dy * FLING_FACTOR; } mPreviousDy = dy; } @Override public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) { // Stop any previous fling animations that may be running onNestedFling(parent, child, target, 0, 0, false); return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes); } @Override public void onStopNestedScroll(CoordinatorLayout parent, AppBarLayout abl, View target) { if(mTotalDy > 0 && mPreScrollChildRef != null && mPreScrollChildRef.get() != null) { // Programmatically trigger fling if all conditions are met onNestedFling(parent, mPreScrollChildRef.get(), target, 0, mTotalDy, false); mTotalDy = 0; mPreviousDy = 0; mPreScrollChildRef = null; } super.onStopNestedScroll(parent, abl, target); } } 

并将其应用于AppBar

 AppBarLayout scrollView = (AppBarLayout)findViewById(R.id.appbar); CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams)scrollView.getLayoutParams(); params.setBehavior(new NestedScrollViewBehavior()); 

CheeseSquare演示: 之前

我尝试了Floofer的解决scheme,但对我来说仍然不够好。 所以我想出了一个更好的行为版本。 当抛出时,AppBarLayout现在扩展并且平滑地崩溃。

注意:我使用reflection来破解我的方式,所以它可能不适用于与25.0.0版本不同的Androiddevise库。

 public class SmoothScrollBehavior extends AppBarLayout.Behavior { private static final String TAG = "SmoothScrollBehavior"; //The higher this value is, the faster the user must scroll for the AppBarLayout to collapse by itself private static final int SCROLL_SENSIBILITY = 5; //The real fling velocity calculation seems complex, in this case it is simplified with a multiplier private static final int FLING_VELOCITY_MULTIPLIER = 60; private boolean alreadyFlung = false; private boolean request = false; private boolean expand = false; private int velocity = 0; private int nestedScrollViewId; public SmoothScrollBehavior(int nestedScrollViewId) { this.nestedScrollViewId = nestedScrollViewId; } @Override public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) { super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); if(Math.abs(dy) >= SCROLL_SENSIBILITY) { request = true; expand = dy < 0; velocity = dy * FLING_VELOCITY_MULTIPLIER; } else { request = false; } } @Override public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) { request = false; return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes); } @Override public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout, View target) { if(request) { NestedScrollView nestedScrollView = (NestedScrollView) coordinatorLayout.findViewById(nestedScrollViewId); if (expand) { //No need to force expand if it is already ready expanding if (nestedScrollView.getScrollY() > 0) { int finalY = getPredictedScrollY(nestedScrollView); if (finalY <= 0) { //since onNestedFling does not work to expand the AppBarLayout, we need to manually expand it expandAppBarLayoutWithVelocity(coordinatorLayout, appBarLayout, velocity); } } } else { //onNestedFling will collapse the AppBarLayout with an animation time relative to the velocity onNestedFling(coordinatorLayout, appBarLayout, target, 0, velocity, true); if(!alreadyFlung) { //TODO wait for AppBarLayout to be collapsed before scrolling for even smoother visual nestedScrollView.fling(velocity); } } } alreadyFlung = false; super.onStopNestedScroll(coordinatorLayout, appBarLayout, target); } private int getPredictedScrollY(NestedScrollView nestedScrollView) { int finalY = 0; try { //With reflection, we can get the ScrollerCompat from the NestedScrollView to predict where the scroll will end Field scrollerField = nestedScrollView.getClass().getDeclaredField("mScroller"); scrollerField.setAccessible(true); Object object = scrollerField.get(nestedScrollView); ScrollerCompat scrollerCompat = (ScrollerCompat) object; finalY = scrollerCompat.getFinalY(); } catch (Exception e ) { e.printStackTrace(); //If the reflection fails, it will return 0, which means the scroll has reached top Log.e(TAG, "Failed to get mScroller field from NestedScrollView through reflection. Will assume that the scroll reached the top."); } return finalY; } private void expandAppBarLayoutWithVelocity(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout, float velocity) { try { //With reflection, we can call the private method of Behavior that expands the AppBarLayout with specified velocity Method animateOffsetTo = getClass().getSuperclass().getDeclaredMethod("animateOffsetTo", CoordinatorLayout.class, AppBarLayout.class, int.class, float.class); animateOffsetTo.setAccessible(true); animateOffsetTo.invoke(this, coordinatorLayout, appBarLayout, 0, velocity); } catch (Exception e) { e.printStackTrace(); //If the reflection fails, we fall back to the public method setExpanded that expands the AppBarLayout with a fixed velocity Log.e(TAG, "Failed to get animateOffsetTo method from AppBarLayout.Behavior through reflection. Falling back to setExpanded."); appBarLayout.setExpanded(true, true); } } @Override public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) { alreadyFlung = true; return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY); } } 

要使用它,请为您的AppBarLayout设置一个新的行为。

 AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.app_bar); CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams(); params.setBehavior(new SmoothScrollBehavior(R.id.nested_scroll_view)); 

这个答案为我解决了这个问题。 创build一个像这样的自定义AppBarLayout.Behavior

 public final class FlingBehavior extends AppBarLayout.Behavior { private static final int TOP_CHILD_FLING_THRESHOLD = 3; private boolean isPositive; public FlingBehavior() { } public FlingBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) { if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) { velocityY = velocityY * -1; } if (target instanceof RecyclerView && velocityY < 0) { final RecyclerView recyclerView = (RecyclerView) target; final View firstChild = recyclerView.getChildAt(0); final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild); consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD; } return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed); } @Override public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) { super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); isPositive = dy > 0; } } 

并将其添加到AppBarLayout如下所示:

 <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" ... app:layout_behavior="com.example.test.FlingBehavior"> 

我只是张贴在这里,以便别人不要错过评论。 Jinang的回答非常好,但对AntPachon指出了一个更简单的方法。 不要以编程方式Child of the NestedScrollViewChild of the NestedScrollView上实现OnClick方法,更好的方法是在xml中为child设置clickable=true

(使用与Jinang相同的例子)

 <android.support.v4.widget.NestedScrollView 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" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:id="@+id/content_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:clickable="true" > <!-- new --> <!-- Page Content --> </LinearLayout> </android.support.v4.widget.NestedScrollView> 

在代码中: https : //android.googlesource.com/platform/frameworks/support/+/master/core-ui/java/android/support/v4/widget/NestedScrollView.java#834

  case MotionEvent.ACTION_UP: if (mIsBeingDragged) { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker, mActivePointerId); if ((Math.abs(initialVelocity) > mMinimumVelocity)) { flingWithNestedDispatch(-initialVelocity); } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } } mActivePointerId = INVALID_POINTER; endDrag(); break; 

当我使用NestedScrollView有时“mIsBeingDragged = false”的投影滚动,所以NestedScrollView不会调度抛掷事件。

当我删除if (mIsBeingDragged)语句。

  case MotionEvent.ACTION_UP: //if (mIsBeingDragged) { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker, mActivePointerId); if ((Math.abs(initialVelocity) > mMinimumVelocity)) { flingWithNestedDispatch(-initialVelocity); } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } //} mActivePointerId = INVALID_POINTER; endDrag(); break; 

不会有问题的 但是我不知道还有什么其他严重的问题会引起