使用RecyclerView創(chuàng)建性能更好的列表界面

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ù)即可:

  1. onCreateViewHolder(ViewGroup parent, int viewType)
  2. onBindViewHolder(VH holder, int position)
  3. getItemCount()

其中onCreateViewHolder()用于創(chuàng)建ViewHolder對象画畅,onBindViewHolder()用于將數(shù)據(jù)模型綁定到ViewHolder中的視圖組件上轴踱,getItemCount()返回數(shù)據(jù)模型的數(shù)量淫僻。當RecyclerView需要顯示視圖時雳灵,就會通過它的Adapter來實現(xiàn),大致流程如下:

  1. RecyclerView調用Adapter的getItemCount()方法詢問數(shù)組列表中有多少數(shù)據(jù)闸盔;
  2. RecyclerView調用Adapter的createViewHolder()創(chuàng)建ViewHolder及其要顯示的視圖悯辙;
  3. 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ā)中強烈建議使用這個新的組件。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子儿子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機附井,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門沼死,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人秀姐,你說我怎么就攤上這事。” “怎么了舷蟀?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵速缨,是天一觀的道長。 經(jīng)常有香客問我,道長擂橘,這世上最難降的妖魔是什么恼五? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任轨功,我火速辦了婚禮琉预,結果婚禮上,老公的妹妹穿的比我還像新娘昙楚。我一直安慰自己奖亚,他們只是感情好,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布夹攒。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪斥季。 梳的紋絲不亂的頭發(fā)上躁锡,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天赎败,我揣著相機與錄音搞糕,去河邊找鬼辈赋。 笑死,一個胖子當著我的面吹牛近忙,可吹牛的內容都是我干的咐柜。 我是一名探鬼主播遗契,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼贡茅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蹈胡?” 一聲冷哼從身側響起却汉,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了姥卢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓶堕。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖竞思,靈堂內的尸體忽然破棺而出跨算,到底是詐尸還是另有隱情,我是刑警寧澤氧猬,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布背犯,位于F島的核電站,受9級特大地震影響盅抚,放射性物質發(fā)生泄漏漠魏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一妄均、第九天 我趴在偏房一處隱蔽的房頂上張望柱锹。 院中可真熱鬧,春花似錦丛晦、人聲如沸奕纫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至隙笆,卻和暖如春锌蓄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撑柔。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工瘸爽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铅忿。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓剪决,卻偏偏與公主長得像,于是被迫代替她去往敵國和親檀训。 傳聞我的和親對象是個殘疾皇子柑潦,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內容