android - appbar_scrolling_view_behavior - layout_collapsemode
DiseƱo de Android: vista de reciclaje horizontal dentro de una vista de reciclaje vertical dentro de un visor con comportamientos de desplazamiento (6)
Llego un poco tarde, pero esto definitivamente funcionará para otros que enfrentan el mismo problema
mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
int action = e.getAction();
// Toast.makeText(getActivity(),"HERE",Toast.LENGTH_SHORT).show();
switch (action) {
case MotionEvent.ACTION_POINTER_UP:
rv.getParent().requestDisallowInterceptTouchEvent(true);
break;
}
return false;
}
Esta es la aplicación que estoy tratando de construir con todos los elementos que se detallan a continuación:
Todo funciona, sin embargo, quiero que la vista del reciclador horizontal interior no capture ninguno de los pergaminos verticales. Todos los desplazamientos verticales deben ir hacia la vista de reciclador vertical exterior, no horizontal, de modo que el desplazamiento vertical permita que la barra de herramientas salga de la vista de acuerdo con su scrollFlag.
Cuando coloco mi dedo en la parte "Planta de la fresa" de la vista del reciclador y me desplazo hacia arriba, se desplaza hacia la barra de herramientas:
Si pongo mi dedo en la vista de desplazamiento horizontal y me desplazo hacia arriba, no se desplaza fuera de la barra de herramientas.
El siguiente es mi código de diseño xml hasta ahora.
El diseño XML de la actividad :
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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:id="@+id/fragment_container"
android:clipChildren="false">
<android.support.design.widget.CoordinatorLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
>
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="@+id/sliding_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
style="@style/CustomTabLayout"
/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
</android.support.design.widget.CoordinatorLayout>
</FrameLayout>
El diseño xml del fragmento "Fruits" (que es el código del fragmento; el fragmento está etiquetado en la imagen de arriba):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
android:visibility="gone"
android:layout_centerInParent="true"
android:indeterminate="true"/>
<!-- <android.support.v7.widget.RecyclerView-->
<com.example.simon.customshapes.VerticallyScrollRecyclerView
android:id="@+id/main_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
He usado una clase personalizada llamada VerticallyScrollRecyclerView que sigue el ejemplo de google de manejar eventos táctiles en un grupo de vista. Su objetivo es interceptar y consumir todos los eventos de desplazamiento vertical para que se desplace dentro / fuera de la barra de herramientas: http://developer.android.com/training/gestures/viewgroup.html
El código para VerticallyScrollRecyclerView está abajo:
public class VerticallyScrollRecyclerView extends RecyclerView {
public VerticallyScrollRecyclerView(Context context) {
super(context);
}
public VerticallyScrollRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VerticallyScrollRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
ViewConfiguration vc = ViewConfiguration.get(this.getContext());
private int mTouchSlop = vc.getScaledTouchSlop();
private boolean mIsScrolling;
private float startY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
// Always handle the case of the touch gesture being complete.
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// Release the scroll.
mIsScrolling = false;
startY = ev.getY();
return super.onInterceptTouchEvent(ev); // Do not intercept touch event, let the child handle it
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
Log.e("VRecView", "its moving");
if (mIsScrolling) {
// We''re currently scrolling, so yes, intercept the
// touch event!
return true;
}
// If the user has dragged her finger horizontally more than
// the touch slop, start the scroll
// left as an exercise for the reader
final float yDiff = calculateDistanceY(ev.getY());
Log.e("yDiff ", ""+yDiff);
// Touch slop should be calculated using ViewConfiguration
// constants.
if (Math.abs(yDiff) > 5) {
// Start scrolling!
Log.e("Scroll", "we are scrolling vertically");
mIsScrolling = true;
return true;
}
break;
}
}
return super.onInterceptTouchEvent(ev);
}
private float calculateDistanceY(float endY) {
return startY - endY;
}
}
El diseño "Favorito", que es la vista de reciclaje dentro de la vista de reciclaje vertical:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/white"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Favourite"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="16dp"
android:id="@+id/header_fav"/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_below="@+id/header_fav"
android:id="@+id/recyclerview_fav">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
Esto me ha estado molestando por un tiempo y no he logrado encontrar una solución. ¿Alguien sabe cómo solucionar este problema?
5 puntos a Griffindor por la respuesta correcta y, por supuesto, puntos de reputación en SO.
Solución probada, use un
NestedScrollView()
.
Código:
public class CustomNestedScrollView extends NestedScrollView {
public CustomNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// Explicitly call computeScroll() to make the Scroller compute itself
computeScroll();
}
return super.onInterceptTouchEvent(ev);
}
}
Te sugiero que agregues la vista horizontal de reciclaje dentro de fragmentos como la aplicación de Google
si alguien sigue buscando, intente esto:
private val Y_BUFFER = 10
private var preX = 0f
private var preY = 0f
mView.rv.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
override fun onTouchEvent(p0: RecyclerView, p1: MotionEvent) {
}
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
when (e.action) {
MotionEvent.ACTION_DOWN -> rv.parent.requestDisallowInterceptTouchEvent(true)
MotionEvent.ACTION_MOVE -> {
if (Math.abs(e.x - preX) > Math.abs(e.y - preY)) {
rv.parent.requestDisallowInterceptTouchEvent(true)
} else if (Math.abs(e.y - preY) > Y_BUFFER) {
rv.parent.requestDisallowInterceptTouchEvent(false)
}
}
}
preX = e.x
preY = e.y
return false
}
override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {
}
})
comprueba si actualmente se desplaza horizontalmente y luego no permite que el padre maneje el evento
tratar
public OuterRecyclerViewAdapter(List<Item> items) {
//Constructor stuff
viewPool = new RecyclerView.RecycledViewPool();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//Create viewHolder etc
holder.innerRecyclerView.setRecycledViewPool(viewPool);
}
La vista interna del reciclador usará el mismo viewpool y será más suave
Solución probada:
Todo lo que necesita es llamar a
mInnerRecycler.setNestedScrollingEnabled(false);
en su
RecyclerView
s interno
Explicacion
:
RecyclerView
tiene soporte para desplazamiento anidado introducido en
API 21
través de la implementación de la interfaz
NestedScrollingChild
.
Esta es una característica valiosa cuando tiene una vista de desplazamiento dentro de otra que se desplaza en la misma dirección y desea desplazar la
View
interna solo cuando está enfocada.
En cualquier caso,
RecyclerView
por defecto llama a
RecyclerView.setNestedScrollingEnabled(true);
en sí mismo al inicializar.
Ahora, volviendo al problema, dado que sus dos
RecyclerView
s están dentro del mismo
ViewPager
que tiene
AppBarBehavior
,
CoordinateLayout
tiene que decidir a qué desplazamiento responder cuando se desplaza desde su
RecyclerView
interno;
cuando el desplazamiento anidado interno de
RecyclerView
está habilitado, obtiene el foco de desplazamiento y
CoordinateLayout
elegirá responder a su desplazamiento sobre el desplazamiento externo de
RecyclerView
.
La cuestión es que, dado que sus
RecyclerView
internos no se desplazan verticalmente, no hay cambio de desplazamiento vertical (desde el punto de vista de
CoordinateLayout
), y si no hay cambio, el
AppBarLayout
tampoco cambia.
En su caso, debido a que sus
RecyclerView
internos se desplazan en una dirección diferente, puede deshabilitarlos, haciendo que
CoordinateLayout
ignore su desplazamiento y responda al desplazamiento externo de
RecyclerView
.
Aviso :
El atributo xml
android:nestedScrollingEnabled="boolean"
no está diseñado para usarse con
RecyclerView
, y un intento de usar
android:nestedScrollingEnabled="false"
dará como resultado una
java.lang.NullPointerException
, por lo tanto, al menos por ahora, tendrá para hacerlo en código.