Dalam membuat sebuah aplikasi, biasanya kita dihadapkan pada banyaknya data yang ditampilkan dalam aplikasi atau kepada user. jika salah dalam menerapkan Koding, misalnya data yang ditampilkan pada user diload seluruhnya, tentu ini tidak bagus karena akan memperlambat data yang akan ditampilkan, bayangkan jika data tersebut ada 1 juta dan harus diload seluruhnya, MBLEDOS !!
Untuk mengatasi ini, kita perlu menggunakan pagination atau kalau dalam pemrograman android disebut dengan istilah Endless scrolling. intinya, kita akan membatasi jumlah data yang ditampilkan ke user. Misalnya, jumlah yang akan ditampilkan per-20 data kemudian ketika user menscroll aplikasi setelah data ke-20, maka akan muncul progress bar dan data selanjutkan akan ditampilkan sebanyak 20. Kita gunakan recyclerview dan cardview untuk menampung data pada tutorial ini.
Setting gradle
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' implementation 'com.android.support:design:26.1.0' compile 'com.android.support:recyclerview-v7:26.1.0' compile 'com.android.support:cardview-v7:26.1.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' }
Pada tutorial ini kita gunakan 5 layout, mari buat layout untuk activity_main.xml, layout ini berfungsi untuk menampilkan toolbar sekaligus menampung content_main.xml.
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.dekikurnia.scrolling.MainActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_main" /> </android.support.design.widget.CoordinatorLayout>
Selanjutnya buat layout content_main.xml, layout ini berfungsi untuk menampung adapter_contact.xml dalam FrameLayout.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentTop="true" android:layout_alignParentStart="true" android:layout_alignParentLeft="true"> <include layout="@layout/adapter_contact"></include> </FrameLayout> </RelativeLayout>
Setelah membuat layout untuk activity dan content, kita buat layout untuk adapter, buat layout baru dengan nama file adapter_content.xml. Kita gunakan Recyclerview nantinya untuk menampung Cardview.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content" /> </RelativeLayout>
Selanjutnya, kita buat layout untuk menampilkan datanya. Buat layout dengan nama list_contact.xml, layout ini menggunakan Cardview untuk menampung Textview. Dan tidak lupa kita buat layout untuk progressbarnya.
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" card_view:cardUseCompatPadding="true"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="?android:selectableItemBackground" android:padding="10dp"> <TextView android:id="@+id/txt_email" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/black" android:textSize="16sp" android:textStyle="bold" /> <TextView android:id="@+id/txt_phone" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/txt_email" android:textColor="@android:color/black" android:textSize="12sp" /> </RelativeLayout> </android.support.v7.widget.CardView>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <ProgressBar android:id="@+id/progressBar1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" /> </LinearLayout>
Oke, kita telah menyelesaikan untuk tampilannya. Selanjutnya, kita buat untuk bagian koding java-nya. Mari kita buat class POJO. Contoh disini kita akan buat class Contact
dimana dalam class tersebut kita buat 2 field yaitu email dan phone dengan masing-masing menggunakan tipe data String.
package com.dekikurnia.scrolling; /** * Created by dekikurnia on 12/12/2017. */ public class Contact { private String email; private String phone; public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } }
Kemudian, buat interface dengan nama OnLoadMoreListener
yang didalamnya terdapat abstract method onLoadMore()
package com.dekikurnia.scrolling; /** * Created by dekikurnia on 12/12/2017. */ public interface OnLoadMoreListener { void onLoadMore(); }
Konfigurasi RecyclerView adapter
Pada bagian ini kita buat class RecyclerView adapter, kita buat dengan nama ContactAdapter
. pada bagian ini kita buat 2 tempat untuk menampung item yang telah kita buat sebelumnya di layout, yaitu untuk ProgressBar dan untuk TextView. Masing-masing kita buat class dengan nama LoadingViewHolder
dan UserViewHolder.
private class LoadingViewHolder extends RecyclerView.ViewHolder { public ProgressBar progressBar; public LoadingViewHolder(View view) { super(view); progressBar = (ProgressBar) view.findViewById(R.id.progressBar1); } } private class UserViewHolder extends RecyclerView.ViewHolder { public TextView phone; public TextView email; public UserViewHolder(View view) { super(view); phone = (TextView) view.findViewById(R.id.txt_phone); email = (TextView) view.findViewById(R.id.txt_email); } }
Kita definisikan 2 buat konstata yaitu VIEW_TYPE_ITEM
dan VIEW_TYPE_LOADING
. Kemudian instansiasi OnLoadMoreListener
dan kita buat singleton.
private final int VIEW_TYPE_ITEM = 0; private final int VIEW_TYPE_LOADING = 1; private OnLoadMoreListener onLoadMoreListener; public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) { this.onLoadMoreListener = mOnLoadMoreListener; }
Untuk menghandle event scroll pada RecyclerView kita buat didalam konstruktor adapter class. Buat method linearLayoutManager pada RecyclerView. Logikanya, jika scroll sudah mencapai batas data yang ditampilkan maka event progressBar dimunculkan.
private boolean isLoading; private Activity activity; private List<Contact> contacts; private int visibleThreshold = 5; private int lastVisibleItem, totalItemCount; public ContactAdapter(RecyclerView recyclerView, List<Contact> contacts, Activity activity) { this.contacts = contacts; this.activity = activity; final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); totalItemCount = linearLayoutManager.getItemCount(); lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition(); if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) { if (onLoadMoreListener != null) { onLoadMoreListener.onLoadMore(); } isLoading = true; } } }); }
Pada bagian RecyclerView.Holder,
kita buat 2 buah logika jika viewType == VIEW_TYPE_ITEM
maka membubungkan dengan layout list_contact
, sedangkan jika viewType == VIEW_TYPE_LOADING
maka akan membubungkan dengan layout item_loading
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == VIEW_TYPE_ITEM) { View view = LayoutInflater.from(activity).inflate(R.layout.list_contact, parent, false); return new UserViewHolder(view); } else if (viewType == VIEW_TYPE_LOADING) { View view = LayoutInflater.from(activity).inflate(R.layout.item_loading, parent, false); return new LoadingViewHolder(view); } return null; }
Berikut kode lengkap untuk ContactAdapter :
package com.dekikurnia.scrolling; import android.app.Activity; 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.ProgressBar; import android.widget.TextView; import java.util.List; /** * Created by dekikurnia on 12/12/2017. */ public class ContactAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private final int VIEW_TYPE_ITEM = 0; private final int VIEW_TYPE_LOADING = 1; private OnLoadMoreListener onLoadMoreListener; private boolean isLoading; private Activity activity; private List<Contact> contacts; private int visibleThreshold = 5; private int lastVisibleItem, totalItemCount; public ContactAdapter(RecyclerView recyclerView, List<Contact> contacts, Activity activity) { this.contacts = contacts; this.activity = activity; final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); totalItemCount = linearLayoutManager.getItemCount(); lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition(); if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) { if (onLoadMoreListener != null) { onLoadMoreListener.onLoadMore(); } isLoading = true; } } }); } public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) { this.onLoadMoreListener = mOnLoadMoreListener; } @Override public int getItemViewType(int position) { return contacts.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == VIEW_TYPE_ITEM) { View view = LayoutInflater.from(activity).inflate(R.layout.list_contact, parent, false); return new UserViewHolder(view); } else if (viewType == VIEW_TYPE_LOADING) { View view = LayoutInflater.from(activity).inflate(R.layout.item_loading, parent, false); return new LoadingViewHolder(view); } return null; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof UserViewHolder) { Contact contact = contacts.get(position); UserViewHolder userViewHolder = (UserViewHolder) holder; userViewHolder.phone.setText(contact.getEmail()); userViewHolder.email.setText(contact.getPhone()); } else if (holder instanceof LoadingViewHolder) { LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder; loadingViewHolder.progressBar.setIndeterminate(true); } } @Override public int getItemCount() { return contacts == null ? 0 : contacts.size(); } public void setLoaded() { isLoading = false; } private class LoadingViewHolder extends RecyclerView.ViewHolder { public ProgressBar progressBar; public LoadingViewHolder(View view) { super(view); progressBar = (ProgressBar) view.findViewById(R.id.progressBar1); } } private class UserViewHolder extends RecyclerView.ViewHolder { public TextView phone; public TextView email; public UserViewHolder(View view) { super(view); phone = (TextView) view.findViewById(R.id.txt_phone); email = (TextView) view.findViewById(R.id.txt_email); } } }
Terakhir, kita buat koding untuk MainActivity-nya. dalam method onCreate()
panggil setOnLoadMoreListener() dan menampilkan data baru setelah memanggil event progressBar didalam onLoadMore().
pada kode MainActivity, logikanya adalah kita buat dulu dummy data sebanyak 20 kemudian saat user menscroll sampai data ke-20 maka akan muncul progressBar dengan waktu delay 5000ms. Lalu, saat user menscroll sampai data terakhir maka aplikasi akan memberi pesan “Loading data completed”. Selebihnya, silahkan diset/diatur sesuai kebutuhan waktu delay dan data yang akan ditampilkan pertama sebanyak berapa pada user.
private List<Contact> contacts; private ContactAdapter contactAdapter; private Random random; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); contacts = new ArrayList<>(); random = new Random(); for (int i = 0; i < 20; i++) { Contact contact = new Contact(); contact.setPhone("085710830260"); contact.setEmail("dekikurnia" + i + "@gmail.com"); contacts.add(contact); } RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this)); contactAdapter = new ContactAdapter(recyclerView, contacts, this); recyclerView.setAdapter(contactAdapter); contactAdapter.setOnLoadMoreListener(new OnLoadMoreListener() { @Override public void onLoadMore() { if (contacts.size() <= 20) { contacts.add(null); contactAdapter.notifyItemInserted(contacts.size() - 1); new Handler().postDelayed(new Runnable() { @Override public void run() { contacts.remove(contacts.size() - 1); contactAdapter.notifyItemRemoved(contacts.size()); int index = contacts.size(); int end = index + 10; for (int i = index; i < end; i++) { Contact contact = new Contact(); contact.setPhone("085710830260"); contact.setEmail("dekikurnia" + i + "@gmail.com"); contacts.add(contact); } contactAdapter.notifyDataSetChanged(); contactAdapter.setLoaded(); } }, 5000); } else { Toast.makeText(MainActivity.this, "Loading data completed", Toast.LENGTH_SHORT).show(); } } }); }
Untuk source code lengkapnya bisa diakses di https://github.com/dekikurnia/Endless-Scrolling