標籤:android recycle view
我的小魚你醒了,
還認識早晨嗎?
昨夜你曾經說,
願夜幕永不開啟
…
上述小詩先逗比一下,接下來切入正題;
自從RecyclerView的誕生起,人們就為她貼上了高貴的標籤;她靈活華麗高度可定製,而另一邊ListView確已是明日黃花;人們趨之若鶩的奔向了RecyclerView讚美她吹捧她似乎一切的一切都盡在他們的掌握中;各大技術論壇開始講解RecyclerView是多麼的好用;於是乎我也加入了使用RecyclerView的行列中,哦不!是混入了;
初識RecyclerView
說實在的我不願意從頭開始講解她是如何工作,如何建立Adapter;因為早就有人寫了相關用法的文章;再此我也不會贅述,我就當你會基本的用法了;如果還不知道她的基本用法可移至Android RecyclerView 使用完全解析 體驗藝術般的控制項,正所謂前人種樹後人乘涼相信我們也從這些前輩的部落格中學到了不少;
使用流程大概總結如下:
RecyclerView.setLayoutManager(layout);
LinearLayoutManager:線性布局通常就是替換ListView布局
GridLayoutManager:表格版面配置通常就是替換GridView布局
StaggeredGridLayoutManager:這個布局比較靈活可以用來替換ListView,GridView還可以做出瀑布流的效果;
RecyclerView.setAdapter(adapter)
RecyclerView.setItemAnimator(new DefaultItemAnimator());
RecyclerView.addItemDecoration(new DividerItemDecoration(
getActivity(), DividerItemDecoration.HORIZONTAL_LIST));
(ps:分割線我覺得可以設定視圖的margin來實現,或者自己添加邊框;用這種方式做分割線反而是個累贅,當然還是根據實際情況來;)
RecyclerView你真的會用了嗎?
看過不少RecyclerView的例子,大多是一些靜態測試環境;這樣一看我們會認為RecyclerView果真是個好東西;於是乎我也心動了,我在上一篇文章中Android學習之最佳化美女圖片瀏覽器中使用了GridView決定要替換為 RecyclerView並實現瀑布流的效果;想想還是有些小激動的,替換實現之分分鐘搞定,下面是測試;
ScreenShot
美麗的背後總是隱藏著不為人知的故事
看了RecyclerView的使用後,我分分鐘加入了上次寫的項目中,本以為可以萬事大吉;但是結果總是不會差強人意;當然我也懷疑過也會質疑應該沒那麼簡單,果真遵循了墨菲定律;索性程式還是跑起來了,至少沒有崩潰(ps:其實崩潰才是最好解決,哈哈:)
下面進入看圖說話
上面兩幅圖中分別出現了兩個bug
通過notifyDataSetChanged()方法後滑動到頂部圖片會出現移位的效果
下拉到底部自動載入的時候全屏圖片閃動(ps:相信看了我上一篇文章的同學已經知道如何避免下拉載入時整個頁面閃動的問題,這裡我同樣採用了那種方式卻失敗了)
陽光總在風雨後,讓我們一探究竟
由於上面的兩個bug,我們已經猜出了個大概;可能得原因視圖在重新整理後相同位置的視圖對象被替換了,這樣導致他內部儲存的唯一標識改變了;
下面我將對ListView和RecyclerView進行一個比較:
測試代碼如下,我列印對象id看相同位置的TextView對象是否一致;
RecyclerView測試
public void onBindViewHolder(MyViewHolder holder, final int position) { if (position == 0) { Log.e(TAG, "position:" + position + " tv:" + holder.tv.toString()); }else { Log.i(TAG, "position:" + position + " tv:" + holder.tv.toString()); } holder.tv.setText(mDatas.get(position));}
還是進入看圖說話,我經過兩次重新整理視圖得到如的log資訊;你會發現視圖沒有移動,只是重新整理之後相應位置的TextView對象都已經改變了,我想這種情況我是不能容忍的;心中有萬千草泥馬奔騰而來,Google怎麼會犯如此問題呢?(ps:哈哈,我也想是Google的問題,但是往往是我們自己的問題)
ListView測試
public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { holder = new ViewHolder(); convertView = mInflater.inflate(R.layout.item_simple, parent,false); holder.tv = (TextView) convertView.findViewById(R.id.textView); convertView.setTag(holder); }else { holder = (ViewHolder) convertView.getTag(); } if (position == 0) { Log.e(TAG, "position:" + position + " tv:" + holder.tv.toString()); }else { Log.i(TAG, "position:" + position + " tv:" + holder.tv.toString()); } holder.tv.setText(getItem(position)); return convertView; }
還是還是看圖說話,發現沒有看到沒有這是ListView在進過兩次重新整理之後的結果;相同位置多次重新整理之後可以完美的返回相同的對象,這樣的好處是我們無需重複賦值;具體思路是可以將唯一標識儲存在對象中,重新整理之後我們比較標識就可以知道是否改變;
“有了新歡就拋棄了舊愛,我勤勤懇懇任勞任怨多年,最終等到的卻是被遺棄的命運(@deprecated),等到你們被傷的遍體鱗傷的時候別來找我,哼…”
——ListView的告白
怎麼辦難道我的與時俱進就止步於此了嗎,我不甘心;好吧,只能去看官方API了;
Google API的探索
功夫不負有心人終於讓我找到了他,我就說Google不會這麼坑嗎?是誰說的拖出去斬了…:)
RecyclerView.java
/** * Returns true if this adapter publishes a unique <code>long</code> value that can * act as a key for the item at a given position in the data set. If that item is relocated * in the data set, the ID returned for that item should be the same. * * @return true if this adapter‘s items have stable IDs */ public final boolean hasStableIds() { return mHasStableIds; }
上述代碼出自RecyclerView的源碼描述,注釋的大體意思是我們要重寫getItemId方法給他一個不同位置的唯一標識,並且hasStableIds返回true的時候應該返回相同的資料集;
接下來我們複寫getItemId()方法,並且設定setHasStableIds(true); (ps:在建構函式中設定)
好吧,眼淚不停落下… 返回對象驚人的一致…
接下來我們繼續研究API:
以上是RecyclerView.Adapter的重新整理資料的API,看到沒如今notifyDataSetChanged()方法似乎顯得黯然失色了;以上為我們提供了:
更新1-n條資料;
插入1-n條資料;
刪除1-n條資料;
具體用法我想大家看方法名稱應該就會使用了,無需解釋;
風雨過後會見到彩虹嗎?
首先我們改進原有使用的方式:
下拉載入資料我們可以使用notifyItemRangeChanged(int, int)或者notifyItemRangeInserted(int, int) 這個方法,這樣也避免了整個頁面的全部重新整理問題,也無需自己判斷是否原來的View了;
盡量使用固定ID,這樣可以正確返回position的位置否則我們可能在使用了notifyItemInserted(int)或者notifyItemRangeInserted(int, int) 的時候position出現異常
疑惑:
照Google文檔描述在使用了notifyItemInserted時候原來位置的position會加1,但是事實證明資料的position位置毫無變化;可是當我設定了使用了固定ID之後,位置確實動態變化了但是插入卻出現了問題,雖有動畫但是結構沒有變化,可是notifyItemRemoved方法卻是可用的;
不設定固定ID,但是要讓他position正確
public void addData(int pos){ mDatas.add(pos,"Insert One"); notifyItemInserted(pos); // 加入如下代碼保證position的位置正確性 if (pos != mDatas.size() - 1) { notifyItemRangeChanged(pos, mDatas.size() - pos); }}public void deleteData(int pos){ mDatas.remove(pos); notifyItemRemoved(pos); // 加入如下代碼保證position的位置正確性 if (pos != mDatas.size() - 1) { notifyItemRangeChanged(pos, mDatas.size() - pos); }}
我們主要是對於插入刪除之後手動更新position未變的位置,當時這樣會導致新的對象建立;
設定固定ID,就無需擔心position的問題了
雖然設定了固定ID無需擔心position的問題,可是要記得這該死的墨菲定律 ;增加一項會在原來的位置不斷增加,而刪除正常;what’s a fucking bug!這個令人匪夷所思的問題很遺憾的告訴大家如今我也不知道是為什麼,可是值得慶幸是刪除沒問題;你要知道我們在列表中增加一項和刪除一項哪個用的頻率會高一些呢?(ps:如大家有更好的辦法也歡迎留言)
3. 這個是官方的建議,有兩種事件:資料更新和結構變化,但是不管是哪種方式他總是最有效和保險的方式,他能讓我們得到正確的position;也就是針對上面第二條時,我們不確定position是否正確時我們可以在最後在調用他一次;
RecylerView我真的會用了?!
如今採用新的方式之後感覺還是不錯的,但是我們其實還有一個問題沒有解決就是第一幅圖重新整理之後回到頂部圖片還是會移動位置,我沒有辦法找到解決辦法可能是由於它瀑布流本生的問題;可是我們有替代方案就是讓recyclerview重新整理之後滾動到頂部RecyclerView.scrollToPosition(0);這樣也算是間接解決了這個問題;
玫瑰玫瑰最嬌美
玫瑰玫瑰最豔麗
…
玫瑰雖美可是往往我們沒有注意到她的刺,美不美香不香只有試過才知道切不可人云亦云;好了寫到這裡算是告一段落了;TMD真的好累…
Android學習之-RecyclerView帶刺的玫瑰