Android App開發(fā)中使用RecyclerView替代ListView的實(shí)踐
RecyclerView是Android 5.0的新特性,可以直接代替ListView與GridView,并且能夠?qū)崿F(xiàn)瀑布流的布局,感覺RecyclerView使用的好處就是它不關(guān)心布局,只關(guān)心資源的回收與復(fù)用,正因?yàn)槿绱?,RecyclerView中將ViewHolder進(jìn)行了單獨(dú)的編寫,這也正是google所不斷提倡的,另外,RecyclerView能夠更簡(jiǎn)單的實(shí)現(xiàn)布局的轉(zhuǎn)換。
新的視圖類RecyclerView,它被用來代替ListView以及GridView,提供更為高效的回收復(fù)用機(jī)制,同時(shí)實(shí)現(xiàn)管理與視圖的解耦合。RecyclerView的特點(diǎn):
1、RecyclerView不關(guān)心布局,需要使用setLayoutManager進(jìn)行設(shè)置布局。
2、RecyclerView不關(guān)心分割線,因此分割線需要我們自己想辦法設(shè)置。
3、RecyclerView不關(guān)心Item的點(diǎn)擊事件與動(dòng)畫效果,需要自己編寫接口進(jìn)行監(jiān)聽。
4、RecyclerView僅關(guān)注View的回收與復(fù)用。
相關(guān)的類:
1、RecyclerView.Adapter:托管數(shù)據(jù)集合,為每個(gè)Item創(chuàng)建視圖;
2、RecyclerView.ViewHolder:承載Item視圖的子視圖;
3、RecyclerView.LayoutManager:負(fù)責(zé)Item視圖的布局;
4、RecyclerView.ItemDecoration:為每個(gè)Item視圖添加分割線;
5、RecyclerView.ItemAnimator:負(fù)責(zé)添加、刪除數(shù)據(jù)時(shí)的動(dòng)畫效果;
RecyclerView代替ListView的使用示例
RecyclerView是一個(gè)比ListView更靈活的一個(gè)控件,以后可以直接拋棄ListView了。具體好在哪些地方,往下看就知道了。
首先我們來使用RecyclerView來實(shí)現(xiàn)ListView的效果,一個(gè)滾動(dòng)列表,先看下效果圖(除了有動(dòng)畫之外,沒什么特別--):
每個(gè)item的布局如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/recycler_view_test_item_person_view" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="15dp" android:background="#aabbcc" > <TextView android:id="@+id/recycler_view_test_item_person_name_tv" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18sp" android:background="#ccbbaa" /> <TextView android:id="@+id/recycler_view_test_item_person_age_tv" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="5dp" android:background="#aaccbb" android:textSize="15sp" /> </LinearLayout>
item的布局很簡(jiǎn)單,只有兩個(gè)TextView,一個(gè)用來顯示名字,一個(gè)用來顯示年齡。
Person的實(shí)體類就不貼代碼了,兩個(gè)屬性:名字和年齡。
然后需要使用到RecyclerView,所以需要把support v7添加到class path,并在布局中添加該控件:
<android.support.v7.widget.RecyclerView android:id="@+id/recycler_view_test_rv" android:scrollbars="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#bbccaa" />
然后在onCreate中:
recyclerView.setHasFixedSize(true); RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(context); recyclerView.setLayoutManager(layoutManager); initData(); adapter = new PersonAdapter(personList); adapter.setOnRecyclerViewListener(this); recyclerView.setAdapter(adapter);
如上述代碼:
Line1: 使RecyclerView保持固定的大小,這樣會(huì)提高RecyclerView的性能。
Line3: LinearLayoutManager,如果你需要顯示的是橫向滾動(dòng)的列表或者豎直滾動(dòng)的列表,則使用這個(gè)LayoutManager。顯然,我們要實(shí)現(xiàn)的是ListView的效果,所以需要使用它。生成這個(gè)LinearLayoutManager之后可以設(shè)置他滾動(dòng)的方向,默認(rèn)豎直滾動(dòng),所以這里沒有顯式地設(shè)置。
Line6: 初始化數(shù)據(jù)源。
Line7~9: 跟ListView一樣,需要設(shè)置RecyclerView的Adapter,但是這里的Adapter跟ListView使用的Adapter不一樣,這里的Adapter需要繼承RecyclerView.Adapter,需要實(shí)現(xiàn)3個(gè)方法:
1、onCreateViewHolder()
2、onBindViewHolder()
3、getItemCount()
直接看代碼:
package com.wangjie.helloandroid.sample.recycler.person; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import com.wangjie.androidbucket.log.Logger; import com.wangjie.helloandroid.R; import java.util.List; public class PersonAdapter extends RecyclerView.Adapter { public static interface OnRecyclerViewListener { void onItemClick(int position); boolean onItemLongClick(int position); } private OnRecyclerViewListener onRecyclerViewListener; public void setOnRecyclerViewListener(OnRecyclerViewListener onRecyclerViewListener) { this.onRecyclerViewListener = onRecyclerViewListener; } private static final String TAG = PersonAdapter.class.getSimpleName(); private List<Person> list; public PersonAdapter(List<Person> list) { this.list = list; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { Logger.d(TAG, "onCreateViewHolder, i: " + i); View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_test_item_person, null); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); view.setLayoutParams(lp); return new PersonViewHolder(view); } @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { Logger.d(TAG, "onBindViewHolder, i: " + i + ", viewHolder: " + viewHolder); PersonViewHolder holder = (PersonViewHolder) viewHolder; holder.position = i; Person person = list.get(i); holder.nameTv.setText(person.getName()); holder.ageTv.setText(person.getAge() + "歲"); } @Override public int getItemCount() { return list.size(); } class PersonViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { public View rootView; public TextView nameTv; public TextView ageTv; public int position; public PersonViewHolder(View itemView) { super(itemView); nameTv = (TextView) itemView.findViewById(R.id.recycler_view_test_item_person_name_tv); ageTv = (TextView) itemView.findViewById(R.id.recycler_view_test_item_person_age_tv); rootView = itemView.findViewById(R.id.recycler_view_test_item_person_view); rootView.setOnClickListener(this); rootView.setOnLongClickListener(this); } @Override public void onClick(View v) { if (null != onRecyclerViewListener) { onRecyclerViewListener.onItemClick(position); } } @Override public boolean onLongClick(View v) { if(null != onRecyclerViewListener){ return onRecyclerViewListener.onItemLongClick(position); } return false; } } }
如上代碼所示:
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i)
這個(gè)方法主要生成為每個(gè)Item inflater出一個(gè)View,但是該方法返回的是一個(gè)ViewHolder。方法是把View直接封裝在ViewHolder中,然后我們面向的是ViewHolder這個(gè)實(shí)例,當(dāng)然這個(gè)ViewHolder需要我們自己去編寫。直接省去了當(dāng)初的convertView.setTag(holder)和convertView.getTag()這些繁瑣的步驟。
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i)
這個(gè)方法主要用于適配渲染數(shù)據(jù)到View中。方法提供給你了一個(gè)viewHolder,而不是原來的convertView。
對(duì)比下以前的寫法就一目了然了:
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if(null == convertView){ holder = new ViewHolder(); LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = mInflater.inflate(R.layout.item, null); holder.btn = (Button) convertView.findViewById(R.id.btn); holder.tv = (TextView) convertView.findViewById(R.id.tv); holder.iv = (TextView) convertView.findViewById(R.id.iv); convertView.setTag(holder); }else{ holder = (ViewHolder) convertView.getTag(); } final HashMap<String, Object> map = list.get(position); holder.iv.setImageResource(Integer.valueOf(map.get("iv").toString())); holder.tv.setText(map.get("tv").toString()); holder.btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(context, map.get("btn").toString(), Toast.LENGTH_SHORT).show(); } }); return convertView; } class ViewHolder{ Button btn; ImageView iv; TextView tv; }
對(duì)比后可以發(fā)現(xiàn):
1、舊的寫法中Line5~Line12+Line28部分的代碼其實(shí)起到的作用相當(dāng)于新的寫法的onCreateViewHolder();
2、舊的寫法中Line14~Line26部分的代碼其實(shí)起到的作用相當(dāng)于新的寫法的onBindViewHolder();
既然是這樣,那我們就把原來相應(yīng)的代碼搬到對(duì)應(yīng)的onCreateViewHolder()和onBindViewHolder()這兩個(gè)方法中就可以了。
因?yàn)镽ecyclerView幫我們封裝了Holder,所以我們自己寫的ViewHolder就需要繼承RecyclerView.ViewHolder,只有這樣,RecyclerView才能幫你去管理這個(gè)ViewHolder類。
既然getView方法的渲染數(shù)據(jù)部分的代碼相當(dāng)于onBindViewHolder(),所以如果調(diào)用adapter.notifyDataSetChanged()方法,應(yīng)該也會(huì)重新調(diào)用onBindViewHolder()方法才對(duì)吧?實(shí)驗(yàn)后,果然如此!
除了adapter.notifyDataSetChanged()這個(gè)方法之外,新的Adapter還提供了其他的方法,如下:
public final void notifyDataSetChanged() public final void notifyItemChanged(int position) public final void notifyItemRangeChanged(int positionStart, int itemCount) public final void notifyItemInserted(int position) public final void notifyItemMoved(int fromPosition, int toPosition) public final void notifyItemRangeInserted(int positionStart, int itemCount) public final void notifyItemRemoved(int position) public final void notifyItemRangeRemoved(int positionStart, int itemCount)
基本上看到方法的名字就知道這個(gè)方法是干嘛的了,
第一個(gè)方法沒什么好講的,跟以前一樣。
- notifyItemChanged(int position),position數(shù)據(jù)發(fā)生了改變,那調(diào)用這個(gè)方法,就會(huì)回調(diào)對(duì)應(yīng)position的onBindViewHolder()方法了,當(dāng)然,因?yàn)閂iewHolder是復(fù)用的,所以如果position在當(dāng)前屏幕以外,也就不會(huì)回調(diào)了,因?yàn)闆]有意義,下次position滾動(dòng)會(huì)當(dāng)前屏幕以內(nèi)的時(shí)候同樣會(huì)調(diào)用onBindViewHolder()方法刷新數(shù)據(jù)了。其他的方法也是同樣的道理。
- public final void notifyItemRangeChanged(int positionStart, int itemCount),顧名思義,可以刷新從positionStart開始itemCount數(shù)量的item了(這里的刷新指回調(diào)onBindViewHolder()方法)。
- public final void notifyItemInserted(int position),這個(gè)方法是在第position位置被插入了一條數(shù)據(jù)的時(shí)候可以使用這個(gè)方法刷新,注意這個(gè)方法調(diào)用后會(huì)有插入的動(dòng)畫,這個(gè)動(dòng)畫可以使用默認(rèn)的,也可以自己定義。
- public final void notifyItemMoved(int fromPosition, int toPosition),這個(gè)方法是從fromPosition移動(dòng)到toPosition為止的時(shí)候可以使用這個(gè)方法刷新
- public final void notifyItemRangeInserted(int positionStart, int itemCount),顯然是批量添加。
- public final void notifyItemRemoved(int position),第position個(gè)被刪除的時(shí)候刷新,同樣會(huì)有動(dòng)畫。
- public final void notifyItemRangeRemoved(int positionStart, int itemCount),批量刪除。
這些方法分析完之后,我們來實(shí)現(xiàn)一個(gè)點(diǎn)擊一個(gè)按鈕,新增一條數(shù)據(jù),長(zhǎng)按一個(gè)item,刪除一條數(shù)據(jù)的場(chǎng)景。
以下是新增一條數(shù)據(jù)的代碼:
Person person = new Person(i, "WangJie_" + i, 10 + i); adapter.notifyItemInserted(2); personList.add(2, person); adapter.notifyItemRangeChanged(2, adapter.getItemCount());
如上代碼:
Line2:表示在position為2的位置,插入一條數(shù)據(jù),這個(gè)時(shí)候動(dòng)畫開始執(zhí)行。
Line3: 表示在數(shù)據(jù)源中position為2的位置新增一條數(shù)據(jù)(其實(shí)這個(gè)才是真正的新增數(shù)據(jù)啦)。
Line4: 為什么要刷新position為2以后的數(shù)據(jù)呢?因?yàn)?,在position為2的位置插入了一條數(shù)據(jù)后,新數(shù)據(jù)的position變成了2,那原來的position為2的應(yīng)該變成了3,3的應(yīng)該變成了4,所以2以后的所有數(shù)據(jù)的position都發(fā)生了改變,所以需要把position2以后的數(shù)據(jù)都要刷新。理論上是這樣,但是實(shí)際上刷新的數(shù)量只有在屏幕上顯示的position為2以后的數(shù)據(jù)而已。如果這里使用notifyDataSetChanged()來刷新屏幕上顯示的所有item可以嗎?結(jié)果不會(huì)出錯(cuò),但是會(huì)有一個(gè)問題,前面調(diào)用了notifyItemInserted()方法后會(huì)在執(zhí)行動(dòng)畫,如果你調(diào)用notifyDataSetChanged()刷新屏幕上顯示的所有item的話,必然也會(huì)刷新當(dāng)前正在執(zhí)行動(dòng)畫的那個(gè)item,這樣導(dǎo)致的結(jié)果是,前面的動(dòng)畫還沒執(zhí)行完,它馬上又被刷新了,動(dòng)畫就看不見了。所以只要刷新2以后的item就可以了。
看了RecyclerView的api,發(fā)現(xiàn)沒有setOnItemClickListener--,所以還是自己把onItemClick從Adapter中回調(diào)出來吧。這個(gè)很簡(jiǎn)單,就像上面PersonAdaper中寫的OnRecyclerViewListener那樣。
長(zhǎng)按刪除的代碼如下:
adapter.notifyItemRemoved(position); personList.remove(position); adapter.notifyItemRangeChanged(position, adapter.getItemCount());
代碼跟之前插入的代碼基本一致。先通知執(zhí)行動(dòng)畫,然后刪除數(shù)據(jù)源中的數(shù)據(jù),然后通知position之后的數(shù)據(jù)刷新就可以了。
這樣ListView的效果就實(shí)現(xiàn)了。
- android使用ViewPager組件實(shí)現(xiàn)app引導(dǎo)查看頁面
- Android用webView包裝WebAPP方法
- Android APP之WebView校驗(yàn)SSL證書的方法
- Android中TabLayout+ViewPager 簡(jiǎn)單實(shí)現(xiàn)app底部Tab導(dǎo)航欄
- 詳解Android中ListView實(shí)現(xiàn)圖文并列并且自定義分割線(完善仿微信APP)
- Android App使用RecyclerView實(shí)現(xiàn)上拉和下拉刷新的方法
- Android App中使用ViewPager實(shí)現(xiàn)滑動(dòng)分頁的要點(diǎn)解析
- Android App中ViewPager所帶來的滑動(dòng)沖突問題解決方法
- Android自定義View app更新動(dòng)畫詳解
相關(guān)文章
Kotlin使用TransitionDrawable實(shí)現(xiàn)顏色漸變效果流程講解
這篇文章主要介紹了Kotlin使用TransitionDrawable實(shí)現(xiàn)顏色漸變效果,這里,我們通過TransitionDrawable顯示顏色漸變效果,包括背景顏色的變化,以及圖片與圖片的漸變效果2023-02-02淺析Android 手機(jī)衛(wèi)士設(shè)備管理權(quán)限鎖屏
這篇文章主要介紹了淺析Android 手機(jī)衛(wèi)士設(shè)備管理權(quán)限鎖屏的相關(guān)資料,需要的朋友可以參考下2016-04-04RecyclerView實(shí)現(xiàn)插入和刪除
這篇文章主要為大家詳細(xì)介紹了RecyclerView實(shí)現(xiàn)插入和刪除,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08android 6.0 權(quán)限授權(quán)方法
今天小編就為大家分享一篇android 6.0 權(quán)限授權(quán)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-07-07Android應(yīng)用創(chuàng)建桌面快捷方式代碼
這篇文章主要為大家詳細(xì)介紹了Android應(yīng)用創(chuàng)建桌面快捷方式代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09Android簡(jiǎn)單實(shí)現(xiàn)圓盤抽獎(jiǎng)界面
這篇文章主要介紹了Android簡(jiǎn)單實(shí)現(xiàn)圓盤抽獎(jiǎng)界面的相關(guān)資料,需要的朋友可以參考下2016-01-01Android實(shí)現(xiàn)讀取掃碼槍內(nèi)容(條形碼)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)讀取掃碼槍內(nèi)容、條形碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09Android 清除SharedPreferences 產(chǎn)生的數(shù)據(jù)(實(shí)例代碼)
項(xiàng)目是要保存上次文件播放的位置,我使用SharedPreferences來保存,鍵值對(duì)分別是文件路徑和當(dāng)時(shí)播放的位置2013-11-11Android 實(shí)現(xiàn)通知消息水平播放、無限循環(huán)效果
Android 實(shí)現(xiàn)通知消息水平播放、無限循環(huán)效果非常常見,今天小編給大家分享實(shí)例代碼感興趣的朋友參考下吧2017-08-08