Scrollview vertical y horizontal en android

151

Estoy realmente cansado de buscar una solución para Scrollview vertical y horizontal.

Leí que no hay vistas / diseños en el marco que implementen esta función, pero necesito algo como esto:

Necesito definir un diseño dentro de otro, el diseño secundario debe implementar el desplazamiento vertical / horizontal para moverse.

Inicialmente implementé un código que movía el diseño píxel por píxel, pero creo que esa no es la forma correcta. Lo probé con ScrollView y Horizontal ScrollView pero nada funciona como quiero, porque solo implementa el desplazamiento vertical u horizontal.

Canvas no es mi solución porque necesito adjuntar oyentes en elementos secundarios de alguien.

¿Que puedo hacer?

Kronos
fuente
14
Es absolutamente ridículo que esto no esté disponible de fábrica con Android. Hemos trabajado con media docena de otras tecnologías de IU y es inaudito no tener algo tan básico como un contenedor de desplazamiento horizontal / vertical.
flexicious.com
Ok, finalmente escribimos el nuestro para nuestra cuadrícula de datos: androidjetpack.com/Home/AndroidDataGrid . Hace mucho más que solo desplazamiento horizontal / vertical. Pero la idea es básicamente envolver un HScroller dentro de un Vertical Scroller. También debe anular los métodos secundarios de agregar / quitar para apuntar al desplazador interno y algunas otras travesuras, pero funciona.
flexicious.com

Respuestas:

90

Mezclando algunas de las sugerencias anteriores, pude obtener una buena solución:

Vista de desplazamiento personalizada:

package com.scrollable.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ScrollView;

public class VScroll extends ScrollView {

    public VScroll(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public VScroll(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public VScroll(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }
}

HorizontalScrollView personalizado:

package com.scrollable.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;

public class HScroll extends HorizontalScrollView {

    public HScroll(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public HScroll(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public HScroll(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }
}

ScrollableImageActivity:

package com.scrollable.view;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;

public class ScrollableImageActivity extends Activity {

    private float mx, my;
    private float curX, curY;

    private ScrollView vScroll;
    private HorizontalScrollView hScroll;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        vScroll = (ScrollView) findViewById(R.id.vScroll);
        hScroll = (HorizontalScrollView) findViewById(R.id.hScroll);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float curX, curY;

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                mx = event.getX();
                my = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                curX = event.getX();
                curY = event.getY();
                vScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                hScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                mx = curX;
                my = curY;
                break;
            case MotionEvent.ACTION_UP:
                curX = event.getX();
                curY = event.getY();
                vScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                hScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                break;
        }

        return true;
    }

}

el diseño:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <com.scrollable.view.VScroll android:layout_height="fill_parent"
        android:layout_width="fill_parent" android:id="@+id/vScroll">
        <com.scrollable.view.HScroll android:id="@+id/hScroll"
            android:layout_width="fill_parent" android:layout_height="fill_parent">
            <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:src="@drawable/bg"></ImageView>
        </com.scrollable.view.HScroll>
    </com.scrollable.view.VScroll>

</LinearLayout>
Mahdi Hijazi
fuente
Esta es una gran respuesta, tenía otros elementos de desplazamiento en la vista que también debían cambiar. Todo lo que tenía que hacer era activar su desplazamiento junto con los demás.
T. Markle
1
¡Excelente! Algunos ajustes para tener en cuenta la velocidad, y esto sería perfecto (además, no necesitas la inicial LinearLayout, supongo)
Romuald Brunet
Hola Mahdi, ¿puedes decirme en qué control / widget has reprogramado el oyente táctil en clase de actividad?
Shachillies
Perdón por el retraso @DeepakSharma. No registré ningún oyente táctil en la actividad, simplemente anulé el evento onTouchEvent de la actividad.
Mahdi Hijazi
@MahdiHijazi, ¿puede agregar su código de Desplazamiento horizontal y Desplazamiento vertical en github.com/MikeOrtiz/TouchImageView? Esto está ampliando con éxito la imagen al hacer clic en el botón, pero no se pudo desplazar la imagen
Erum
43

Dado que este parece ser el primer resultado de búsqueda en Google para "Android ScrollView horizontal + horizontal", pensé que debería agregar esto aquí. Matt Clark ha creado una vista personalizada basada en la fuente de Android, y parece funcionar perfectamente: Two Dimensional ScrollView

Tenga en cuenta que la clase en esa página tiene un error al calcular el ancho horizontal de la vista. Una solución de Manuel Hilty está en los comentarios:

Solución: reemplace la declaración en la línea 808 por lo siguiente:

final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);


Editar: el enlace ya no funciona, pero aquí hay un enlace a una versión anterior de la publicación del blog .

Cachapa
fuente
3
Gracias, eso es lo que estaba buscando.
Andrey Agibalov
1
Esto funciona como un encanto después de algunas modificaciones menores para mí. Reimplementé fillViewporty onMeasureexactamente como la fuente ScrollView (v2.1). También cambié los dos primeros constructores con: this( context, null );y this(context, attrs, R.attr.scrollViewStyle);. Con eso, puedo usar la barra de desplazamiento usando setVerticalScrollBarEnabledy setHorizontalScrollBarEnableden el código.
olivier_sdg
¡Funciona muy bien también para mí! ¡Muchas gracias!
Avio
pero cómo obtener el tamaño del pulgar de desplazamiento y la posición de desplazamiento desde la parte inferior vertical y desde la derecha en modo horizontal
Ravi
10
Parece que la publicación vinculada ha desaparecido.
loeschg
28

Encontré una mejor solución.

XML: (design.xml)

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent">
  <FrameLayout android:layout_width="90px" android:layout_height="90px">
    <RelativeLayout android:id="@+id/container" android:layout_width="fill_parent" android:layout_height="fill_parent">        
    </RelativeLayout>
</FrameLayout>
</FrameLayout>

Código Java:

public class Example extends Activity {
  private RelativeLayout container;
  private int currentX;
  private int currentY;

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.design);

    container = (RelativeLayout)findViewById(R.id.container);

    int top = 0;
    int left = 0;

    ImageView image1 = ...
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image1, layoutParams);

    ImageView image2 = ...
    left+= 100;
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image2, layoutParams);

    ImageView image3 = ...
    left= 0;
    top+= 100;
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image3, layoutParams);

    ImageView image4 = ...
    left+= 100;     
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image4, layoutParams);
  }     

  @Override 
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            currentX = (int) event.getRawX();
            currentY = (int) event.getRawY();
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            int x2 = (int) event.getRawX();
            int y2 = (int) event.getRawY();
            container.scrollBy(currentX - x2 , currentY - y2);
            currentX = x2;
            currentY = y2;
            break;
        }   
        case MotionEvent.ACTION_UP: {
            break;
        }
    }
      return true; 
  }
}

Eso funciona !!!

Si desea cargar otro diseño o control, la estructura es la misma.

Kronos
fuente
¡Acabo de probar esto y funciona muy bien! No tiene indicadores de desplazamiento o lanzamiento, pero debido a que este es un enfoque de nivel inferior, esas cosas podrían construirse sobre esto con bastante facilidad. ¡Gracias!
ubzack
A mí también me funcionó bien para escribir una vista desplazable bidimensional que generaba resultados en tiempo real a partir del resultado de comandos remotos SSH a través de la red.
pilcrowpipe
@Kronos: ¿Qué pasa si solo quiero desplazarme horizontalmente? ¿Cuál debería ser mi parámetro y en container.scrollBy (currentX - x2, currentY - y2)? ¿Hay alguna forma de hacer que se desplace solo horizontalmente?
Ashwin
@Kronos: Se desplaza demasiado lejos. ¿Hay alguna forma de detener el desplazamiento cuando se llega al final?
Ashwin
Los botones no son desplazables, ¿por qué?
salih kallai
25

Lo uso y funciona bien:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView android:id="@+id/ScrollView02" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content"
            xmlns:android="http://schemas.android.com/apk/res/android">
<HorizontalScrollView android:id="@+id/HorizontalScrollView01" 
                      android:layout_width="wrap_content" 
                      android:layout_height="wrap_content">
<ImageView android:id="@+id/ImageView01"
           android:src="@drawable/pic" 
           android:isScrollContainer="true" 
           android:layout_height="fill_parent" 
           android:layout_width="fill_parent" 
           android:adjustViewBounds="true">
</ImageView>
</HorizontalScrollView>
</ScrollView>

El enlace fuente está aquí: Android-spa

Redax
fuente
2
Funciona "bien" para contenido estático, en el mejor de los casos. Varios ingenieros de Google han dicho que lo que intentas usar tendrá problemas ( groups.google.com/group/android-developers/browse_frm/thread/… y groups.google.com/group/android-beginners/browse_thread/thread/ ... )
CommonsWare
44
@CommonsWare: con respeto por los brillantes ingenieros múltiples de Google, en esos hilos le dijeron que reescribiera desde cero su propio componente: esta es la mejor manera. Seguramente es verdad. Pero no dicen que esta solución es mala, y si funciona ... Tal vez les lleve un minuto, para mí es mejor escribir algunos xml usando los componentes existentes. ¿Qué significa "contenido estático"? Yo uso botón e imágenes.
Redax
3
Por "contenido estático" me refiero a cosas que no involucran eventos táctiles. Mi punto, para otros que leen esta respuesta, es que si bien su solución puede funcionar para un solo ImageViewcontenido desplazable, puede o no funcionar para otros contenidos arbitrarios, y que Google cree que habrá problemas con su enfoque. La opinión de Google también es importante con respecto al desarrollo futuro: es posible que no estén tratando de asegurarse de que su enfoque funcione a largo plazo, si aconsejan a las personas que no lo usen en primer lugar.
CommonsWare
Esto está funcionando bien para mí ... Estoy actualizando una vista de imagen en esta vista de desplazamiento usando un hilo
sonu thomas
19

Mi solución basada en la respuesta de Mahdi Hijazi , pero sin ninguna vista personalizada:

Diseño:

<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/scrollHorizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ScrollView 
        android:id="@+id/scrollVertical"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" >

        <WateverViewYouWant/>

    </ScrollView>
</HorizontalScrollView>

Código (onCreate / onCreateView):

    final HorizontalScrollView hScroll = (HorizontalScrollView) value.findViewById(R.id.scrollHorizontal);
    final ScrollView vScroll = (ScrollView) value.findViewById(R.id.scrollVertical);
    vScroll.setOnTouchListener(new View.OnTouchListener() { //inner scroll listener         
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return false;
        }
    });
    hScroll.setOnTouchListener(new View.OnTouchListener() { //outer scroll listener         
        private float mx, my, curX, curY;
        private boolean started = false;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            curX = event.getX();
            curY = event.getY();
            int dx = (int) (mx - curX);
            int dy = (int) (my - curY);
            switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    if (started) {
                        vScroll.scrollBy(0, dy);
                        hScroll.scrollBy(dx, 0);
                    } else {
                        started = true;
                    }
                    mx = curX;
                    my = curY;
                    break;
                case MotionEvent.ACTION_UP: 
                    vScroll.scrollBy(0, dy);
                    hScroll.scrollBy(dx, 0);
                    started = false;
                    break;
            }
            return true;
        }
    });

Puede cambiar el orden de las vistas de desplazamiento. Simplemente cambie su orden en el diseño y en el código. Y obviamente, en lugar de WateverViewYouWant, pones el diseño / vistas que deseas desplazar en ambas direcciones.

Yan.Yurkin
fuente
Le sugiero que devuelva falso en hScroll.setOnTouchListener. Cuando cambié a falso, la lista comenzó a desplazarse "suavemente auto" en el dedo hacia arriba en ambas direcciones.
Greta Radisauskaite
1
Funciona cuando se desplaza horizontalmente primero. Cuando vertical es primero, va solo verticalmente
Irhala
6

Opción n. ° 1: puede crear un nuevo diseño de interfaz de usuario que no requiera desplazamiento horizontal y vertical simultáneo.

Opción n. ° 2: puede obtener el código fuente ScrollViewy HorizontalScrollViewaprender cómo los implementó el equipo principal de Android y crear su propia BiDirectionalScrollViewimplementación.

Opción n. ° 3: puede deshacerse de las dependencias que le exigen utilizar el sistema de widgets y dibujar directamente en el lienzo.

Opción # 4: Si te topas con una aplicación de código abierto que parece implementar lo que buscas, mira cómo lo hicieron.

CommonsWare
fuente
6

Prueba esto

<?xml version="1.0" encoding="utf-8"?>
<ScrollView android:id="@+id/Sview" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        xmlns:android="http://schemas.android.com/apk/res/android">
<HorizontalScrollView 
   android:id="@+id/hview" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content">
<ImageView .......
 [here xml code for image]
</ImageView>
</HorizontalScrollView>
</ScrollView>
MBMJ
fuente
6

Como el enlace de la Vista de desplazamiento bidimensional está muerto, no pude obtenerlo, así que creé mi propio componente. Maneja el lanzamiento y funciona correctamente para mí. La única restricción es que wrap_content podría no funcionar correctamente para ese componente.

https://gist.github.com/androidseb/9902093

androidseb
fuente
2

Tengo una solución para tu problema. Puede verificar el código ScrollView que maneja solo el desplazamiento vertical e ignora el horizontal y lo modifica. Quería una vista como una vista web, así que modifiqué ScrollView y funcionó bien para mí. Pero esto puede no satisfacer sus necesidades.

Avíseme a qué tipo de interfaz de usuario está apuntando.

Saludos,

Ravi Pandit

Ravi Pandit
fuente
1

Jugando con el código, puede poner un HorizontalScrollView en un ScrollView. De este modo, puede tener los dos métodos de desplazamiento en la misma vista.

Fuente: http://androiddevblog.blogspot.com/2009/12/creating-two-dimensions-scroll-view.html

Espero que esto te pueda ayudar.

Eriatolc
fuente
1
Esta no es una buena solución. Si observa, al arrastrar la vista, siempre está arrastrando vertical u horizontalmente. No puede arrastrar a lo largo de una diagonal usando vistas de desplazamiento anidadas.
Sky Kelsey
Lo que es peor que la falta de desplazamiento diagonal es la falta de ambas barras de desplazamiento restantes en el borde de la vista porque una está anidada. Un mejor ScrollView es la respuesta y el enlace de Cachapa al código de Matt Clark es la mejor solución en este momento debido a su integridad y generalización.
Huperniketes