간단한 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>
답변
ImageView 와 NestedScrollView가 있는 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
답변
이 질문은 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());
답변
나는 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));
답변
이 대답 은 나를 위해이 문제를 해결했습니다. 다음 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에 대한 훨씬 더 간단한 방법을 지적한 것에 찬사 를 보냅니다 . 프로그래밍 방식으로 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>
답변
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;
문제가 없을 것입니다. 하지만 다른 심각한 문제가 발생할지 모르겠습니다.