如何使用Android RecyclerView建立一個List列表

2018/08/08 posted in  Android comments

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
      • LayoutManager

Layout

總共需要在res/裡建立兩個layout檔:

  1. person_fragment.xml:Fragment的layout,裡面放一個RecyclerView
  2. 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流程簡介

  1. Adapter製作少量所需的ViewHolder,它們只有相關的View,裡面沒有任何data
  2. Adapter根據需要(如頁面滾動)自動bind和unbind這些ViewHolder
    • bind的時候一般以position來找data,然後用來設置ViewHolder裡的各個View應該顯示甚麼

透過拆開「建立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> {
    // ...
}

留意定義了這個Adapter所使用的ViewHolder類型,之後會在下面建立這個class。

然後override三個相關的methods:

  1. onCreateViewHolder:調用R.layout建立View,以此建立一個新的ViewHolder
  2. onBindViewHolder:透過position找到data,以此設置ViewHolder裡的View
  3. 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裡使用

流程非常簡單:

  1. 找出RecyclerView
  2. 建立LayoutManager
  3. 設定到RecyclerView
  4. 獲取data,建立Adapter
  5. 設定到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