ScrollView Inside ScrollView

Sé que las personas de Google nos han pedido que no coloquemos la vista desplazable dentro de otra vista desplazable, pero ¿hay algún comunicado oficial que nos indique que no lo hagamos?

¿Esto es lo suficientemente cerca?

Nunca debe usar un HorizontalScrollView con un ListView, ya que ListView se ocupa de su propio desplazamiento. Lo más importante es que al hacerlo se superan todas las optimizaciones importantes en ListView para tratar con listas grandes, ya que fuerza a ListView a mostrar toda su lista de elementos para llenar el contenedor infinito proporcionado por HorizontalScrollView.

http://developer.android.com/reference/android/widget/HorizontalScrollView.html

ACTUALIZAR:

Como puede verse obligado a utilizar una vista de desplazamiento bidimensional, puede considerar usar esto: Archivo de Internet de blog.gorges.us/2010/06/android-two-dimensional-scrollview/

No he usado esto, pero puede ser un enfoque razonable.

Prueba este

Nota: Aquí parentScrollView significa Outer ScrollView Y childScrollView significa Innner ScrollView

 parentScrollView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.v(TAG, "PARENT TOUCH"); findViewById(R.id.child_scroll).getParent() .requestDisallowInterceptTouchEvent(false); return false; } }); childScrollView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.v(TAG, "CHILD TOUCH"); // Disallow the touch request for parent scroll on touch of child view v.getParent().requestDisallowInterceptTouchEvent(true); return false; } }); 

La respuesta anterior de Atul Bhardwaj es la forma correcta de hacerlo. Pero en caso de que alguien necesite aplicarlo a ScrollView, donde tienes menos control sobre el padre, creo que es lo suficientemente flexible y de la manera en que debería funcionar:

 private void makeMyScrollSmart() { myScroll.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View __v, MotionEvent __event) { if (__event.getAction() == MotionEvent.ACTION_DOWN) { // Disallow the touch request for parent scroll on touch of child view requestDisallowParentInterceptTouchEvent(__v, true); } else if (__event.getAction() == MotionEvent.ACTION_UP || __event.getAction() == MotionEvent.ACTION_CANCEL) { // Re-allows parent events requestDisallowParentInterceptTouchEvent(__v, false); } return false; } }); } private void requestDisallowParentInterceptTouchEvent(View __v, Boolean __disallowIntercept) { while (__v.getParent() != null && __v.getParent() instanceof View) { if (__v.getParent() instanceof ScrollView) { __v.getParent().requestDisallowInterceptTouchEvent(__disallowIntercept); } __v = (View) __v.getParent(); } } 

Lo que hace la función es agregar un elemento de escucha táctil a myScroll que deshabilita la intercepción táctil de los padres cuando se inicia un toque en el niño, y luego lo habilita nuevamente cuando el toque final realmente. No necesita una referencia al ScrollView padre y no tiene que ser el padre inmediato … recorrerá la lista de visualización hasta que lo encuentre.

Lo mejor de ambos mundos, en mi opinión.

 childScrollView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: // Disallow ScrollView to intercept touch events. v.getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: // Allow ScrollView to intercept touch events. v.getParent().requestDisallowInterceptTouchEvent(false); break; } return false; } }); 

v.getParent () = padre scrollView.

Aquí hay una posible solución. Cuando se llega al final de un ScrollView niño pasa el control al ScrollView principal para desplazarse. Funciona con ScrollView y ListView dentro de ScrollView.

Paso 1: configure el elemento primario OnTouchListener

 parentScroll.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { v.getParent().requestDisallowInterceptTouchEvent(false); return false; } }); 

Paso 2: configure OnTouchListener para niños (ScrollView o ListView)

 aChildScrollView.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { v.getParent().requestDisallowInterceptTouchEvent(shouldRequestDisallowIntercept((ViewGroup) v, event)); return false; } }); aListView.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { v.getParent().requestDisallowInterceptTouchEvent(shouldRequestDisallowIntercept((ViewGroup) v, event)); return false; } }); 

Paso 3: aquí están los métodos mágicos necesarios para la funcionalidad correcta

 protected boolean shouldRequestDisallowIntercept(ViewGroup scrollView, MotionEvent event) { boolean disallowIntercept = true; float yOffset = getYOffset(event); if (scrollView instanceof ListView) { ListView listView = (ListView) scrollView; if (yOffset < 0 && listView.getFirstVisiblePosition() == 0 && listView.getChildAt(0).getTop() >= 0) { disallowIntercept = false; } else if (yOffset > 0 && listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1 && listView.getChildAt(listView.getChildCount() - 1).getBottom() <= listView.getHeight()) { disallowIntercept = false; } } else { float scrollY = scrollView.getScrollY(); disallowIntercept = !((scrollY == 0 && yOffset < 0) || (scrollView.getHeight() + scrollY == scrollView.getChildAt(0).getHeight() && yOffset >= 0)); } return disallowIntercept; } protected float getYOffset(MotionEvent ev) { final int historySize = ev.getHistorySize(); final int pointerCount = ev.getPointerCount(); if (historySize > 0 && pointerCount > 0) { float lastYOffset = ev.getHistoricalY(pointerCount - 1, historySize - 1); float currentYOffset = ev.getY(pointerCount - 1); float dY = lastYOffset - currentYOffset; return dY; } return 0; } 

[…] ¿hay alguna statement oficial de ellos que nos indique que no lo hagamos?

Creo que sí, aunque parece que no puedo encontrarlo en mis notas. Sé que encontré tal afirmación cuando trato de tener una vista de desplazamiento en una actividad de lista. Creo que en realidad hay un “error” de enfoque lógico en la forma en que el sistema de interfaz de usuario de Android trata con scrollables nesteds que probablemente deberían ser mejor detectados y comunicados al desarrollador. Pero mi consejo es …

Al final, es mejor considerar una sola vista desplazable por el bien del usuario de todos modos. Es como tener barras de desplazamiento dentro de barras de desplazamiento en una página HTML; puede ser legal, pero es una experiencia de usuario terrible.

Encontré una muy buena solución. Por favor usa este código

  parentScrollView.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { Utils.showLog("PARENT TOUCH"); findViewById(R.id.activity_mesh_child_scrollView).getParent().requestDisallowInterceptTouchEvent(false); return false; } }); childScrollView.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { Utils.showLog("CHILD TOUCH"); // Disallow the touch request for parent scroll on touch of child view v.getParent().requestDisallowInterceptTouchEvent(true); return false; } }); 

Esto seguramente funcionará. Por favor, intente y avíseme si no funciona.

En realidad, hay una statement oficial al respecto, en un video bastante antiguo llamado ” el mundo de ListView “. Dicen que no coloque ninguna vista desplazable dentro de otra (cuando ambas están en la misma dirección).

Sin embargo, ahora tenemos una nueva vista que permite que ambas vistas se desplacen al mismo tiempo, probablemente para mostrar un efecto genial:

https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

No encontré ningún ejemplo para esto, así que lo que escribí es solo una suposición de lo que hace y para qué se usa.

Puede colocar un ScrollView dentro de otro ScrollView. Simplemente extienda el ScrollView hijo para anular el método onTouchEvent. Al igual que

 import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; public class ChildScrollView extends android.widget.ScrollView { private int parent_id; public ChildScrollView(Context context) { super(context); } public ChildScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public ChildScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onTouchEvent(MotionEvent event){ requestDisallowInterceptTouchEvent(true); return super.onTouchEvent(event); } } 

Aquí, he creado un proyecto de muestra relacionado con ScrollView dentro de ScrollView. Una vista es desplazable en ambos sentidos. Echale un vistazo :-

MainActivity.java –

 package com.example.dev_task_193_scrollview; import com.example.dev_task_196_scrollview.R; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.Toast; public class MainActivity extends Activity implements View.OnClickListener{ ImageView imageView1,imageView2,imageView3,IVimage1,IVimage2,IVimage3,IVimage4,IVimage5,IVimage6; ListView listView1,listView2; HorizontalScrollView horizontalScrollView1,horizontalScrollView2; ScrollView parentScrollView, scrollView1; RelativeLayout relativeLayout1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String[] values = new String[] { "Android", "iPhone", "WindowsMobile", "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2", "Android", "iPhone", "WindowsMobileWindowsMobileWindowsMobileWindowsMobile" }; relativeLayout1 = (RelativeLayout) findViewById(R.id.relativeLayout1); imageView1 = (ImageView) findViewById(R.id.imageView1); imageView1.setBackgroundResource(R.drawable.info); imageView2 = (ImageView) findViewById(R.id.imageView2); imageView2.setBackgroundResource(R.drawable.info); imageView3 = (ImageView) findViewById(R.id.imageView3); imageView3.setBackgroundResource(R.drawable.info); listView1 = (ListView) findViewById(R.id.listView1); ArrayAdapter adapter = new ArrayAdapter(this, R.layout.list_item, values); listView1.setAdapter(adapter); listView2 = (ListView) findViewById(R.id.listView2); ArrayAdapter adapter1 = new ArrayAdapter(this, R.layout.list_item, values); listView2.setAdapter(adapter1); parentScrollView = (ScrollView) findViewById(R.id.parentScrollView); scrollView1 = (ScrollView) findViewById(R.id.scrollView1); horizontalScrollView1 = (HorizontalScrollView) findViewById(R.id.horizontalScrollView1); horizontalScrollView2 = (HorizontalScrollView) findViewById(R.id.horizontalScrollView2); IVimage1 = (ImageView) findViewById(R.id.IVimage1); IVimage2 = (ImageView) findViewById(R.id.IVimage2); IVimage3 = (ImageView) findViewById(R.id.IVimage3); IVimage4 = (ImageView) findViewById(R.id.IVimage4); IVimage5 = (ImageView) findViewById(R.id.IVimage5); IVimage6 = (ImageView) findViewById(R.id.IVimage6); scrollView1.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { // Disallow the touch request for parent scroll on touch of child view parentScrollView.requestDisallowInterceptTouchEvent(true); return false; } }); horizontalScrollView1.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { // Disallow the touch request for parent scroll on touch of child view parentScrollView.requestDisallowInterceptTouchEvent(true); return false; } }); listView1.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { // Disallow the touch request for parent scroll on touch of child view parentScrollView.requestDisallowInterceptTouchEvent(true); return false; } }); listView1.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { Toast.makeText(getApplicationContext(), "Clicked "+parent.getItemAtPosition(position).toString(), Toast.LENGTH_SHORT).show(); } }); listView2.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { Toast.makeText(getApplicationContext(), "Clicked "+parent.getItemAtPosition(position).toString(), Toast.LENGTH_SHORT).show(); } }); listView2.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { // Disallow the touch request for parent scroll on touch of child view parentScrollView.requestDisallowInterceptTouchEvent(true); return false; } }); horizontalScrollView2.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { // Disallow the touch request for parent scroll on touch of child view parentScrollView.requestDisallowInterceptTouchEvent(true); return false; } }); /*imageView1.setOnClickListener(this); imageView2.setOnClickListener(this); imageView3.setOnClickListener(this);*/ IVimage1.setOnClickListener(this); IVimage2.setOnClickListener(this); IVimage3.setOnClickListener(this); IVimage4.setOnClickListener(this); IVimage5.setOnClickListener(this); IVimage6.setOnClickListener(this); imageView1.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { // Disallow the touch request for parent scroll on touch of child view parentScrollView.requestDisallowInterceptTouchEvent(true); Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); return false; } }); imageView2.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { // Disallow the touch request for parent scroll on touch of child view parentScrollView.requestDisallowInterceptTouchEvent(true); Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); return false; } }); imageView3.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { // Disallow the touch request for parent scroll on touch of child view parentScrollView.requestDisallowInterceptTouchEvent(true); Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); return false; } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public void onClick(View v) { switch(v.getId()){ case R.id.imageView1: Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); break; case R.id.imageView2: Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); break; case R.id.imageView3: Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); break; case R.id.IVimage1: Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); break; case R.id.IVimage2: Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); break; case R.id.IVimage3: Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); break; case R.id.IVimage4: Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); break; case R.id.IVimage5: Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); break; case R.id.IVimage6: Toast.makeText(getApplicationContext(), "Clicked "+v.getTag(), Toast.LENGTH_SHORT).show(); break; } // TODO Auto-generated method stub } } 

activity_main.xml –

                                  

list_item.xml (para ListView) –

   

La biblioteca de soporte de Android v4 tiene una clase llamada NestedScrollView.

Pruebe la vista de desplazamiento nested: http://ivankocijan.xyz/android-nestedscrollview/

Si alguien está buscando una respuesta a esto, tuve una implementación ligeramente diferente. Extendí la clase ScrollView e implementé en TouchListener en el elemento secundario y lo configuré en el constructor.

En la callback onTouch, si el objeto de evento de movimiento viene con un valor para el conteo de puntero como 2, devolví verdadero, de lo contrario sería falso. De esta forma, si dos dedos se movieran en la pantalla, lo consideraría como un pellizco para ampliar, de lo contrario lo consideraría un desplazamiento normal. No solicité la desactivación táctil de los padres, etc.

 @Override public boolean onTouch(View view, MotionEvent motionEvent) { if(motionEvent.getPointerCount() == 2){ mCallbacks.onPinchZoomAction(motionEvent); return true; } return false; } 

No es solo que Google dice que es una mala práctica, simplemente no tiene mucho sentido. Supongamos que tiene dos vistas desplazables verticales anidadas una dentro de la otra. Cuando mueve su dedo sobre las vistas de desplazamiento, ¿cuál quiere mover, la interna o la externa?

Debes replantear tu diseño de interfaz de usuario para algo que no lo requiera, hay muchas maneras de hacer una gran interfaz de usuario y aún así mantenerlo realmente simple.