List是Mobile app的一個基本元素,幾乎應用在每一個app裡面。這篇教學會講解一下如何用RecyclerView建立一個簡單的列表。
前言:ListView vs RecycleView
ListView是一個歷史較悠久的component,簡單直接顯示所有data。而RecyclerView是它的改良版,主要提升了效能和提供更大Layout彈性。
相關閱讀:RecyclerView vs. ListView
BaseAdapter vs RecyclerView.Adpater
RecyclerView需要使用比較特別的Adapter,以配合它reuse ViewHolder的需要。
它們的concrete class分別需要override以下methods:
BaseAdapter:
- int getCount()
- Object getItem(int position)
- long getItemId(int position)
- View getView(int position, View convertView, ViewGroup parent)
RecyclerView.Adpater:
- ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
- void onBindViewHolder(ViewHolder holder, int position)
- int getItemCount()
在這篇我們會使用RecyclerView和它的Adapter。
建立一個基本的RecyclerView
Android Developer Guide - Create a List with RecyclerView
結構
- Fragment
- layout xml
- RecyclerView
- Adapter
- ViewHolder
- layout xml
- TextView, etc
- ViewHolder
- LayoutManager
- Adapter
Layout
總共需要在res/
裡建立兩個layout檔:
- person_fragment.xml:Fragment的layout,裡面放一個RecyclerView
- person_list_item.xml:ViewHolder的layout,也等於每個List item的layout
建立完成後應該有齊以下材料:
R.layout.person_fragment
R.id.person_list_view
R.layout.person_list_item
R.id.person_name_text_view
都是之後會用到的。
建立一個Adapter
每個RecyclerView都會有一個Adapter,Adapter是處理data,製作list item的地方。與一般ListView的adapter不同,RecyclerView的Adapter必須跟從ViewHolder pattern。
ViewHolder pattern流程簡介
- Adapter製作少量所需的ViewHolder,它們只有相關的View,裡面沒有任何data
- Adapter根據需要(如頁面滾動)自動bind和unbind這些ViewHolder
- bind的時候一般以
position
來找data,然後用來設置ViewHolder裡的各個View應該顯示甚麼
- bind的時候一般以
透過拆開「建立View」和「設置View」的logic,Adapter可以重用ViewHolder,減少View的數量,在顯示長列表時效能會顯著提升。
相關閱讀:StackOverflow - onCreateViewHolder of RecyclerView.Adapter is called twice or more, multiple times
建立初始Adapter
Adapter的constructor裡應接收list data並存起備用。
public class PersonListAdapter {
private List<Person> personList;
public RouteListAdapter(List<Person> personList) {
this.personList = personList;
}
}
繼承RecyclerView.Adapter
Adapter需要繼承RecyclerView.Adapter
:
public class PersonListAdapter extends RecyclerView.Adapter<PersonListAdapter.ViewHolder> {
// ...
}
留意
然後override三個相關的methods:
- onCreateViewHolder:調用R.layout建立View,以此建立一個新的ViewHolder
- onBindViewHolder:透過position找到data,以此設置ViewHolder裡的View
- getItemCount
// ...
@NonNull
@Override
public PersonListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.person_list_item, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Route route = mRoutes.get(position);
holder.routeName.setText(route.getName());
}
@Override
public int getItemCount() {
return mRoutes.size();
}
// ...
建立ViewHolder
在一般convention下,我們會在Adapter裡建立一個ViewHolder
class,它的功能十分簡單,只需要在constructor裡存儲起自己用到的View即可。
public class PersonListAdapter extends RecyclerView.Adapter<PersonListAdapter.ViewHolder> {
// ...
public class ViewHolder extends RecyclerView.ViewHolder {
TextView personNameTextView;
public ViewHolder(View v) {
super(v);
personTextView = v.findViewById(R.id.person_name);
}
}
}
到此我們已經完成了Adapter,接下來可以在Activity/Fragment裡使用了。
在Fragment裡使用
流程非常簡單:
- 找出RecyclerView
- 建立LayoutManager
- 設定到RecyclerView
- 獲取data,建立Adapter
- 設定到RecyclerView
public class RouteListFragment extends Fragment {
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.my_fragment, container, false);
initRecyclerView(view);
return view;
}
private void initRecyclerView(View view) {
mRecyclerView = (RecyclerView) view.findViewById(R.id.my_recycler_view);
// Layout manager
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
// Retrieve data: e.g. Query data from SQLite
List<Route> routes = SQLite.select().from(Route.class).queryList();
// Set an Adapter
mAdapter = new RouteListAdapter(routes);
mRecyclerView.setAdapter(mAdapter);
}
}
注意:與Activity不同,Fragment無法直接使用findViewById
。我們仍可透過inflate出來的View去找到我們的RecyclerView