Material Design : RecyclerView et CardView

Lors de la Google IO 2014, la firme a introduit un nouveau design, nommé Material Design. Ce dernier a pour but de standardiser le design des applications, en proposant une ergonomie adaptée et un look qui déchire 😀

Cette design est la base de leur nouvelle version d’Android : Lollipop

material

 

Voyons ensemble comment l’implémenter !

cards

 

Le principal atout du material design est l’ajout d’une profondeur dans l’application. Les vues sont donc soumises à un 3ème index (z-index en css), nommé ici l’élévation. Parmi les nouvelles vues sont disponibles les CardsView (d’où le nom : afficher des cards/cartes, comme ci dessus) et le RecyclerView, venant succéder aux ListView/GridView.

elevation

Ces nouvelles vues sont disponibles dans la librairie de support v7 d’android, il vous faut donc commencer par importer dans votre fichier gralde :

build.gralde

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.1.1'
    compile 'com.android.support:recyclerview-v7:22.1.1'
    compile 'com.android.support:cardview-v7:22.1.1'
}

CardView

Une CardView s’utilise simplement comme n’importe quelle vue, depuis nos layout xml :

Petit  détail, la CardView agit comme une FrameView, c’est à dire qu’elle n’influence pas la disposition de ses sous-vues, si vous souhaitez les ordonnez, ajoutez-y un LinearLayout ou un RelativeLayout.

cell_cards.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"

    app:cardBackgroundColor="@android:color/white"
    app:cardCornerRadius="2dp"
    app:cardElevation="2dp">

    <!-- Les CardView possèdent des attributs supplémentaires dont
         - cardBackgroundColor
         - cardElevation pour l'élévation (donc aussi l'ombre)
         - cardCornerRadius pour arrondir les angles
     -->

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <!-- Les CardView agissent comme des FrameLayout,
         pour avoir une organisation verticale nous devons
         donc rajouter un LinearLayout -->

        <ImageView
            android:id="@+id/image"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:scaleType="centerCrop"
            tools:src="@drawable/parisguidetower" />

        <TextView
            android:id="@+id/text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?android:selectableItemBackground"
            android:padding="20dp"
            tools:text="Paris"
            android:fontFamily="sans-serif"
            android:textColor="#333"
            android:textSize="18sp" />
    </LinearLayout>

</android.support.v7.widget.CardView>

Voici ce qui devrait apparaître dans votre preview :

Capture d’écran 2015-05-01 à 15.06.14

RecyclerView

Une RecyclerView est une nouvelle façon d’afficher une liste ou une grille de vues.

Elle peux s’apparenter à une ListView mais permet beaucoup plus de personnalisation.

Comme toutes vues, il faut la déclarer dans nos layout xml :

activity_main.xml

<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

A la même façon que les ListView, il faut créer un Adapter, mais cette fois ci il doit étendre RecyclerView.Adapter<>. Cet adapter est typé pour un fonctionner avec une certaine classe de ViewHolder (objet qui va garder les références vers les vues de chaque cellule). Dans notre cas il va donc falloir créer un ViewHolder, nous le nommerons MyViewHolder :

public class MyViewHolder extends RecyclerView.ViewHolder{

    private TextView textViewView;
    private ImageView imageView;

    //itemView est la vue correspondante à 1 cellule
    public MyViewHolder(View itemView) {
        super(itemView);

        //c'est ici que l'on fait nos findView

        textViewView = (TextView) itemView.findViewById(R.id.text);
        imageView = (ImageView) itemView.findViewById(R.id.image);
    }

    //puis ajouter une fonction pour remplir la cellule en fonction d'un MyObject
    public void bind(MyObject myObject){
        textViewView.setText(myObject.getText());
        Picasso.with(imageView.getContext()).load(myObject.getImageUrl()).centerCrop().fit().into(imageView);
    }
}

Notre adapter va donc être du type

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder>

Il possède relativement les mêmes fonctions qu’un ArrayAdapter, à la différence que la fonction onCreateView est divisée en 2 appels : onCreateViewHolder et onBindViewHolder

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {

    List<MyObject> list;

    //ajouter un constructeur prenant en entrée une liste
    public MyAdapter(List<MyObject> list) {
        this.list = list;
    }

    //cette fonction permet de créer les viewHolder
    //et par la même indiquer la vue à inflater (à partir des layout xml)
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int itemType) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.cell_cards,viewGroup,false);
        return new MyViewHolder(view);
    }

    //c'est ici que nous allons remplir notre cellule avec le texte/image de chaque MyObjects
    @Override
    public void onBindViewHolder(MyViewHolder myViewHolder, int position) {
        MyObject myObject = list.get(position);
        myViewHolder.bind(myObject);
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

}

MyObject.java

public class MyObject {
    private String text;
    private String imageUrl;

    public MyObject(String text, String imageUrl) {
        this.text = text;
        this.imageUrl = imageUrl;
    }

    //getters & setters
}

Utilisons le maintenant dans notre Activity
Une étape importante est d’affecter un LayoutManager à notre activity, sans quoi une erreur sera levée
RecyclerView﹕ No layout manager attached; skipping layout

setLayoutManager(new LinearLayoutManager(this));

public class MainActivity extends ActionBarActivity {

    private RecyclerView recyclerView;

    private List<MyObject> cities = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //remplir la ville
        ajouterVilles();

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);

        //définit l'agencement des cellules, ici de façon verticale, comme une ListView
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        //pour adapter en grille comme une RecyclerView, avec 2 cellules par ligne
        //recyclerView.setLayoutManager(new GridLayoutManager(this,2));

        //puis créer un MyAdapter, lui fournir notre liste de villes.
        //cet adapter servira à remplir notre recyclerview
        recyclerView.setAdapter(new MyAdapter(cities));
    }

    private void ajouterVilles() {
        cities.add(new MyObject("France","http://www.telegraph.co.uk/travel/destination/article130148.ece/ALTERNATES/w620/parisguidetower.jpg"));
        cities.add(new MyObject("Angleterre","http://www.traditours.com/images/Photos%20Angleterre/ForumLondonBridge.jpg"));
        cities.add(new MyObject("Allemagne","http://tanned-allemagne.com/wp-content/uploads/2012/10/pano_rathaus_1280.jpg"));
        cities.add(new MyObject("Espagne","http://www.sejour-linguistique-lec.fr/wp-content/uploads/espagne-02.jpg"));
        cities.add(new MyObject("Italie","http://retouralinnocence.com/wp-content/uploads/2013/05/Hotel-en-Italie-pour-les-Vacances2.jpg"));
        cities.add(new MyObject("Russie","http://www.choisir-ma-destination.com/uploads/_large_russie-moscou2.jpg"));
    }

}

Compilez et admirez le résultat :

cardsview_sample_1

 

 

Afficher nos cards en grille

L’objet RecyclerView permet aussi d’afficher les éléments sous forme de grille au lieu de liste, en une simple modification du LayoutManager (objet qui définit l’organisation des vues dans la RecyclerView). Il suffit de le passer à une GridViewManager(context,nombreCellulesParLigne)

recyclerView.setLayoutManager(new GridLayoutManager(this,2));

 

device-2015-05-01-201313

 

 

 

Les sources de ce tuto sont disponibles à l’adresse suivante : https://github.com/florent37/TutosAndroidFrance/tree/master/RecyclerViewSample

 

Si vous aussi vous aimez le material design, je vous invite à regardez la documentation officielle de material design, elle est très bien faite et plutôt détaillée

Et pour l’inspiration venez découvrir MaterialUp, comment ne pas aimer ce site 😀

Capture d’écran 2015-05-01 à 15.18.00

16 commentaire sur “Material Design : RecyclerView et CardView

  1. Salut, je suis débutant en android et je voudrais juste savoir quelles sont les getters et setters. parce que j’ai une erreur avec ceux que j’ai fait moi même.
    Merci de ta reponse. Sinon très bon tuto ;D

    1. Les getters & setters sont des moyens d’accéder à tes propriétés privés depuis un autre objet. Pour chaque propriété on va créé 2 fonctions publiques :
      //propriété privée
      private String text;

      //getter
      public String getText(){
      return text;
      }

      //setter
      public void setText(String text){
      this.text = text;
      }

      Grace à ces 2 fonctions tu maitrise la façon dont tu retourne et dont tu peux affecter tes propriétés privées

      Si tu veux voir la classe complète : https://github.com/florent37/TutosAndroidFrance/blob/master/RecyclerViewSample/app/src/main/java/com/tutosandroidfrance/recyclerviewsample/MyObject.java

      1. tu peux d’ailleurs les générer avec android studio

        cmd+n / generate getters & setters

        sous mac

        alt+insert / generate getters & setters

        sous pc

          1. de rien, n’hésite pas à nous contacter si tu as des sujets en android que tu voudrais que l’on traite ici 🙂

  2. Je suis désolé de revenir encore une fois. mais j’ai un problème avec l’Adapter.

    public class MyAdapter extends RecyclerView.Adapter { //une erreur à cette ligne

    List list;

    //ajouter un constructeur prenant en entrée une liste
    public MyAdapter(List list) {
    this.list = list;
    }

    //cette fonction permet de créer les viewHolder
    //et par la même indiquer la vue à inflater (à partir des layout xml)
    @Override //une ici
    public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int itemType) {
    View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.cell_cards,viewGroup,false);
    return new MyViewHolder(view); //une autre ici
    }

    //c’est ici que nous allons remplir notre cellule avec le texte/image de chaque MyObjects
    @Override
    public void onBindViewHolder(MyViewHolder myViewHolder, int position) {
    MyObject myObject = list.get(position);
    myViewHolder.bind(myObject);
    }

    @Override
    public int getItemCount() {
    return list.size();
    }

    }

    J’ai bien copié le java du MyViewHolder et MyObject, je sais pas si quelqu’un peu m’aider ?

  3. 08 10:41:42.566 22513 22513 E AndroidRuntime: Process: com.test.urgencecall, PID: 22513

    05-08 10:41:42.566 22513 22513 E AndroidRuntime: at com.test.urgencecall.MyViewHolder.(MyViewHolder.java:24)

    05-08 10:41:42.566 22513 22513 E AndroidRuntime: at com.test.urgencecall.MyAdapter.onCreateViewHolder(MyAdapter.java:27)

    05-08 10:41:42.566 22513 22513 E AndroidRuntime: at com.test.urgencecall.MyAdapter.onCreateViewHolder(MyAdapter.java:13)

    voilà, je sais pas si ça va t’aider …

    1. envoie moi le log complet s’il te plait, il indique les lignes mais pas quelle erreur est remontée. Il serait bien de savoir si c’est un NullPointerException ou autre 🙂

      1. https://www.dropbox.com/s/1q1u4tduq3koj44/UrgenceCall.zip?dl=0 ici tu pourras trouver tout mon code.

        ici il y a le LogCat : —- 8 mai 2015 10:54:58 —-

        05-08 10:54:51.352 2176 2176 I Timeline: Timeline: Activity_launch_request id:com.test.urgencecall time:104195225

        05-08 10:54:51.353 809 1687 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.test.urgencecall/.MainActivity bnds=[224,267][435,537] (has extras)} from uid 10032 on display 0

        05-08 10:54:51.437 809 1356 I ActivityManager: Start proc com.test.urgencecall for activity com.test.urgencecall/.MainActivity: pid=25174 uid=10200 gids={50200, 9997, 3003, 1028, 1015} abi=armeabi-v7a

        05-08 10:54:51.831 25196 25196 I dex2oat : /system/bin/dex2oat –runtime-arg -classpath –runtime-arg –instruction-set=arm –instruction-set-features=div –runtime-arg -Xrelocate –boot-image=/system/framework/boot.art –dex-file=/data/data/com.test.urgencecall/cache/ads926679025.jar –oat-fd=24 –oat-location=/data/data/com.test.urgencecall/cache/ads926679025.dex –runtime-arg -Xms64m –runtime-arg -Xmx512m

        05-08 10:54:52.146 25174 25174 E AndroidRuntime: Process: com.test.urgencecall, PID: 25174

        05-08 10:54:52.146 25174 25174 E AndroidRuntime: at com.test.urgencecall.MyViewHolder.(MyViewHolder.java:24)

        05-08 10:54:52.146 25174 25174 E AndroidRuntime: at com.test.urgencecall.MyAdapter.onCreateViewHolder(MyAdapter.java:27)

        05-08 10:54:52.146 25174 25174 E AndroidRuntime: at com.test.urgencecall.MyAdapter.onCreateViewHolder(MyAdapter.java:13)

        05-08 10:54:52.148 809 1534 W ActivityManager: Force finishing activity com.test.urgencecall/.MainActivity

        05-08 10:54:52.451 809 1534 I WindowManager: Screenshot max retries 4 of Token{37a8c4f ActivityRecord{31b486ae u0 com.test.urgencecall/.MainActivity t399 f}} appWin=Window{1688431a u0 com.test.urgencecall/com.test.urgencecall.MainActivity} drawState=1

        05-08 10:54:52.953 809 829 W ActivityManager: Activity pause timeout for ActivityRecord{31b486ae u0 com.test.urgencecall/.MainActivity t399 f}

        05-08 10:54:53.806 809 1196 I WindowState: WIN DEATH: Window{1688431a u0 com.test.urgencecall/com.test.urgencecall.MainActivity}

        05-08 10:54:53.857 809 1709 I ActivityManager: Process com.test.urgencecall (pid 25174) has died

        —- 8 mai 2015 10:54:58 —-

        1. enfin j’ai trouvé !
          c’est un problème de ClassCast. Dans ton code tu utilise des com.rey.material.widget.TextView, et dans ta vue tu as des TextView Android

          retire simplement
          import com.rey.material.widget.TextView;

          de ton ViewHolder 🙂

  4. J’ai encore une question, ce serait possible de changer d’activité avec un OnItemClickeListener et un switch ?

    1. Le onitemclick n’existe plus, mais vous pouvez ajouter un clickListener sur les cellules, faire remonter l’évènement au fragment ou à l’activity pour lancer la seconde

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *