折叠工具栏布局不能识别滚动

我已经创建了一个简单的 折叠工具栏布局和它的工作像一个魅力。我的问题是,如果我试图在 Nestedscrollview上使用一个滚动卷轴,它只会在我松开手指时停止。正常的滚动应该是这样的。

我的活动代码是 < em > unchanged = > auto 生成的空活动。(我只是点击了在 android 工作室中创建新的空活动,然后编辑了 XML)。

我在这里读到,滚动手势在图像视图本身是错误的,但不是,滚动本身是错误的: 看这里

我试过用 Java 代码激活 “光滑滚动”。看起来好像如果我滚动的足够远,图像视图不再可见,抛出的手势,然后被识别。

为什么只要图像视图是可见的,抛出手势就不起作用? 我的 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>
13163 次浏览

This answer solved this problem for me. Create a custom AppBarLayout.Behavior like this:

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

and add it to the AppBarLayout like this:

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

I know this question was asked over a year ago but this issue still doesn't seem to be resolved in the Support/Design libraries. You can star this issue so it moves farther up in the priority queue.

That said, I tried most of the posted solutions for this, including the one by patrick-iv with no success. The only way I was able to get to work was to mimic the fling and call it programmatically if a certain set of conditions were detected in onPreNestedScroll(). In the few hours of my debugging I noticed that the onNestedFling() was never called on an upward (scroll down) fling and seemed to be consumed prematurely. I can't say with 100% certainty this will work for 100% of implementations but it works well enough for my uses so I ended up settling for this, even though it's pretty hacky and definitely not what I wanted to do.

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

And apply it to the AppBar

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

CheeseSquare demo: Before After

I tried Floofer's solution but it was still not good enough for me. So I came up with a better version of his Behavior. The AppBarLayout now expands and collapses smoothly when flinging.

Note: I used reflection to hack my way into this, so it may not work for perfectly with a version of Android Design library different from 25.0.0.

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

To use it, set a new Behavior to your 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));

I had exactly the same issue with CollapsingToolbarLayout with ImageView inside and NestedScrollView. The fling scroll stops when finger is released.

However, I've noticed something strange. If you start scrolling with your finger from a view with OnClickListener (e.g. Button), the fling scrolling works perfectly.

Thus I fixed it with a weird solution. Set OnClickListener (that does nothing) on the direct child of NestedScrollView. Then it works perfectly!

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

Give the direct child (LinearLayout) an id and set OnClickListener in Activity

ViewGroup mContentContainer = (ViewGroup) findViewById(R.id.content_container);
mContentContainer.setOnClickListener(this);


@Override
public void onClick(View view) {
int viewId = view.getId();
}

Notes:

Tested using Support Design Library 25.0.1

CollapsingToolbarLayout with scrollFlags="scroll|enterAlwaysCollapsed"

In the code :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;

When i use a fling scroll on the NestedScrollView sometimes "mIsBeingDragged = false", So NestedScrollView dont dispatch fling event.

When i delete the if (mIsBeingDragged) statement.

 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;

there will be no problem.But I do not know what other serious problems will be caused

Am just posting this here so that others do not miss it in Comments. The answer by Jinang works beautifully, but kudos to AntPachon for pointing out a far simpler method for the same. Instead of implementing an OnClick method on the Child of the NestedScrollView programatically, a better way is to set clickable=true in the xml for the child.

(Using the same example as Jinang's)

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