with scrollflags ejemplo coordinatorlayout collapsing appbarlayout java android xml collapsingtoolbarlayout android-collapsingtoolbarlayout

java - scrollflags - CollapsingToolbarLayout no reconoce el desplazamiento de desplazamiento



coordinatorlayout (6)

He creado un simple CollapsingToolbarLayout y funciona de maravilla. Mi problema es que si trato de usar un desplazamiento de desplazamiento en la vista de desplazamiento anidada , simplemente se detiene cuando libero mi dedo. El desplazamiento normal funciona como debería.

Mi código de actividades no ha cambiado => actividad vacía generada automáticamente . (Acabo de hacer clic en crear nueva actividad vacía en Android Studio y aún edité el XML).

Leí aquí, que los gestos de desplazamiento en la vista de imagen en sí son defectuosos, pero no, que el desplazamiento en sí es defectuoso: ver aquí .

Intenté activar el "desplazamiento suave" a través del código Java. Parece que si me desplazo lo suficiente como para que la vista de imagen ya no sea visible, entonces se reconocen los gestos de lanzamiento.

TLDR: ¿Por qué el gesto de lanzamiento no funciona mientras la vista de imagen sea visible? Mi código XML se ve así:

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


En el código: 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;

Cuando uso un desplazamiento de lanzamiento en el NestedScrollView a veces "mIsBeingDragged = false", NestedScrollView no envía el evento de lanzamiento.

Cuando if (mIsBeingDragged) instrucción 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;

No habrá problema. Pero no sé qué otros problemas serios serán causados


Probé la solución de Floofer pero todavía no era lo suficientemente buena para mí. Así que se me ocurrió una mejor versión de su comportamiento. AppBarLayout ahora se expande y contrae sin problemas cuando se lanza.

Nota: Utilicé la reflexión para introducirme en esto, por lo que puede que no funcione perfectamente con una versión de la biblioteca de diseño de Android diferente de 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); } }

Para usarlo, establezca un nuevo comportamiento en su 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));


Sé que esta pregunta se hizo hace más de un año, pero este problema aún no parece resolverse en las bibliotecas de Soporte / Diseño. Puede destacar este problema para que avance más en la cola de prioridad.

Dicho esto, probé la mayoría de las soluciones publicadas para esto, incluida la de patrick-iv sin éxito. La única forma en que pude llegar al trabajo fue imitar el lanzamiento y llamarlo mediante programación si se detectó un cierto conjunto de condiciones en onPreNestedScroll() . En las pocas horas de mi depuración, noté que onNestedFling() nunca fue llamado en un lanzamiento hacia arriba (desplazamiento hacia abajo) y parecía consumirse prematuramente. No puedo decir con 100% de certeza que esto funcionará para el 100% de las implementaciones, pero funciona lo suficientemente bien para mis usos, así que terminé resolviendo esto, a pesar de que es bastante hacky y definitivamente no es lo que quería hacer.

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

Y aplicarlo a la barra de aplicaciones

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

Demostración de CheeseSquare: Before After


Solo estoy publicando esto aquí para que otros no se lo pierdan en Comentarios. La respuesta de Jinang funciona maravillosamente, pero felicitaciones a Jinang por señalar un método mucho más simple para lo mismo. En lugar de implementar un método OnClick en el elemento Child of the NestedScrollView programación, una mejor manera es establecer clickable=true en el xml para el elemento secundario.

(Usando el mismo ejemplo que el de 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>


Tuve exactamente el mismo problema con CollapsingToolbarLayout con ImageView dentro y NestedScrollView . El desplazamiento de lanzamiento se detiene cuando se suelta el dedo.

Sin embargo, he notado algo extraño. Si comienza a desplazarse con el dedo desde una vista con OnClickListener (por ejemplo, Button), el desplazamiento de desplazamiento funciona perfectamente.

Así lo arreglé con una solución extraña. Establezca OnClickListener (que no hace nada) en el hijo directo de NestedScrollView . ¡Entonces funciona perfectamente!

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

Dele al niño directo (LinearLayout) un id y configure OnClickListener en Activity

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

Notas:

Probado con Support Design Library 25.0.1

CollapsingToolbarLayout with scrollFlags = "scroll | enterAlwaysCollapsed"


Esta respuesta resolvió este problema para mí. Cree un AppBarLayout.Behavior personalizado como este:

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

y agregarlo a la AppBarLayout esta manera:

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