Android5.0發(fā)布后為我們帶來了新的控件RecyclerView蛹锰。RecyclerView被稱為ListView和GridView的繼任者铜犬,它使用一種統(tǒng)一的方式整合了這兩種視圖的使用并提供更多選擇轻庆。通過使用RecyclerView結合不同的LayoutManager我們可以一次性實現(xiàn)線性余爆,網(wǎng)格和瀑布流三種布局,如果需要蛾方,線性列表不再一定是垂直的,還可以橫向滾動桩砰。此外亚隅,RecyclerView還封裝了更好的ViewHolder和Adapter類,將原來使用ListView時必須由開發(fā)者來實現(xiàn)的復用優(yōu)化工作封裝好懂鸵。開發(fā)者寫更少的代碼就能避免性能不佳的問題矾瑰。這篇短文通過對比ListView和RecyclerView的不同使用方式隘擎,來說明為什么我們應該優(yōu)先使用RecyclerView。
Android App中開發(fā)列表界面可以做的很簡單劲够,每個列表項僅顯示一行文字休傍;也可以做的很復雜,每個列表項包含標題人柿,副標題凫岖,按鈕,復選框哥放,還能響應點擊事件等等甥雕。這些視圖組件在加載時需要父視圖通過findViewById()一個一個創(chuàng)建社露。這個函數(shù)通過遍歷整個布局資源來查找目標視圖組件宰掉,如果用戶每滑動一次列表就遍歷一遍甚至多遍赁濒,就會影響App的性能拒炎,因此兩種列表布局都使用ViewHolder來解決這個問題。此外玉组,因為可以針對不同的數(shù)據(jù)模型定制列表項丁侄,所以ListView或者RecyclerView需要有輔助類來負責將數(shù)據(jù)模型適配到視圖組件中鸿摇,這就需要借助“適配器模式”來實現(xiàn)。ListView使用的是ArrayAdapter揪荣,而RecyclerView使用的則是Adapter往史。下面讓我們通過對比,來看看RecyclerView的實現(xiàn)方式為什么比ListView的實現(xiàn)方式要好挨决。
一. 使用ListView創(chuàng)建列表界面
1. ListView凰棉,ArrayAdapter和ViewHolder
使用ListView來創(chuàng)建列表項主要依賴ArrayAdapter和ViewHolder這兩個輔助類陌粹。我們創(chuàng)建一個ArrayAdapter的子類掏秩,然后在子類中定義一個ViewHolder的輔助類蒙幻,這個輔助類負責托管視圖組件。然后最重要的邏輯通過重載ArrayAdapter的getView()方法來實現(xiàn)诈豌。下面通過一個具體的代碼示例來看看抒和。
2. 代碼示例
假設我們要實現(xiàn)一個功能:通過網(wǎng)絡請求獲取數(shù)據(jù)并更新列表摧莽。那么我們首先需要在Activity或者Fragment中創(chuàng)建好ArrayAdapter子類,傳入列表布局資源和數(shù)據(jù)模型油够;初始化ListView石咬,并在ListView上調用setAdapter()設置適配器卖哎,別忘了虏束,一定要記得在數(shù)據(jù)模型發(fā)生更新時調用ArrayAdapter的notifyDataSetChanged()函數(shù)通知列表更新镇匀。
public class NewTaskFragment extends Fragment {
private static String TAG = NewTaskFragment.class.getSimpleName();
private List<CheckItem> mNewTaskList = new ArrayList<>();
private TaskFragmentAdapter mAdapter;
...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Logger.t(TAG).d(TAG, "onCreateView");
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_new_task, container, false);
...
mAdapter = new TaskFragmentAdapter(getContext(), R.layout.task_item, mNewTaskList, true);
ListView newTaskView = (ListView) view.findViewById(R.id.new_task_list);
newTaskView.setAdapter(mAdapter);
return view;
}
private void refreshNewTask() {
...
mNewTaskList.add(taskItem);
mAdapter.notifyDataSetChanged();
}
}
最重要的工作都放到ArrayAdapter的子類中汗侵,其中要特別注意的就是判斷視圖是不是第一次創(chuàng)建晰韵。如果是第一次創(chuàng)建雪猪,那么就需要通過ViewHolder初始化視圖組件起愈,并setTag()緩存到View中去抬虽。如果第一次(比如用戶滾動列表刷新界面)阐污,就通過getTag()獲取已經(jīng)創(chuàng)建好的視圖組件,然后用對應數(shù)據(jù)模型進行更新功氨,這幾乎就是重載ArrayAdapter的getView()方法的標準套路捷凄。
public class TaskFragmentAdapter extends ArrayAdapter<CheckItem> {
private int mResID;
...
public TaskFragmentAdapter(Context context, int resource, List<CheckItem> objects, boolean newTask) {
super(context, resource, objects);
mResID = resource;
...
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final CheckItem item = getItem(position);
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(mResID, null);
viewHolder = new ViewHolder();
viewHolder.image = (ImageView) view.findViewById(R.id.type_image);
viewHolder.addrView = (TextView) view.findViewById(R.id.task_name);
viewHolder.macView = (TextView) view.findViewById(R.id.mac);
viewHolder.checkButton = (Button) view.findViewById(R.id.check);
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.image.setImageResource(item.getTypeIndicatorID());
viewHolder.addrView.setText(item.getAddress());
viewHolder.macView.setText(item.getMAC());
viewHolder.checkButton.setOnClickListener(new OnClickListener() {
...
});
return view;
}
class ViewHolder {
ImageView image;
TextView addrView;
TextView macView;
Button checkButton;
}
}
二. 使用RecyclerView創(chuàng)建列表界面
在使用了RecyclerView之后這些工作都不用開發(fā)者自己寫了纵势,因為RecyclerView都會給你做好。使用RecyclerView最好的一點是才漆,它減少了開發(fā)者不必要的工作量醇滥,使得代碼看上去更規(guī)范,工整。它將這些優(yōu)化工作封裝到了庫中厦幅,因此開發(fā)者只需要在Adapter中重載幾個函數(shù),就能做到高性能實現(xiàn)。這也從更大程度上防止初學者寫出不太好的代碼。
1. RecyclerView,Adapter和ViewHolder
RecyclerView結合Adapter和ViewHolder瘪板,圍繞“適配器模式”提供了一套滿足“單一職責原則”的解決方案侮攀。
“一個類應該只有一個發(fā)生變化的原因”
其中RecyclerView作為ViewGroup的子類厢拭,負責展示列表項蚪腐,每個列表項都是View的子對象。RecyclerView并不是有多少項就創(chuàng)建多少項家制,這樣很容易搞垮應用颤殴。當用戶滑動屏幕時涵但,滑出視圖的列表項會被回收用于顯示新的列表項帖蔓,這就是“RecyclerView”這個名字的由來塑娇。ViewHolder不變埋酬,仍然負責托管和容納視圖組件烧栋,這個沒什么好說的审姓。而Adapter的使用就規(guī)范多了邑跪,只需要重載三個函數(shù)即可:
- onCreateViewHolder(ViewGroup parent, int viewType)
- onBindViewHolder(VH holder, int position)
- getItemCount()
其中onCreateViewHolder()用于創(chuàng)建ViewHolder對象画畅,onBindViewHolder()用于將數(shù)據(jù)模型綁定到ViewHolder中的視圖組件上轴踱,getItemCount()返回數(shù)據(jù)模型的數(shù)量淫僻。當RecyclerView需要顯示視圖時雳灵,就會通過它的Adapter來實現(xiàn),大致流程如下:
- RecyclerView調用Adapter的getItemCount()方法詢問數(shù)組列表中有多少數(shù)據(jù)闸盔;
- RecyclerView調用Adapter的createViewHolder()創(chuàng)建ViewHolder及其要顯示的視圖悯辙;
- RecyclerView調用Adapter的onBindViewHolder()方法,傳入ViewHolder對象及位置迎吵,Adapter找到目標位置對應的數(shù)據(jù)躲撰,用它綁定到ViewHolder對象容納的視圖上。
其中createViewHolder()調用并不頻繁击费。一旦創(chuàng)建了足夠的ViewHolder拢蛋,RecyclerView就不會再調用createViewHolder(),而是回收利用舊的ViewHolder來節(jié)約內存開銷蔫巩。
2. 代碼示例
比如我們要實現(xiàn)如下所示的列表界面:
只需要在Fragment或Activity中編寫初始化代碼谆棱,并定義好ViewHolder和Adapter即可,注意RecyclerView多出來一步設置LayoutManager圆仔。
public class CrimeListFragment extends Fragment {
RecyclerView mCrimeRecyclerView;
CrimeAdapter mCrimeAdapter;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_crime_list, container, false);
mCrimeRecyclerView = (RecyclerView) view.findViewById(R.id.crime_recycler_view);
mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
updateUI();
return view;
}
private class CrimeHolder extends RecyclerView.ViewHolder ... {
private TextView mTitleTextView;
private TextView mDateTextView;
private CheckBox mSolvedCheckBox;
public CrimeHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
mTitleTextView = (TextView) itemView.findViewById(R.id.list_item_crime_title_text_view);
mDateTextView = (TextView) itemView.findViewById(R.id.list_item_crime_date_text_view);
mSolvedCheckBox = (CheckBox) itemView.findViewById(R.id.list_item_crime_solved_check_box);
}
public void bindCrime(Crime crime) {
mTitleTextView.setText(crime.getTitle());
mDateTextView.setText(crime.getDate().toString());
mSolvedCheckBox.setChecked(crime.isSolved());
}
...
}
private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> {
private List<Crime> mCrimes;
public CrimeAdapter(List<Crime> crimes) {
mCrimes = crimes;
}
@Override
public CrimeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(getActivity());
View view = inflater.inflate(R.layout.list_item_crime, parent, false);
return new CrimeHolder(view);
}
@Override
public void onBindViewHolder(CrimeHolder holder, int position) {
Crime crime = mCrimes.get(position);
holder.bindCrime(crime);
}
@Override
public int getItemCount() {
return mCrimes.size();
}
}
private void updateUI() {
mCrimeAdapter = new CrimeAdapter(CrimeLab.get(getActivity()).getCrimes());
mCrimeRecyclerView.setAdapter(mCrimeAdapter);
}
}
使用RecyclerView寫出來的代碼要比ListView更容易理解,所以在項目開發(fā)中強烈建議使用這個新的組件。