• 20 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 04/05/2018

Affichez une Modal Bottom Sheet

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Dans ce dernier chapitre, nous allons implémenter une Bottom Sheet nous permettant d'afficher la liste des commentaires laissés par les utilisateurs sur un projet "design" spécifique. :)

Aperçu d'une
Exemple d'une "Modal Bottom Sheets"

En fait, ce concept "Material Design" s'illustre par une feuille qui monte sur le bas de l'écran pour révéler un contenu additionnel, un peu à la manière du clavier tactile.

Dans notre cas, notre Bottom Sheet contiendra une simple RecyclerView. Ainsi, créons dans un premier temps le ViewHolder et l'Adapter correspondant ainsi que le layout représentant chaque élément (item) de la RecyclerView.

Fichier de layout modal_fragment_comments_item.xml : 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="@dimen/margin_small"
    android:layout_marginTop="@dimen/margin_small"
    android:layout_marginRight="@dimen/margin_small"
    android:layout_gravity="center_vertical"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/modal_fragment_comments_item_image"
            android:layout_width="70dip"
            android:layout_height="70dip"
            android:padding="@dimen/padding_normal"/>

        <com.openclassrooms.wonder.views.WonderTextView
            android:id="@+id/modal_fragment_comments_item_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:fontName="Roboto_Medium"
            android:layout_gravity="center_vertical"/>

    </LinearLayout>

    <com.openclassrooms.wonder.views.WonderTextView
        android:id="@+id/modal_fragment_comments_item_text_view_date"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:fontName="Roboto_Light"
        android:gravity="end"
        android:layout_gravity="center_vertical"/>

</LinearLayout>

Explications : Chaque élément de la RecyclerView contiendra l'image de profil de l'utilisateur postant le commentaire, le commentaire texte et la date de publication.

Classe /adapters/ViewHolders/CommentViewHolder.java :

public class CommentViewHolder extends RecyclerView.ViewHolder {

    // FOR DESIGN
    @BindView(R.id.modal_fragment_comments_item_image) ImageView imageView;
    @BindView(R.id.modal_fragment_comments_item_text_view) TextView textView;
    @BindView(R.id.modal_fragment_comments_item_text_view_date) TextView textViewDate;

    public CommentViewHolder(View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }

    // ---

    public void updateWithComment(Comment comment, RequestManager glide){
        glide.load(comment.getUser().getImages().getImageProfile()).apply(RequestOptions.circleCropTransform()).into(imageView);
        textView.setText(comment.getComment());
        textViewDate.setText(getDate(comment.getCreatedOn()));
    }

    // ---

    private String getDate(Integer dateCreatedOn){
        try {
            long timestamp = dateCreatedOn * 1000L;
            SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
            Date netDate = (new Date(timestamp));
            return sdf.format(netDate);
        }
        catch(Exception ex){
            return "...";
        }
    }
}

Explications : Un simple ViewHolder permettant de mettre à jour chaque élément de notre RecyclerView. Nous avons créé également la méthode  getDate()  permettant de retourner, à partir d'une date au format numérique (Timestamp), une date au format texte (par exemple "19/09/2018").

Classe adapters/CommentAdapter.java : 

public class CommentAdapter extends RecyclerView.Adapter<CommentViewHolder> {

    // FOR DATA
    private List<Comment> comments;
    private RequestManager glide;

    public CommentAdapter(List<Comment> comments, RequestManager glide) {
        this.comments = comments;
        this.glide = glide;
    }

    @Override
    public CommentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(R.layout.modal_fragment_comments_item, parent, false);

        return new CommentViewHolder(view);
    }

    @Override
    public void onBindViewHolder(CommentViewHolder viewHolder, int position) {
        viewHolder.updateWithComment(this.getComment(position), this.glide);
    }

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

    public Comment getComment(int position){ return this.comments.get(position); }

    public void update(List<Comment> comments){
        this.comments = comments;
        this.notifyDataSetChanged();
    }
}

Explications : Un simple Adapter permettant de faire le lien entre la RecyclerView et le ViewHolder. Rien d'exceptionnel, vous devriez être capables de tout comprendre sans problème... ;)

Passons maintenant à la création de notre Modal Bottom Sheets. Les équipes de Google ont rendu son implémentation assez simple, puisque nous allons pouvoir la créer grâce à la notion de fragment ! :)

En fait, nous allons faire exactement comme nous faisons d'habitude pour créer un fragment, mais cette fois-ci, nous n'allons pas développer une classe héritant de  Fragment, mais plutôt une classe héritant de... BottomSheetDialogFragment, héritant elle-même de  DialogFragment  ! :p

Commençons par le plus facile, et créons le layout de ce fragment.

Fichier layout/modal_fragment_comments.xml : 

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/modal_fragment_comments_recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"/>

Explications : Nous avons ajouté ici une simple RecyclerView, car ce fragment ne contiendra que cela. Nous avons rajouté clipToPadding afin que notre RecyclerView couvre entièrement notre fragment. 

Passons maintenant à la création de la classe représentant ce fragment.

Classe CommentsModalFragment.java : 

public class CommentsModalFragment extends BottomSheetDialogFragment {

    // FOR DESIGN
    @BindView(R.id.modal_fragment_comments_recycler_view) RecyclerView recyclerView;

    // FOR DATA
    private CommentsViewModel commentsViewModel;
    private CommentAdapter commentAdapter;
    private Disposable disposable;
    private static final String KEY_PROJECT_ID = "KEY_PROJECT_ID";

    // FOR CONSTRUCTING
    public static CommentsModalFragment newInstance(Integer projectId){
        CommentsModalFragment bottomSheetFragment = new CommentsModalFragment();
        Bundle bundle = new Bundle();
        bundle.putInt(KEY_PROJECT_ID, projectId);
        bottomSheetFragment .setArguments(bundle);
        return bottomSheetFragment ;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.modal_fragment_comments, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        this.configureViewModel();
        this.configureRecyclerView();
        this.getComments();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        this.disposeWhenDestroy();
    }

    // -------------------
    // CONFIGURATION
    // -------------------

    private void configureViewModel(){
        Integer projectId = getArguments().getInt(KEY_PROJECT_ID);
        ViewModelFactory mViewModelFactory = Injection.provideViewModelFactory();
        this.commentsViewModel = ViewModelProviders.of(this, mViewModelFactory).get(CommentsViewModel.class);
        this.commentsViewModel.init(projectId);
    }

    private void configureRecyclerView(){
        this.commentAdapter = new CommentAdapter(new ArrayList<>(), Glide.with(this));
        this.recyclerView.setAdapter(this.commentAdapter);
        this.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    }

    // -------------------
    // DATA
    // -------------------

    private void getComments(){
        this.disposable = this.commentsViewModel.getComments()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .timeout(10, TimeUnit.SECONDS)
                .subscribe(this::updateDesign, throwable -> Log.e("TAG", "ERROR: ", throwable));
    }

    // -------------------
    // UI
    // -------------------

    private void updateDesign(ApiResponse projectResponse){
        this.commentAdapter.update(projectResponse.getComments());
    }

    // -------------------
    // UTILS
    // -------------------

    private void disposeWhenDestroy(){
        if (this.disposable != null && !this.disposable.isDisposed()) this.disposable.dispose();
    }
}

Explications : Don't panic ! Ce fragment semble assez complexe mais est en réalité relativement simple... :) En effet, nous avons déjà tout vu dans les précédents cours ! 

D'ailleurs, pour tout vous dire, il n'a aucune particularité si ce n'est qu'il hérite de la classe  BottomSheetDialogFragment. Le reste des méthodes sert principalement à configurer la RecyclerView et à récupérer les données sur l'API Behance en utilisant l'Architecture Components.

Pour terminer, nous allons lancer ce nouveau fragment dès qu'un utilisateur appuiera sur l'icône "Commentaire" de l'écran affichant les détails d'un projet. 

Clic sur le bouton
Clic sur le bouton "Commentaire"

Extrait de DetailFragment.java : 

public class DetailFragment extends BaseFragment {
    
    ...

    @OnClick(R.id.social_view_comments_button)
    public void onClickCommentsButton(View view) {
        CommentsModalFragment
            .newInstance(this.currentProject.getId())
            .show(getActivity().getSupportFragmentManager(), "MODAL");
    }

    ...
}

Explications : Nous avons créé un listener grâce à Butterknife pour gérer plus facilement les clics sur le bouton "Commentaire". Ainsi, au clic de l'utilisateur, nous lancerons le fragment  CommentsModalFragment  grâce à sa méthode show()  exigeant en paramètre le FragmentManager.

Et c'est tout ! Lancez maintenant votre application et admirez le résultat. :)

Aperçu du résultat
Aperçu du résultat
Exemple de certificat de réussite
Exemple de certificat de réussite