Scrollview vertical et horizontal sous Android

152

Je suis vraiment fatigué de chercher une solution pour Scrollview vertical et horizontal.

J'ai lu qu'il n'y a pas de vues / mises en page dans le cadre qui implémentent cette fonctionnalité, mais j'ai besoin de quelque chose comme ceci:

J'ai besoin de définir une mise en page dans l'autre, la mise en page enfant doit implémenter un défilement vertical / horizontal pour le déplacement.

Initialement implémenté un code qui déplaçait la mise en page pixel par pixel, mais je pense que ce n'est pas la bonne façon. Je l'ai essayé avec ScrollView et Horizontal ScrollView mais rien ne fonctionne comme je le souhaite, car il n'implémente que le défilement vertical ou horizontal.

Canvas n'est pas ma solution car j'ai besoin d'attacher des écouteurs à certains éléments enfants.

Que puis-je faire?

Kronos
la source
14
Il est absolument ridicule que cela ne soit pas disponible avec Android. Nous avons travaillé avec une demi-douzaine d'autres technologies d'interface utilisateur et il est rare de ne pas avoir une chose aussi basique qu'un conteneur à défilement horizontal / vertical.
flexicious.com
Ok, donc finalement nous avons écrit le nôtre pour notre datagrid: androidjetpack.com/Home/AndroidDataGrid . Fait bien plus qu'un simple défilement horizontal / vertical. Mais l'idée consiste essentiellement à envelopper un HScroller dans un Vertical Scroller. Vous devez également remplacer les méthodes enfant d'ajout / de suppression pour cibler le défilement interne et quelques autres manigances, mais cela fonctionne.
flexicious.com

Réponses:

91

En mélangeant certaines des suggestions ci-dessus, nous avons pu obtenir une bonne solution:

ScrollView personnalisé:

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 personnalisé:

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

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

}

la disposition:

<?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
la source
C'est une excellente réponse, j'avais d'autres éléments de défilement dans la vue qui devaient également changer. Tout ce que j'avais à faire était de déclencher leur défilement avec les autres.
T.Markle
1
Génial! Quelques ajustements pour prendre en compte la vélocité, et ce serait parfait (aussi, vous n'avez pas besoin de l'initiale LinearLayoutje suppose)
Romuald Brunet
Hé Mahdi, pouvez-vous me faire savoir sur quel contrôle / widget vous avez réigstéré l'auditeur tactile en classe d'activité.
Shachillies
Désolé pour le retard @DeepakSharma. Je n'ai enregistré aucun auditeur tactile dans l'activité, je viens de remplacer le onTouchEvent de l'activité.
Mahdi Hijazi
@MahdiHijazi pouvez-vous s'il vous plaît ajouter votre code de défilement horizontal et de défilement vertical dans github.com/MikeOrtiz/TouchImageView cela agrandit l' image avec succès sur le bouton, mais n'a pas réussi à faire défiler l'image
Erum
43

Comme cela semble être le premier résultat de recherche dans Google pour "Android vertical + horizontal ScrollView", j'ai pensé que je devrais ajouter ceci ici. Matt Clark a construit une vue personnalisée basée sur la source Android, et cela semble fonctionner parfaitement: Two Dimensional ScrollView

Attention, la classe de cette page a un bug de calcul de la largeur horizontale de la vue. Un correctif de Manuel Hilty est dans les commentaires:

Solution: remplacez l'instruction à la ligne 808 par ce qui suit:

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


Edit: Le lien ne fonctionne plus mais voici un lien vers une ancienne version du blog .

Cachapa
la source
3
Merci, c'est ce que je cherchais.
Andrey Agibalov
1
Cela fonctionne comme un charme après quelques modifications mineures pour moi. J'ai réimplémenté fillViewportet onMeasureexactement comme la source ScrollView (v2.1). J'ai également changé les deux premiers constructeurs avec: this( context, null );et this(context, attrs, R.attr.scrollViewStyle);. Avec cela, je peux utiliser la barre de défilement en utilisant setVerticalScrollBarEnabledet setHorizontalScrollBarEnableddans le code.
olivier_sdg
Fonctionne très bien aussi pour moi! Merci beaucoup!
Avio
mais comment obtenir la taille du pouce de défilement et la position du défilement à partir du bas vertical et de la droite en mode horizontal
Ravi
10
Il semble que le post lié a disparu.
loeschg
28

J'ai trouvé une meilleure solution.

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>

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

Cela fonctionne !!!

Si vous souhaitez charger une autre disposition ou un autre contrôle, la structure est la même.

Kronos
la source
Je viens d'essayer ceci, et cela fonctionne très bien! Il n'a pas d'indicateurs de défilement, ni de fling, mais comme il s'agit d'une approche de niveau inférieur, ces éléments pourraient être construits assez facilement. Merci!
ubzack
Cela a bien fonctionné pour moi aussi pour écrire une vue défilante en deux dimensions qui produisait une sortie en temps réel à partir du résultat des commandes à distance SSH sur le réseau.
Pilcrowpipe
@Kronos: Et si je veux seulement faire défiler horizontalement? Que doit être mon paramètre y dans container.scrollBy (currentX - x2, currentY - y2). Existe-t-il un moyen de le faire défiler horizontalement uniquement?
Ashwin
@Kronos: Ça défile trop loin. Existe-t-il un moyen d'arrêter le défilement lorsque la fin est atteinte?
Ashwin
Les boutons ne peuvent pas faire défiler, pourquoi?
salih kallai
25

Je l'utilise et fonctionne très 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>

Le lien source est ici: Android-spa

Redax
la source
2
Cela fonctionne "très bien" pour les contenus statiques, au mieux. Plusieurs ingénieurs Google ont déclaré que ce que vous essayez d'utiliser posera des problèmes ( groups.google.com/group/android-developers/browse_frm/thread/… et groups.google.com/group/android-beginners/browse_thread/thread/ … ).
CommonsWare
4
@CommonsWare: avec respect pour les brillants ingénieurs de Google, dans ces threads, ils vous ont dit de réécrire à partir de zéro votre propre composant: c'est la meilleure façon. C'est sûrement vrai. Mais ils ne disent pas que cette solution est mauvaise, et si cela fonctionne ... Peut-être que pour eux cela prend une minute, pour moi il vaut mieux écrire du xml en utilisant les composants existants. Que signifie «contenu statique»? J'utilise des boutons et des images.
Redax le
3
Par «contenu statique», j'entends des choses qui n'impliquent pas d'événements tactiles eux-mêmes. Mon point - pour les autres lisant cette réponse - est que, bien que votre solution puisse fonctionner pour un seul en ImageViewtant que contenu déroulant, elle peut ou non fonctionner pour d'autres contenus arbitraires, et que Google pense qu'il y aura des problèmes avec votre approche. L'opinion de Google est également importante en ce qui concerne le développement futur - ils n'essaient peut-être pas de s'assurer que votre approche fonctionne sur le long terme, s'ils conseillent aux gens de ne pas l'utiliser en premier lieu.
CommonsWare
Cela fonctionne bien pour moi ... Je rafraîchis une imageview dans cette vue par défilement en utilisant un fil
sonu thomas
19

Ma solution basée sur la réponse de Mahdi Hijazi , mais sans vues personnalisées:

Disposition:

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

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

Vous pouvez modifier l'ordre des vues de défilement. Changez simplement leur ordre dans la mise en page et dans le code. Et évidemment au lieu de WateverViewYouWant vous mettez la mise en page / vues que vous souhaitez faire défiler dans les deux sens.

Yan.Yurkin
la source
Je suggère fortement de renvoyer false dans hScroll.setOnTouchListener. Lorsque je suis passé à false, la liste a commencé à défiler "en douceur automatiquement" sur le doigt vers le haut dans les deux sens.
Greta Radisauskaite
1
Fonctionne lors du défilement horizontal d'abord. Quand la verticale est la première, ne va que verticalement
Irhala
6

Option n ° 1: vous pouvez proposer une nouvelle conception d'interface utilisateur qui ne nécessite pas de défilement horizontal et vertical simultané.

Option n ° 2: vous pouvez obtenir le code source vers ScrollViewet HorizontalScrollView, découvrir comment l'équipe Android principale les a implémentés et créer votre propre BiDirectionalScrollViewimplémentation.

Option 3: vous pouvez vous débarrasser des dépendances qui vous obligent à utiliser le système de widgets et dessiner directement sur le canevas.

Option n ° 4: Si vous tombez sur une application open source qui semble implémenter ce que vous cherchez, regardez comment ils l'ont fait.

CommonsWare
la source
6

Essaye ça

<?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
la source
6

puisque le lien Two Dimensional Scrollview est mort, je n'ai pas pu l'obtenir, j'ai donc créé mon propre composant. Il gère le flinging et fonctionne correctement pour moi. La seule restriction est que wrap_content peut ne pas fonctionner correctement pour ce composant.

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

androidseb
la source
2

J'ai une solution à votre problème. Vous pouvez vérifier le code ScrollView, il gère uniquement le défilement vertical et ignore l'horizontale et le modifie. Je voulais une vue comme une vue Web, donc ScrollView modifié et cela a bien fonctionné pour moi. Mais cela peut ne pas répondre à vos besoins.

Faites-moi savoir quel type d'interface utilisateur vous ciblez.

Cordialement,

Ravi Pandit

Ravi Pandit
la source
1

En jouant avec le code, vous pouvez placer un HorizontalScrollView dans un ScrollView. Ainsi, vous pouvez avoir les deux méthodes de défilement dans la même vue.

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

J'espère que cela pourra vous aider.

Eriatolc
la source
1
Ce n'est pas une bonne solution. Si vous remarquez, lorsque vous faites glisser la vue, vous faites toujours glisser verticalement ou horizontalement. Vous ne pouvez pas faire glisser le long d'une diagonale à l'aide de vues de défilement imbriquées.
Sky Kelsey
Ce qui est pire que le manque de défilement diagonal, c'est le manque de deux barres de défilement restant au bord de la vue car l'une est imbriquée. Un meilleur ScrollView est la réponse et le lien de Cachapa vers le code de Matt Clark est la meilleure solution pour le moment en raison de l'exhaustivité et de la généralisation.
Huperniketes