ListView的小插曲
其實ListView
或者說它們這一類都是實現了單選多選模式的,如果你使用CheckedTextview
等實現了Checkable
的子類的話涤浇,根本不用寫什么代碼鳖藕,單選多選模式就搞定了。
* Register a callback to be invoked when an item in this AdapterView has
* been selected.
*
* @param listener The callback that will run
*/
public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
mOnItemSelectedListener = listener;
}
但是只锭,但是著恩,這個回調在 ListView
或者 GridView
里面似乎并沒有什么卵用呢!這是為撒呢蜻展?喉誊!
在 AdapterView
里面有個handleDataChanged()
的方法,這個方法纵顾,就是正常執(zhí)行回調OnItemSelectedListener
滴施逾,但是敷矫,這個方法被AbsListView
給重寫了例获,而且,沒有再去回調對應的OnItemSelectedListener
了曹仗,所以榨汤,在ListView和GridView中設置了這個回調也是沒有用的讥脐。詳細的自己去扒一扒源碼看看吧遭居,這里不細說了。旬渠。
Why is my onItemSelectedListener not called in a ListView?
雖然沒有了相關回調俱萍,但是我們可以在適當的地方通過調用方法:SparseBooleanArray positions = mListView.getCheckedItemPositions()
來獲取我們選中的位置的。
RecyclerView實現單選和多選模式
上面說了 ListView
里面的單選多選模式告丢,接下來就是 RecyclerView
的實現了枪蘑,首先考慮需要處理哪些問題:
選擇標識的添加和相關選擇狀態(tài)保存,item的點擊事件(普通模式點擊響應岖免,選擇模式下刷新為選中或非選中狀態(tài)),最后就是要弄好回調方法岳颇。
這個庫還是基于之前的Android RecycleView輕松實現下拉刷新、加載更多颅湘,所以呢话侧,這次創(chuàng)建了一個SelectRefreshRecycleAdapter
的類用來專門處理單選多選模式,當然闯参,它肯定是繼承自RefreshRecycleAdapter<T>
滴瞻鹏。在這個類中,根據是選擇模式或者是普通模式來進行了不同的點擊處理鹿寨。處理了第一個問題新博。
選擇標識的添加 選擇狀態(tài)保存
對于選擇標識的添加,首先這個一般來說應該和各種業(yè)務沒有關系脚草,所以不應該定義在json數據中赫悄,當然啦,如果選擇狀態(tài)你真的已經定義了相關字段并且時刻根據后臺數據刷新的話馏慨,這里也是可以解決滴涩蜘。
首先我這里定義了一個接口Iselect
,然后熏纯,這里接著也定義好了一個實現類了同诫。
public interface ISelect {
boolean isSelected();
void setSelected(boolean selected);
}
public class SelectBean implements ISelect, Parcelable {
private boolean isSelected;
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
...
}
在使用的時候,如果你的Bean中已經有了相關選中狀態(tài)的字段了樟澜,那么實現Iselect
接口误窖,如果沒有定義相關字段叮盘,那么直接繼承SelectBean
,其他的就不用去管了霹俺。這是關于添加選擇標識的解決柔吼,然后就是選中狀態(tài)的保存,這個很簡單了丙唧,內部維護了一個集合愈魏,選中了添加進去,取消選中就移除了想际。
Item的點擊及同步刷新
因為這里有單選模式和多選模式兩種情況嘛培漏,接下來一次分析下。
如果是單選模式的話胡本,我們在選中item2之后牌柄,不僅要刷新item2的狀態(tài),而且還要將之前的選中item的狀態(tài)更新侧甫,意思就是可能要更新兩個item珊佣。所以這里要定義一個prePos
的字段來保存之前的那個item。
如果是多選模式的話披粟,那么就比較簡單了咒锻,糊糊選就好了(不要糾結為什么用糊糊)。
得益于RecyclerView的后天優(yōu)勢守屉,我們每次只需要調用notifyItemChanged(pos)
的方法來刷新指定的item就行了虫碉。
@Override
public void performClick(final View itemView, final int position) {
final T testBean = list.get(position);
if (isSelectMode) {
Log.e("TAG", "onViewHolderBind: " + position + "點擊了!胸梆!");
//點擊后取反當前位置
boolean selected = !testBean.isSelected();
testBean.setSelected(selected);
dispatchSelected(itemView, position, testBean, selected);
if (currentMode == SingleMode && position != prePos && testBean.isSelected()) {
//單選模式的prePos處理
list.get(prePos).setSelected(false);
dispatchSelected(itemView, prePos, testBean, false);
notifyItemChanged(prePos);
}
notifyItemRangeChanged(position, 1);
prePos = position;
} else {
//不是選擇模式就正扯嘏酰回調咯
if (listener != null) {
listener.onItemClick(itemView, position);
}
}
}
最后就是Viewholder里面的具體實現了:
private static class MyViewHolder extends RecyclerView.ViewHolder {
private final CheckedTextView mTv;
public MyViewHolder(View itemView) {
super(itemView);
mTv = (CheckedTextView) itemView.findViewById(R.id.text);
}
public void bindDateView(TestBean s) {
mTv.setText(s.isSelected() ? "選中:" + s.getName() : s.getName());
mTv.setChecked(s.isSelected());
}
}
相關回調
根據上面 ListView
的那個問題,如果在選擇過程中碰镜,沒有相關回調我們是會抓狂的兢卵。參照 ListView
的相關接口,有了以下的定義:
interface OnItemSelectedListener {
void onItemSelected(View view, int position, boolean isSelected);
void onNothingSelected();
}
額绪颖,最后加了一個isSelected
的參數秽荤,感覺就說你選中了撒沒有選中的不說是不是有點兒不厚道?柠横!萬一你自己要在這里去維護自己的一個數據呢窃款?那不是坑你啦,其實我不想給你說你完全可以通過getSelectedBeans()
的方法來直接獲取已經選中的集合牍氛。
private void dispatchSelected(View itemView, int position, T testBean, boolean isSelected) {
if (isSelected) {
selectedBeans.add(testBean);
} else {
selectedBeans.remove(testBean);
if (selectedListener != null && selectedBeans.isEmpty()) {
selectedListener.onNothingSelected();
}
}
if (selectedListener != null) {
selectedListener.onItemSelected(itemView, position, isSelected);
}
}
額外贈送
bug修復
之前寫的版本中晨继,加載更多還沒有適配GridLayoutManager
,這里也做了相關的適配,并且提供了回調搬俊,方便你指定對應的某個 position
的spanSize
:
public void setLayoutManager(final RecyclerView.LayoutManager manager) {
this.manager = manager;
if (manager instanceof GridLayoutManager) {
((GridLayoutManager) manager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
switch (adapter.getItemViewType(position)) {
case TYPE_BOTTOM:
return ((GridLayoutManager) manager).getSpanCount();
default:
return (spanSizeCallBack != null ? spanSizeCallBack.getSpanSize(position) : 0) == 0 ? 1 : spanSizeCallBack.getSpanSize(position);
}
}
});
}
mRecyclerView.setLayoutManager(manager);
}
你只需要這么調用:
mRecycleView.setLayoutManager(manager);
mRecycleView.setSpanSizeCallBack(new SwipeRefreshRecycleView.SpanSizeCallBack() {
@Override
public int getSpanSize(int position) {
return 1;
}
});
強大的ItemDecoration
因為說到了GridLayoutManager
了嘛紊扬,那么必須說下這個ItemDecoration
,例如我們需要讓多個item的留白是等距離的,那么就要使用這個東東了蜒茄。
另外它還可以來用作頭布局,還可以做出sticky的效果餐屎。上面的核心代碼大概就是醬紫的:
/**
*
* @param space item之間的空間
* @param count 列數
* @param showEdge 是否顯示左右邊緣
*/
public SpacesItemDecoration(int space, int count, boolean showEdge) {
this.spacing = space;
this.spanCount = count;
this.showEdge = showEdge;
pre = spacing * 1.0f / spanCount;
}
@Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildLayoutPosition(view);
int column = position % 3;
if (showEdge) {
outRect.left = (int) (spacing - column * pre);//left
outRect.right = (int) ((column + 1) * pre);//right
} else {
outRect.left = (int) (column * pre);
outRect.right = (int) (spacing - (column + 1) * pre);
}
if (position < spanCount) { // top
outRect.top = spacing;
}
outRect.bottom = spacing; // bottom
}
到這里檀葛,RecyclerView
的單選或者多選模式就搞定了!如果對于這個 Adapter
有相關疑問的可以先去看看上面的一篇喲腹缩。
附上最新的相關效果:
倉庫地址:https://github.com/lovejjfg/PowerRecyclerView
---- Edit By Joe At 2016 11 26 ----