CollapsingToolbarLayout이 스크롤 플링을 인식하지 못합니다.


93

간단한 CollapsingToolbarLayout 을 만들었 으며 매력처럼 작동합니다. 내 문제는 nestedscrollview 에서 플링 스크롤을 사용하려고하면 손가락을 떼면 중지 된다는 것입니다. 정상적인 스크롤은 정상적으로 작동합니다.

내 활동 코드가 변경되지 않았습니다 => 자동 생성 된 빈 활동. (나는 방금 android studio에서 새로운 빈 활동 만들기를 클릭하고 아직 XML을 편집했습니다).

나는 여기에서 imageview 자체의 스크롤 제스처가 버그가 있지만 스크롤 자체가 버그가 있다는 것을 읽었습니다. here 참조 .

Java 코드를 통해 "부드러운 스크롤" 을 활성화 해 보았습니다 . 이미지 뷰가 더 이상 보이지 않을 정도로 멀리 스크롤하면 플링 제스처가 인식되는 것 같습니다.

TLDR : 이미지 뷰 가 표시되는 동안 플링 제스처가 작동하지 않는 이유는 무엇입니까? 내 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>

흥미롭게도, 영향을받는 플링 동안 중첩 된 스크롤 뷰에 터치 이벤트를 기록했습니다. 가져옵니다 ACTION_DOWN y=98 -> ACTION_MOVE y=-40 -> ACTION_MOVE y=-33 -> ACTION_UP y=97. 마지막 터치 이벤트가 첫 번째 이벤트 옆에 있다고 잘못보고하는 것 같습니다.
Xiao

어떤 버전의 설계 지원 라이브러리를 사용하고 있습니까?
Radu Topor

터치 이벤트를 재정의하고 있습니까? 설정을 시도 nestedScrollView.getParent().requestDisallowInterceptTouchEvent(true);하여 중첩 된 스크롤 뷰
Bhargav

답변:


20

ImageViewNestedScrollView가 있는 CollapsingToolbarLayout 과 똑같은 문제가 있습니다. 손가락을 떼면 플링 스크롤이 멈 춥니 다.

하지만 이상한 점을 발견했습니다. OnClickListener (예 : Button)를 사용하여보기에서 손가락으로 스크롤을 시작하면 플링 스크롤이 완벽하게 작동합니다.

따라서 나는 이상한 해결책으로 그것을 고쳤다. 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를 부여하고 활동에서 OnClickListener를 설정합니다.

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

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

노트:

지원 디자인 라이브러리 25.0.1을 사용하여 테스트 됨

scrollFlags = "scroll | enterAlwaysCollapsed"가있는 CollapsingToolbarLayout


AOSP는이 훌륭한 솔루션으로 패치되어야합니다 : D
deviant

더 많은 투표를 받아야합니다. Btw 이것은 CollapsingToolbarLayout을 스크롤에 매우 민감하게 만들었지 만 현재의 깨진 동작보다 낫습니다.
아마드 Fadli

1
내가 그것을 나를 위해 작동하지 자사의 노력 때문에이 너무 좋은 사람이었다
길버트 멘도사을

이것은 내가 지금까지 시도한 가장 미친 해결책입니다.
Techfist

: D 대단한 관찰 @jinang !!
Srichakradhar 2017

10

이 질문은 1 년 전에 요청되었지만 여전히 지원 / 디자인 라이브러리에서이 문제가 해결되지 않는 것 같습니다. 문제에 별표 를 표시하여 우선 순위 대기열에서 더 위로 이동할 수 있습니다 .

즉, 성공하지 못한 patrick-iv의 솔루션을 포함하여 게시 된 대부분의 솔루션을 시도했습니다. 내가 일할 수 있었던 유일한 방법은 플링을 모방하고에서 특정 조건 세트가 감지되면 프로그래밍 방식으로 호출하는 것입니다 onPreNestedScroll(). 내 디버깅 몇 시간 동안 나는 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 데모 : 비포 애프터


실제로 아무것도없는 것보다 낫지 만 숙련 된 Android 사용자가 기대하는 것과는 다릅니다. 문제를 연결해 주셔서 감사합니다. 별표 표시했습니다.
Raphael Royer-Rivard

enterAlways작동 하려면 layout_ScrollFlag 를 제거해야 했지만 이제는 잘 작동합니다
Alexandre G

3

나는 Floofer의 솔루션을 시도했지만 여전히 나에게 충분하지 않았습니다. 그래서 나는 그의 행동의 더 나은 버전을 생각해 냈습니다. 이제 AppBarLayout이 플링 할 때 부드럽게 확장 및 축소됩니다.

참고 : 리플렉션을 사용하여이 방법을 해킹했기 때문에 25.0.0과 다른 Android 디자인 라이브러리 버전에서는 완벽하게 작동하지 않을 수 있습니다.

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에 새 Behavior를 설정하십시오.

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

클래스는 생성자에서 int를 요구하지만 코드에서는 생성자에게 아무것도 보내지 않습니다
bluesummers

내 잘못이다.
Raphael Royer-Rivard 2016

이것은 좋게 보이지만 스크롤을 부드럽게 만들지 만 한 가지 질문이 있습니다 .AppBarLayout이 맨 위에 도달하면 NestedScrollView가 AppBarLayout으로 스크롤되도록 할 수 있으며 아래로 스크롤하면 AppBarLayout이 마침내 나타납니다 .NestedScrollView 완전히 스크롤 된 다음 AppBarLayout이 확장되기 시작합니다.
Zijian 왕

@ZijianWang "AppBarLayout으로 스크롤"이 무슨 뜻인지 설명해 주시고 두 번째 질문도 이해하지 못합니다. 다시 말씀해 주시겠습니까?
라파엘 이어-Rivard

0

이 대답 은 나를 위해이 문제를 해결했습니다. 다음 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">

1
다른 질문의 문제는 여기서 사용되지 않는 RecyclerView였습니다.
Felix Edelmann

0

다른 사람들이 댓글에서 놓치지 않도록 여기에 게시하고 있습니다. Jinang 의 대답은 아름답게 작동하지만 AntPachon에 대한 훨씬 더 간단한 방법을 지적한 것에 찬사보냅니다 . 프로그래밍 방식으로 OnClick메서드를 구현하는 대신 자식에 대한 xml Child of the NestedScrollView을 설정하는 것이 더 좋습니다 clickable=true.

( 지낭 과 같은 예 사용 )

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

-1

코드에서 :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;

문제가 없을 것입니다. 하지만 다른 심각한 문제가 발생할지 모르겠습니다.


답변을 이해하기 쉽도록 자세한 내용을 추가하십시오. You write when i remove the if ... from where you are talking to remove if ?
Devendra Singh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.