183 votes

Comment faire un ListView horizontal dans Android ?

Duplicata possible :
ListView horizontal dans Android ?

Comme beaucoup de choses dans Android, vous ne penseriez pas qu'il s'agisse d'un problème si difficile à résoudre, mais vous auriez tort. Et, comme beaucoup de choses dans Android, l'API ne fournit même pas un point de départ raisonnablement extensible. Que je sois damné si je dois créer ma propre ListView, alors que tout ce que je veux, c'est prendre la chose et la retourner. \rant

Bon, maintenant que j'ai fini de m'énerver, parlons du problème lui-même. Ce dont j'ai besoin, c'est quelque chose qui ressemble exactement à l'image suivante Gallery mais sans la fonction de verrouillage central. Je n'ai pas vraiment besoin ListView mais c'est un élément indispensable. En général, je pourrais faire ce que je veux avec une fonction LinearLayout à l'intérieur d'un ScrollView mais j'ai besoin que les vues enfant proviennent d'un fichier ListAdapter et j'aimerais vraiment avoir un recycleur de vues. Et je vraiment ne veulent pas écrire de code de mise en page.

J'ai jeté un coup d'oeil au code source de certaines de ces classes...

Galerie : Il semble que je puisse utiliser la Gallery si je surcharge la plupart des méthodes 'onXyz', que je copie tout leur code source, mais que je m'abstiens d'appeler scrollIntoSlots() . Mais je suis sûr que si je fais cela, je me heurterai à un champ membre inaccessible ou à une autre conséquence imprévue.

AbsSpinner : Depuis le mRecycler est package-private, je doute que je puisse étendre cette classe.

AbsListView : Il semble que cette classe ne soit destinée qu'au défilement vertical, donc aucune aide ici.

AdapterView : Je n'ai jamais eu à étendre cette classe directement. Si vous me dites que c'est facile à faire, et qu'il est facile de mettre en place ma propre RecycleBin Je serai très sceptique, mais je vais essayer.

Je suppose que je pourrais éventuellement copier les deux AbsSpinner y Gallery pour obtenir ce que je veux... en espérant que ces classes n'utilisent pas une variable privée du paquet à laquelle je ne peux pas accéder. Pensez-vous que c'est une bonne pratique ? Quelqu'un a-t-il des tutoriels ou des solutions tierces qui pourraient me mettre sur la bonne voie ?

Mise à jour :
La seule solution que j'ai trouvée jusqu'à présent est de tout faire moi-même. Depuis que j'ai posé cette question, j'ai modifié les paramètres suivants AdapterView et j'ai implémenté mon propre "HorizontalListView" à partir de rien. La seule façon d'outrepasser réellement la fonction de verrouillage central de la galerie est d'outrepasser le paramètre privé scrollIntoSlots ce qui, je crois, nécessiterait de générer une sous-classe au moment de l'exécution. Si vous êtes assez audacieux pour le faire, c'est sans doute la meilleure solution, mais je ne veux pas me fier à des méthodes non documentées qui pourraient changer.

Swathi EP ci-dessous a suggéré que je donne le Gallery un OnTouchListener et remplacer la fonctionnalité de défilement. Si vous ne vous souciez pas de la prise en charge du défilement dans votre liste, ou si vous êtes d'accord pour que les vues reviennent au centre à la fin de l'animation du défilement, alors cette option est la suivante sera travailler pour vous ! Cependant, au final, il s'avère toujours impossible de supprimer la fonction de verrouillage central sans supprimer le support du fling. Et je vous le demande, quel genre de liste n'a pas de fling ?

Donc, hélas, cela n'a pas fonctionné pour moi :-( Mais si cette approche vous intéresse, lisez la suite...

J'ai également dû faire quelques ajouts au code de Swathi pour obtenir ce que je voulais. Dans GestureListener.onTouch en plus de déléguer au détecteur de gestes, je devais également renvoyer true pour les éléments suivants ACTION_UP y ACTION_CANCEL événements. Cela a permis de désactiver la fonction de verrouillage central, mais aussi de désactiver le flingage. J'ai pu réactiver le flottement en faisant en sorte que mon propre GestureListener soit délégué à l'événement onFling méthode. Si vous voulez l'essayer, allez dans votre code d'exemple ApiDemos et remplacez la classe Gallery1.java par le code suivant :

import com.example.android.apis.R;

import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.GestureDetector;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.View.OnTouchListener;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;

public class Gallery1 extends Activity {

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

        // Reference the Gallery view
        final Gallery g = (Gallery) findViewById(R.id.gallery);

        // Set the adapter to our custom adapter (below)
        g.setAdapter(new ImageAdapter(this));

        // Set a item click listener, and just Toast the clicked position
        g.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView parent, View v, int position, long id) {
                Toast.makeText(Gallery1.this, "" + position, Toast.LENGTH_SHORT).show();
            }
        });

        // Gesture detection
        final GestureDetector gestureDetector = new GestureDetector(new MyGestureDetector(g));
        OnTouchListener gestureListener = new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                boolean retVal = gestureDetector.onTouchEvent(event);
                int action = event.getAction();
                if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
                    retVal = true;
                    onUp();
                }
                return retVal;
            }

            public void onUp() {
                // Here I am merely copying the Gallery's onUp() method.
                for (int i = g.getChildCount() - 1; i >= 0; i--) {
                    g.getChildAt(i).setPressed(false);
                }
                g.setPressed(false);
            }
        };
        g.setOnTouchListener(gestureListener);

        // We also want to show context menu for longpressed items in the gallery
        registerForContextMenu(g);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        menu.add(R.string.gallery_2_text);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
        Toast.makeText(this, "Longpress: " + info.position, Toast.LENGTH_SHORT).show();
        return true;
    }

    public class ImageAdapter extends BaseAdapter {
        int mGalleryItemBackground;

        public ImageAdapter(Context c) {
            mContext = c;
            // See res/values/attrs.xml for the <declare-styleable> that defines
            // Gallery1.
            TypedArray a = obtainStyledAttributes(R.styleable.Gallery1);
            mGalleryItemBackground = a.getResourceId(
                    R.styleable.Gallery1_android_galleryItemBackground, 0);
            a.recycle();
        }

        public int getCount() {
            return mImageIds.length;
        }

        public Object getItem(int position) {
            return position;
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView i = new ImageView(mContext);

            i.setImageResource(mImageIds[position]);
            i.setScaleType(ImageView.ScaleType.FIT_XY);
            i.setLayoutParams(new Gallery.LayoutParams(136, 88));

            // The preferred Gallery item background
            i.setBackgroundResource(mGalleryItemBackground);

            return i;
        }

        private Context mContext;

        private Integer[] mImageIds = {
                R.drawable.gallery_photo_1,
                R.drawable.gallery_photo_2,
                R.drawable.gallery_photo_3,
                R.drawable.gallery_photo_4,
                R.drawable.gallery_photo_5,
                R.drawable.gallery_photo_6,
                R.drawable.gallery_photo_7,
                R.drawable.gallery_photo_8
        };
    }

    public class MyGestureDetector extends SimpleOnGestureListener {

        private Gallery gallery;

        public MyGestureDetector(Gallery gallery) {
            this.gallery = gallery;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 
                float velocityY) {
            return gallery.onFling(e1, e2, velocityX, velocityY);
        }
    }

}

48voto

Paul Points 1578

Après avoir lu cet article, j'ai implémenté mon propre listview horizontal. Vous pouvez le trouver : http://www.dev-smart.com/?p=34 Faites-moi savoir si cela vous aide...

12voto

Thira Points 678

Avez-vous envisagé d'utiliser un HorizontalScrollView pour envelopper les éléments de votre liste ? Cela permettra à chacun de vos éléments de liste de défiler horizontalement (vous pouvez choisir ce que vous y mettez et en faire des éléments dynamiques similaires à ListView). Cela fonctionnera bien si vous ne recherchez qu'une seule ligne d'éléments.

4voto

Cette réponse est peut-être très tardive mais elle fonctionne pour nous. Nous utilisons la même galerie fournie par Android, mais nous avons ajusté la marge gauche de telle sorte que l'extrémité gauche de l'écran soit considérée comme le centre de la galerie. Cela a vraiment bien fonctionné pour nous.

4voto

Reuben Scratton Points 22314

Vous savez, il puede Il est possible d'utiliser un ListView existant en modifiant judicieusement les paramètres de l'affichage. dispatchDraw() (pour faire pivoter le canevas de 90 degrés), onTouch() (pour échanger les coordonnées X et Y du MotionEvent) et peut-être onMeasure() ou autre pour lui faire croire que c'est y par x plutôt que x par y...

Je n'ai aucune idée si cela fonctionnerait réellement mais ce serait amusant de le découvrir :)

3voto

free Points 31

J'ai utilisé Pauls (voir son responder ) Mise en œuvre de HorizontalListview et cela fonctionne, merci beaucoup pour le partage !

J'ai légèrement modifié son HorizontalListView-Class (btw. Paul il y a une faute de frappe dans votre nom de classe, votre nom de classe est "HorizontialListView" au lieu de "HorizontalListView", le "i" est de trop) pour mettre à jour les child-views lorsqu'ils sont sélectionnés.

UPDATE : Le code que j'ai posté ici était erroné, je suppose, car j'ai rencontré des problèmes avec la sélection (je pense que cela a à voir avec le recyclage des vues), je dois retourner à la planche à dessin...

UPDATE 2 : Ok Problème résolu, j'ai simplement commenté "removeNonVisibleItems(dx) ;" dans "onLayout(..)", je suppose que cela va nuire aux performances, mais comme je n'utilise que de très petites listes, ce n'est pas un problème pour moi.

J'ai essentiellement utilisé ce tutoriel ici sur developerlife J'ai remplacé ListView par HorizontalListView de Paul, et j'ai fait les changements nécessaires pour permettre une sélection "permanente" (un enfant sur lequel on clique change d'apparence, et quand on clique à nouveau, il change à nouveau).

Je suis un débutant, donc probablement beaucoup de choses laides dans le code, faites-moi savoir si vous avez besoin de plus de détails.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X