Otak-Atik RecyclerView | CardView | LayoutManager

RecyclerView adalah salah satu komponen ui di Android yang sering digunakan dalam sebuah aplikasi. Karena komponen ini digunakan untuk membuat sebuah app yang didalamnya terdapat list atau daftar yang dapat discroll ke atas ke bawah atau ke kanan ke kiri. Contoh aplikasi yang menggunakan RecyclerView : halaman utama aplikasi facebook, halaman chat diLine, WA atau BBM.

Ditutorial kali ini, saya akan memberikan contoh beberapa dari penggunaan RecyclerView, dari yang hanya berisikan text saja hingga yang isinya dengan berbagai kombinasi, seperti text dan gambar, tombol like dan lain-lain.

Tutorial kali ini, source codenya saya upload di github, dengan maksud agar kedepannya jika ada update, maka Anda dapat dengan mudah mengupdatenya juga. Berikut adalah linknya Recycler View Collection.

Screenshot hasil tutorial ini:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Untuk menggunakan komponen RecyclerView, paling tidak ada 3 hal yang perlu dipelajari juga, yaitu : RecyclerView Adapter, LayoutManager dan ItemDecoration. Ada sebuah video dari Google IO 2016 yang menjelaskan mengenai RecyclerView, berikut videonya:

 

andevindo-otak-atik-recyclerview-image1

Aplikasi ini membutuhkan sebuah file json sebagai datanya. Datanya ini disimpan didalam asset dari proyek. Sebelum mengolah data JSONnya, kita perlu mengambilnya terlebih dahulu. Saya menggunakan sebuah method seperti dibawah, untuk mendapatkan String dari file JSONnya.

private static String loadJSONFromAsset(Context context) {
        String json = null;
        try {

            InputStream is = context.getAssets().open("post.json");

            int size = is.available();

            byte[] buffer = new byte[size];

            is.read(buffer);

            is.close();

            json = new String(buffer, "UTF-8");


        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        } catch (NullPointerException ex){
            ex.printStackTrace();
            return null;
        }
        return json;

    }

Method tersebut terletak didalam class JSONHelper.

Setelah didapatkan Stringnya, lalu kita olah dengan cara menguraikannya. Karena data yang kita pakai ini bertipe JSON, maka sebaiknya Anda harus paham bagaimana cara menguraikan atau biasanya sering disebut parsing data JSON. Kebetulan saya telah membuat sebuah post untuk mengurai data JSON. Silahkan kunjungi post berikut :

Urai data JSON : http://www.andevindo.com/urai-data-json/

Datanya JSON yang telah diurai, tersimpan dalam List<Post> atau bisa dibilang kumpulan data bermodel Post. Post ini adalah model yang mempresentasikan data yang ada didalam file JSON tadi. Mari kita lihat file Post.java ini yang terletak didalam package Model.

package com.andevindo.recyclerview.Model;

/**
 * Created by heendher on 5/24/2016.
 */
public class Post {

    private String mTitle;
    private String mContent;
    private String mImage;
    private boolean mIsBookmarked;
    private boolean mIsLiked;

    public String getTitle() {
        return mTitle;
    }

    public void setTitle(String title) {
        mTitle = title;
    }

    public String getContent() {
        return mContent;
    }

    public void setContent(String content) {
        mContent = content;
    }

    public String getImage() {
        return mImage;
    }

    public void setImage(String image) {
        mImage = image;
    }

    public boolean isBookmarked() {
        return mIsBookmarked;
    }

    public void setBookmarked(boolean bookmarked) {
        mIsBookmarked = bookmarked;
    }

    public boolean isLiked() {
        return mIsLiked;
    }

    public void setLiked(boolean liked) {
        mIsLiked = liked;
    }
}

Data dari file JSON tadi berisikan title, content dan image. Nah file Post.java ini sebagai model dari data file JSON tadi. Lalu kenapa ada like dan bookmark di file Post.java ini? Sedangkan di file JSONnya tidak ada. Saya tambahkan 2 variabel tersebut untuk RecyclerView yang mempunyai interaksi like dan bookmark didalamnya. Jadi secara default, data like dan bookmark tadi bernilai false, karena memang belum diinisialisasi sebelumnya, karena di file JSONnya tidak terdapat data like dan bookmark.

Lanjut lagi, untuk contoh RecyclerView yang akan saya buat kali ini, terdapat 6 contoh, yaitu : Text Only, With Image, CardView, LikeBookmark, Grid, Staggered.

4 contoh pertama saya menggunakan LinearLayoutManager, kemudian 2 contoh berikutnya sesuai namanya, yaitu GridLayoutManager dan StaggeredLayoutManager. Bedanya apa dari ke-3 LayoutManager tersebut?

  1. LinearLayoutManager
    Hanya mendukung satu kolom jika itu orientasinya vertical dan satu baris jika orientasinya horizontal.
  2. GridLayoutManager
    Sama seperti LinearLayoutManager mempunyai 2 orientasi juga, namun dapat lebih dari 1 span. Maksudnya bisa lebih dari 1 kolom jika vertikal dan 1 baris jika horizontal.
  3. StaggeredLayoutManager
    Sama seperti GridLayoutManager, namun ukuran tinggi(jika vertikal) dari setiap view/child/item dari RecyclerViewnya menyesuaikan tinggi viewnya masing-masing. Agar lebih jelasnya lihat contoh dibawah.
GridLayoutManager
GridLayoutManager
StaggeredLayoutManager
StaggeredLayoutManager

Sekarang saatnya menelaah kode dari contoh RecyclerViewnya. Saya ambil contoh yang LikeBookmark saja, karena menurut saya, jika mengerti bagian ini, contoh lainnya sudah tercangkup.

LikeBookmarkAdapter

package com.andevindo.recyclerview.Adapter;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.andevindo.recyclerview.Model.Post;
import com.andevindo.recyclerview.R;
import com.squareup.picasso.Picasso;

import java.util.List;

/**
 * Created by heendher on 5/24/2016.
 */
public class LikeBookmarkAdapter extends RecyclerView.Adapter<LikeBookmarkViewHolder> implements LikeBookmarkViewHolder.LocalLikeBookmarkPresenter{

    private List<Post> mPostList;
    private Context mContext;
    private LikeBookmarkPresenter mPresenter;

    public LikeBookmarkAdapter(Context context) {
        mContext = context;
    }

    public void setData(List<Post> list) {
        mPostList = list;
        notifyDataSetChanged();
    }

    public void setPresenter(LikeBookmarkPresenter presenter) {
        mPresenter = presenter;
    }

    @Override
    public LikeBookmarkViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.recycler_like_bookmark, parent, false);
        return new LikeBookmarkViewHolder(view, mContext, mPresenter);
    }

    @Override
    public void onBindViewHolder(LikeBookmarkViewHolder holder, int position) {
        holder.setLocalPresenter(this);
        holder.bindData(mPostList.get(position));
    }

    @Override
    public int getItemCount() {
        if (mPostList == null)
            return 0;
        else
            return mPostList.size();
    }

    @Override
    public void onLike(Post post) {
        int index = mPostList.indexOf(post);
        if (post.isLiked())
            post.setLiked(false);
        else
            post.setLiked(true);
        notifyItemChanged(index);
    }

    @Override
    public void onBookmark(Post post) {
        int index = mPostList.indexOf(post);
        if (post.isBookmarked())
            post.setBookmarked(false);
        else
            post.setBookmarked(true);
        notifyItemChanged(index);
    }


    public interface LikeBookmarkPresenter {
        void onClick(Post post);

        void onLongClick(Post post);

        void onLike(Post post);

        void onBookmark(Post post);
    }
}

class LikeBookmarkViewHolder extends RecyclerView.ViewHolder {

    private TextView mTitle;
    private ImageView mImage, mLike, mBookmark;
    private Context mContext;
    private LikeBookmarkAdapter.LikeBookmarkPresenter mPresenter;
    private LocalLikeBookmarkPresenter mLocalPresenter;
    private Post mPost;

    public LikeBookmarkViewHolder(View itemView, Context context, LikeBookmarkAdapter.LikeBookmarkPresenter presenter) {
        super(itemView);
        mPresenter = presenter;
        mContext = context;
        mTitle = (TextView) itemView.findViewById(R.id.title);
        mImage = (ImageView) itemView.findViewById(R.id.image);
        mLike = (ImageView) itemView.findViewById(R.id.like);
        mBookmark = (ImageView) itemView.findViewById(R.id.bookmark);
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.onClick(mPost);
            }
        });
        itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                mPresenter.onLongClick(mPost);
                return true;
            }
        });
        mLike.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mLocalPresenter.onLike(mPost);
                mPresenter.onLike(mPost);
            }
        });
        mBookmark.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mLocalPresenter.onBookmark(mPost);
                mPresenter.onBookmark(mPost);
            }
        });
    }

    public void bindData(Post post) {
        mPost = post;
        mTitle.setText(post.getTitle());
        Picasso.with(mContext).load(post.getImage()).fit().centerCrop().into(mImage);
        if (post.isBookmarked())
            mBookmark.setImageResource(R.drawable.ic_bookmark_accent_24dp);
        else
            mBookmark.setImageResource(R.drawable.ic_bookmark_border_accent_24dp);

        if (post.isLiked())
            mLike.setImageResource(R.drawable.thumb_up_accent_24dp);
        else
            mLike.setImageResource(R.drawable.thumb_up_outline);
    }

    public void setLocalPresenter(LocalLikeBookmarkPresenter localPresenter){
        mLocalPresenter = localPresenter;
    }

    public interface LocalLikeBookmarkPresenter{
        void onLike(Post post);

        void onBookmark(Post post);
    }

}


Di file LikeBookmarkAdapter.java ini akan saya jelaskan perfungsinya.

public LikeBookmarkAdapter(Context context) {
        mContext = context;
    }

Fungsi tersebut adalah Constructor dan melewatkan Context sebagai paramaternya. Context ini digunakan untuk inflate layout dan penggunaan Picasso.

 

public void setData(List<Post> list) {
        mPostList = list;
        notifyDataSetChanged();
    }

Fungsi ini digunakan untuk memberikan data ke dalam List. Waktu awal adapter ini dideklarasikan, datanya adalah null, karena memang belum ada data yang dimasukkan. Lalu lewat fungsi inilah datanya dimasukkan. Ada fungsi notifyDataSetChanged() fungsinya untuk memberitahukan kepada RecyclerView, bahwa data telah berubah dan kemudian RecyclerView akan mengupdate tampilannya.

 

public void setPresenter(LikeBookmarkPresenter presenter) {
        mPresenter = presenter;
    }

Fungsi tersebut untuk men-set presenter yang berada di-class lain. Fungsi dari presenter ini adalah untuk menghubungkan 2 class yang berbeda. Semisal, jika ada interaksi seperti klik maka class lain akan mengetahui bahwa di-class lain ada interaksi klik.

 

@Override
    public LikeBookmarkViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.recycler_like_bookmark, parent, false);
        return new LikeBookmarkViewHolder(view, mContext, mPresenter);
    }

Fungsi diatas digunakan untuk memilih layout mana yang akan dipakai. Seperti contoh diatas, saya menggunakan layout recycler_like_bookmark. Kemudian hasil dari memilih dan memanggil layout tersebut, dikirim ke ViewHolder yang akan digunakan. Didalam ViewHolder kita dapat memasukkan nilai ke dalam view, atau menangkap interaksi user seperti klik, klik dua kali dan lain-lain.

 

@Override
    public void onBindViewHolder(LikeBookmarkViewHolder holder, int position) {
        holder.setLocalPresenter(this);
        holder.bindData(mPostList.get(position));
    }

Nah didalam fungsi ini, data dan presenter mulai dikaitkan dengan ViewHolder tadi. holder.setLocalPresenter(this) ini untuk menyambungkan class LikeBookmarkAdapter dengan LikeBookmarkViewHolder. holder.bindData(mPostList.get(position)) ini untuk mengirimkan data ke ViewHolder.

 

@Override
    public int getItemCount() {
        if (mPostList == null)
            return 0;
        else
            return mPostList.size();
    }

Sesuai namanya, untuk mendapatkan jumlah data/item yang ada. Didalamnya saya modifikasi dengan menambahkan kondisi mPostList == null untuk mengantisipasi jika Listnya masih null, karena jika tidak seperti itu, akan crash ketika memanggil mPostList.size().

 

@Override
    public void onLike(Post post) {
        int index = mPostList.indexOf(post);
        if (post.isLiked())
            post.setLiked(false);
        else
            post.setLiked(true);
        notifyItemChanged(index);
    }

    @Override
    public void onBookmark(Post post) {
        int index = mPostList.indexOf(post);
        if (post.isBookmarked())
            post.setBookmarked(false);
        else
            post.setBookmarked(true);
        notifyItemChanged(index);
    }

2 Fungsi diatas adalah fungsi yang digenerate ketika mengimplementasi LocalLikeBookmarkPresenter. Jadi jika button like atau bookmark diklik, maka fungsi tersebut akan dijalankan. Dan didalamnya terdapat proses penggantian status like dan bookmark. Kemudian setelah diganti, RecyclerView diberitahu dengan cara memanggil notifyItemChanged(index).

 

public interface LikeBookmarkPresenter {
        void onClick(Post post);

        void onLongClick(Post post);

        void onLike(Post post);

        void onBookmark(Post post);
    }

Nah ini kumpulan fungsi yang harus diterapkan jika ada yang mengimplementasinya. Ini adalah sebuah interface, biasanya digunakan sebagai perantara antara 2 class jika ingin berhubungan. Didalamnya terdapat fungsi abstract onClick, onLongClick, onLike dan onBookmark yang kegunaannya sesuai dengan namanya.

 

Kemudian masuk ke dalam class lain dari LikeBookmarkAdapter.java yaitu LikeBookmarkViewHolder.

public LikeBookmarkViewHolder(View itemView, Context context, LikeBookmarkAdapter.LikeBookmarkPresenter presenter) {
        super(itemView);
        mPresenter = presenter;
        mContext = context;
        mTitle = (TextView) itemView.findViewById(R.id.title);
        mImage = (ImageView) itemView.findViewById(R.id.image);
        mLike = (ImageView) itemView.findViewById(R.id.like);
        mBookmark = (ImageView) itemView.findViewById(R.id.bookmark);
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.onClick(mPost);
            }
        });
        itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                mPresenter.onLongClick(mPost);
                return true;
            }
        });
        mLike.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mLocalPresenter.onLike(mPost);
                mPresenter.onLike(mPost);
            }
        });
        mBookmark.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mLocalPresenter.onBookmark(mPost);
                mPresenter.onBookmark(mPost);
            }
        });
    }

Nah fungsi tersebut adalah fungsi Constructor. Didalamnya dideklarasikan setiap view yang ada di layout yang digunakan. Dan juga memberikan listener kepada view yang diperlukan.

 

public void bindData(Post post) {
        mPost = post;
        mTitle.setText(post.getTitle());
        Picasso.with(mContext).load(post.getImage()).fit().centerCrop().into(mImage);
        if (post.isBookmarked())
            mBookmark.setImageResource(R.drawable.ic_bookmark_accent_24dp);
        else
            mBookmark.setImageResource(R.drawable.ic_bookmark_border_accent_24dp);

        if (post.isLiked())
            mLike.setImageResource(R.drawable.thumb_up_accent_24dp);
        else
            mLike.setImageResource(R.drawable.thumb_up_outline);
    }

Fungsi ini untuk membarikan nilai ke view yang ada. Seperti mTitle.setText(post.getTitle()) untuk men-set judul kedalam mTitle.

 

public void setLocalPresenter(LocalLikeBookmarkPresenter localPresenter){
        mLocalPresenter = localPresenter;
    }

Digunakan untuk menghubungkan class LikeBookmarkViewHolder dengan LikeBookmarkAdapter.

 

public interface LocalLikeBookmarkPresenter{
        void onLike(Post post);

        void onBookmark(Post post);
    }

Interface untuk menghubungan class LikeBookmarkViewHolder dengan LikeBookmarkAdapter.

 

Setelah adapter dibuat, lalu adapter ini digunakan diActivity ataupun Fragment yang akan menggunakannya. Ditutorial ini, saya menggunakan Fragment. Setiap adapter yang ada digunakan untuk satu class Fragment yang saya buat, dengan maksud agar lebih mudah untuk dipahami.

LikeBookmarkFragment

package com.andevindo.recyclerview.View.Fragment;


import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.andevindo.recyclerview.Adapter.CardViewAdapter;
import com.andevindo.recyclerview.Adapter.LikeBookmarkAdapter;
import com.andevindo.recyclerview.Helper.JSONHelper;
import com.andevindo.recyclerview.Model.Post;
import com.andevindo.recyclerview.R;
import com.andevindo.recyclerview.View.Support.LinearItemDecoration;

/**
 * A simple {@link Fragment} subclass.
 */
public class LikeBookmarkFragment extends Fragment implements LikeBookmarkAdapter.LikeBookmarkPresenter{

    private RecyclerView mRecyclerView;
    private LikeBookmarkAdapter mAdapter;
    private LinearItemDecoration mLinearItemDecoration;

    public LikeBookmarkFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_recycler_view, container, false);

        mLinearItemDecoration = new LinearItemDecoration(getContext());
        mRecyclerView = (RecyclerView)view.findViewById(R.id.recycler);
        mAdapter = new LikeBookmarkAdapter(getContext());
        mAdapter.setPresenter(this);
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        mRecyclerView.addItemDecoration(mLinearItemDecoration);
        mAdapter.setData(JSONHelper.getData(getContext()));
        return view;
    }

    @Override
    public void onClick(Post post) {
        Toast.makeText(getContext(), "Click di post tentang " + post.getTitle(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onLongClick(Post post) {
        Toast.makeText(getContext(), "Long click di post tentang " + post.getTitle(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onLike(Post post) {
        if (post.isLiked())
            Toast.makeText(getContext(), "Like", Toast.LENGTH_SHORT).show();
        else
            Toast.makeText(getContext(), "Dislike", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onBookmark(Post post) {
        if (post.isBookmarked())
            Toast.makeText(getContext(), "Bookmark", Toast.LENGTH_SHORT).show();
        else
            Toast.makeText(getContext(), "Unbookmark", Toast.LENGTH_SHORT).show();
    }
}

Berikut penjelasannya:

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_recycler_view, container, false);

        mLinearItemDecoration = new LinearItemDecoration(getContext());
        mRecyclerView = (RecyclerView)view.findViewById(R.id.recycler);
        mAdapter = new LikeBookmarkAdapter(getContext());
        mAdapter.setPresenter(this);
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        mRecyclerView.addItemDecoration(mLinearItemDecoration);
        mAdapter.setData(JSONHelper.getData(getContext()));
        return view;
    }

Difungsi onCreateView ini RecyclerView, Adapter, ItemDecoration dideklarasikan. Dan juga presenter yang digunakan untuk menghubungkan 2 class diterapkan juga.  Di contoh yang LikeBookmark ini, saya menggunakan LayoutManager yang linear, sehingga ItemDecoration yang saya gunakan juga yang linear. 3 Item Decoration bisa dilihat dipackage View>Support. Kemudian untuk memberikan data ke RecyclerView digunakan :

mAdapter.setData(JSONHelper.getData(getContext())). JSONHelper sendiri adalah class yang awal disebutkan, yang berisi tentang cara mengambil data dari assets dan mengurai data dari JSON ke dalam List<Post>.

 

    @Override
    public void onClick(Post post) {
        Toast.makeText(getContext(), "Click di post tentang " + post.getTitle(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onLongClick(Post post) {
        Toast.makeText(getContext(), "Long click di post tentang " + post.getTitle(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onLike(Post post) {
        if (post.isLiked())
            Toast.makeText(getContext(), "Like", Toast.LENGTH_SHORT).show();
        else
            Toast.makeText(getContext(), "Dislike", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onBookmark(Post post) {
        if (post.isBookmarked())
            Toast.makeText(getContext(), "Bookmark", Toast.LENGTH_SHORT).show();
        else
            Toast.makeText(getContext(), "Unbookmark", Toast.LENGTH_SHORT).show();
    }

Fungsi-fungsi diatas akan digenerate jika class tersebut mengimplementasi LikeBookmarkPresenter. Anda bisa memberikan action yang lainnya didalam fungsi-fungsi tersebut. Misal seperti onClick, jika view diklik, maka akan membuat Activity baru misalnya. Maka Anda dapat menambahkan kode untuk membuka Activity baru didalam fungsi onClick(Post post).

 

Sekian dulu tutorial kali ini tentang RecyclerView. Untuk source codenya saya upload di-github, untuk mempermudah me-maintenance source code maupun blog ini. Jika anda kurang familiar dengar git, anda bisa mendownload source code tersebut lewat github juga.

Source code : Recycler View Collection