CollapsingToolbarLayout не распознает бросок прокрутки
Я создал простой CollapsingToolbarLayout и это работает как шарм. Моя проблема в том, что если я попытаюсь использовать бросок прокрутки на nestedscrollview, он просто останавливается, когда я отпускаю палец. Обычная прокрутка работает так, как и должно быть.
мой код деятельности без изменений => авто создается пустое занятие. (Я просто нажал на Создать новую пустую активность в android studio и отредактировал XML еще).
I читайте здесь, что жесты прокрутки на самом imageview являются багги, но не то, что сама прокрутка является багги:посмотреть здесь.
Я пробовал активировать "плавная прокрутка" через 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>
6 ответов:
у меня была такая же проблема с CollapsingToolbarLayout С ImageView и внутри NestedScrollView. Бросок прокрутки останавливается, когда палец отпущен.
однако я заметил кое-что странное. Если вы начинаете прокрутку пальцем из вида с помощью OnClickListener (например, кнопки), прокрутка fling работает отлично.
таким образом, я исправил его со странным решением. Установить OnClickListener (который ничего не делает) на прямой ребенок NestedScrollView. Тогда он отлично работает!
<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) идентификатор и установите OnClickListener в Activity
ViewGroup mContentContainer = (ViewGroup) findViewById(R.id.content_container); mContentContainer.setOnClickListener(this); @Override public void onClick(View view) { int viewId = view.getId(); }
Примечания:
протестировано с помощью библиотеки поддержки дизайна 25.0.1
CollapsingToolbarLayout с помощью scrollFlags= "scroll / enterAlwaysCollapsed"
Я знаю, что этот вопрос был задан более года назад, но эта проблема до сих пор не решена в библиотеках поддержки/дизайна. Вы можете звезды проблема, так что он движется дальше в очереди приоритетов.
тем не менее, я попробовал большинство опубликованных решений для этого, в том числе один Патрик-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); } }
и применить его к Панели приложений
AppBarLayout scrollView = (AppBarLayout)findViewById(R.id.appbar); CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams)scrollView.getLayoutParams(); params.setBehavior(new NestedScrollViewBehavior());
я попробовал решение Floofer, но это все еще не было достаточно хорошо для меня. Поэтому я придумал лучшую версию его поведения. AppBarLayout теперь расширяется и плавно сворачивается при броске.
Примечание: я использовал отражение, чтобы взломать мой путь в это, поэтому он может не работать идеально с версией Android Design library, отличной от 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); } }
чтобы использовать его, установите новое поведение для вашего 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));
ответ решил эту проблему для меня. Создайте пользовательский
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
методChild of the NestedScrollView
программно, лучший способ-установитьclickable=true
в xml для ребенка.(используя тот же пример, как Цзинань)
<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;
не будет никаких проблем.но я не знаю, какие еще серьезные проблемы будет вызвано