2016年7月22日金曜日

[安藤]Position!Position!Position!:如何脫出ListView與ExpandableListView的迷宮

本篇整理了在走過ExpandableListView的巨大位置轉換迷宮之後,經歷過的cell access所遇到的位置轉換問題。看完保證穩過不再卡。

bgm: FC版五右衛門2的迷宮。迷宮bgm的最愛。做這個部分的時候一直腦中無限循環這首...




本篇的名詞定義:

  • 頭字小寫為object funtion, 大寫為static method.
  • 初始顯示位置:使用listview.getFirstVisiblePosition(); 取得。
  • 結束顯示位置:使用listview.getLastVisiblePosition(); 取得。
  • 絕對位置:指listview的資料的position。數量就是adapter.getCount()來的...
  • 相對位置:指listview的顯示position。只有listview.getChildAt()會用到。
  • 編碼位置:ExpandableListView限定的概念。指使用以下方法編碼過的位置。經過一個方法把Group跟Child位置合併編碼為另一個數字。ExpandableListView.getPackedPositionForGroup(groupPosition);
    ExpandableListView.getPackedPositionForChild(groupPosition, childPosition);
  • 解碼位置:等於ListView的絕對位置。指使用以下方法解碼過的位置。expandableListView.getPackedPositionGroup(packedPosition);
    expandableListView.getPackedPositionChild(packedPosition);



ListView 從外部 Touch access:
利用「listview.pointToPosition(x, y);」,可以把觸碰點轉換為碰到listview的絕對位置。
要取得當時正在顯示的child view,須以listview.getChildAt(相對位置)來取。
轉換方式: 相對位置 = 絕對位置-初始顯示位置。


ListView 從外部更新畫面上所有的cell:
for (i = 初始顯示位置; i < 結束顯示位置; i++)
{
    View v = listview.getChildAt(i);
    ....任何想做的操作。
}


ListView的項目選擇:
項目顏色的變換,比較簡單的情況可以使用listView.setItemChecked()的設計。但是必須先知道它怎麼運作。

第一步,必須先設定選擇方法是單選還是多選,否則不會動作。可以xml 「android:choiceMode="singleChoice"」指定或是程式碼指定 「listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE)」

接下來可以使用UI點擊指定的cell,或是使用程式碼
「listView.setItemChecked(絕對位置, boolean);」點選。
若是view裡面有多個object的話,只有最底層的object會被標記為checked。因此把該view object的背景以selector指定的話就會自動改變狀態。

取得結果,單選使用「listview.getCheckedItemPosition();」,多選使用「listview.getCheckedItemPositions()」然後針對該sparsearray使用Iterator遍歷。





接下來是重頭戲「ExpandableListView」。
既然是擴展ListView的實作,所以Group跟Child跟ListView的絕對位置會有一種轉換機制。
Group跟Child,以ListView的角度來看都是一個個依序排的cell。
假設有4個Group, Goroup0與1已被展開,看起來的狀況會是這樣。
ListView的絕對位置   0   1   2   3   4   5   6   7
Group位置                    0             1        2   3
Child位置                           0   1        0

ExpandableListView是採用把group/child再編碼的方式,來處理跟ListView之間的轉換。
如此就成為三個階層:
ListView 的絕對位置(解碼位置) --- 編碼位置 --- ExpandListView的Group / Child

轉換的方式如下:
編碼位置-->解碼位置
expandableListView.getFlatListPosition(編碼位置)

解碼位置-->編碼位置
expandableListView.getExpandableListPosition(絕對位置)

編碼位置 -> ExpandListView的Group / Child
ExpandableListView.getPackedPositionGroup(編碼位置)
ExpandableListView.getPackedPositionChild(編碼位置)

ExpandListView的Group / Child -> 編碼位置
ExpandableListView.getPackedPositionForGroup(groupPosition);
ExpandableListView.getPackedPositionForChild(groupPosition, childPosition);

其他:
ExpandableListView.getPackedPositionType(編碼位置) :可以判斷該編碼的位置是group或是child。遍歷相對位置的時候還蠻方便的。


重點:
  • Expand/collapse的時候,所有的cell都會重畫。也就是位置會跑掉。上面提到的轉換出來的結果有可能會變動。所以執行這些轉換跟取cell的處理的時候不要去執行expand/collapse。實作一次只開一個child的設計的時候,建議先關閉舊的之後再做其他處理。
  • ExpandableListView跟ListView的函式要判別清楚。