先看效果圖:
思路:
使用DialogFragment、RecyclerView、CheckBox
準(zhǔn)備:
圓角Drawable,checkbox Drawable,checkButtonDrawable诵盼,字體顏色 Drawable
開發(fā)的時(shí)候應(yīng)先把所需要的所有UI準(zhǔn)備好之后 再進(jìn)行開發(fā),而不是邊開發(fā)邊找ui圖或者編寫xml文件
開始Code:
1. 整體布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:actionBarSize">
<TextView
android:id="@+id/tip_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""/>
<TextView
android:id="@+id/title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Title"/>
</android.support.v7.widget.Toolbar>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
</android.support.v7.widget.RecyclerView>
<android.support.v4.widget.Space
android:layout_width="match_parent"
android:layout_height="10dp"/>
<TextView
android:id="@+id/submit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:background="@drawable/blue_button_background"
android:padding="10dp"
android:text="確定"/>
</LinearLayout>
Tips: 用Space可以用來(lái)占位
2.PickerDialog
/**
* 新建一個(gè)dialog
*
* @param maxSelected 最大可選數(shù)
* @param title 標(biāo)題
* @param list 數(shù)據(jù)源
* @return
*/
public static PickerDialog newInstance(int maxSelected, String title, ArrayList<? extends IContent> list) {
Bundle args = new Bundle();
args.putInt(MAX_NUM, maxSelected);
args.putString(TITLE, title);
args.putParcelableArrayList(SOURCE, list);
PickerDialog fragment = new PickerDialog();
fragment.setArguments(args);
return fragment;
}
IContent是一個(gè)接口,有一個(gè)getDesc()方法 用于顯示單位的名稱风宁,由于需要將其序列化洁墙,所以IContent 需要繼承自Parcelable接口。
public interface IContent extends Parcelable{
String getDesc();
}
然后給RecyclerView設(shè)置一下adapter,布局方式以及間隔就好了
·····
·····
·····
·····
·····
·····
·····
·····
·····
·····
·····
嗎戒财?當(dāng)然不是囖!重點(diǎn)才剛開始热监,精髓都在adapter里
3.PickerAdapter
public class PickerAdapter<T extends IContent> extends
RecyclerView.Adapter<PickerAdapter.ItemHolder> {
···
public PickerAdapter(int maxSelected, List<T> list, Context context) {
this.maxSelected = maxSelected;
mList = list;
this.context = context;
}
@Override
public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(context).inflate(R.layout.item_picker, null, false);
return new ItemHolder(v);
}
@Override
public void onBindViewHolder(ItemHolder holder, int position) {
···
}
static class ItemHolder extends RecyclerView.ViewHolder {
CheckBox cbx;
public ItemHolder(View itemView) {
super(itemView);
cbx = (CheckBox) itemView.findViewById(R.id.checkbox);
}
}
}
item_picker.xml
<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/checkbox"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_gravity="center"
android:background="@drawable/selector_item_bg"
android:button="@drawable/selector_check_button"
android:gravity="right|center_vertical"
android:paddingRight="10dp"
android:textColor="@drawable/selector_text"/>
經(jīng)過上面的步驟一個(gè)簡(jiǎn)單的adapter就寫好了,但是有經(jīng)驗(yàn)的開發(fā)一眼就知道上面的代碼有復(fù)用所帶來(lái)的顯示問題饮寞。
由于ListView/RecyclerView的復(fù)用機(jī)制,如果我們對(duì)第一個(gè)Item中的CheckBox進(jìn)行了選中操作孝扛,那么當(dāng)你向上滑動(dòng)的時(shí)候會(huì)發(fā)現(xiàn)下面的Item中的CheckBox會(huì)自動(dòng)選中了。相信不少人都曾經(jīng)遇到過這樣的問題幽崩,通過goole或者stackoverflow,知道不能用view去保存item視圖的狀態(tài)苦始,于是選擇去使用數(shù)據(jù)來(lái)控制,這樣確實(shí)可以基本解決這個(gè)問題慌申。
但是如果我們每個(gè)數(shù)據(jù)都再加上一個(gè)布爾值用于記錄的話陌选,這代價(jià)就有點(diǎn)略大了。為什么呢蹄溉?一是費(fèi)時(shí)二是費(fèi)力三是完全沒必要咨油。其實(shí)Android為我們提供了一種完美的數(shù)據(jù)結(jié)構(gòu)來(lái)解決這個(gè)問題:SparseBooleanArray ←_←
相信不少看過android內(nèi)存優(yōu)化、性能優(yōu)化的同學(xué)都知道這個(gè)東西柒爵,然后這些文章都只告訴你使用SparseArray替代HashMap役电,然后會(huì)寫一堆關(guān)于存儲(chǔ)結(jié)構(gòu)的東西,告訴你這個(gè)更適合android餐弱。當(dāng)時(shí)我就比較疑惑問什么SparseArray默認(rèn)的key都是Integer類型的宴霸。那么囱晴,現(xiàn)在的代碼就是這樣了:
···
private SparseBooleanArray mCheckStates = new SparseBooleanArray();
@Override
public void onBindViewHolder(ItemHolder holder, int position) {
holder.cbx.setTag(position);
holder.cbx.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int pos = (int) buttonView.getTag();
if (isChecked) {
mCheckStates.put(pos, true);
//do something
} else {
mCheckStates.delete(pos);
//do something
}
}
});
holder.cbx.setText(mList.get(position).getDesc());
holder.cbx.setChecked(mCheckStates.get(position, false));
}
···
就這些代碼就解決了復(fù)用的問題膏蚓,而且完全不必去給數(shù)據(jù)項(xiàng)新增一個(gè)布爾字段,到這里是不是就恍然大悟了
到了這一步后畸写,我們就開始實(shí)現(xiàn)單選模式
實(shí)現(xiàn)單選
單選比較簡(jiǎn)單驮瞧,點(diǎn)擊之后關(guān)閉dialog然后通過一個(gè)回調(diào)將選中的值傳回去就可以了,當(dāng)然我們需要先判斷一下是否已經(jīng)有選中了的值,如果有選中了的值了枯芬,那么久先將其置為未選中狀態(tài)论笔,那么怎么知道是否有選中的值呢?SparseArray又立功了
···
@Override
public void onBindViewHolder(ItemHolder holder, int position) {
holder.cbx.setTag(position);
holder.cbx.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int pos = (int) buttonView.getTag();
if (isChecked) {
if (maxSelected == 1 && maxSelected == mCheckStates.size()) {
int pre = mCheckStates.keyAt(0);
mCheckStates.clear();
notifyItemChanged(pre);
}
mCheckStates.put(pos, true);
//do something
if (mOnSelectChangeListener != null) {
mOnSelectChangeListener.onSelect(pos, mCheckStates.size());
}
} else {
mCheckStates.delete(pos);
//do something else
if (mOnSelectChangeListener != null) {
mOnSelectChangeListener.unSelect(pos);
}
}
}
});
holder.cbx.setText(mList.get(position).getDesc());
holder.cbx.setChecked(mCheckStates.get(position, false));
}
public interface OnSelectChangeListener {
void onSelect(int pos, int selectedSize);
void unSelect(int pos);
}
···
實(shí)際效果:
[圖片上傳失敗...(image-e4ebf6-1521086193167)]
實(shí)現(xiàn)多選
多選分為2種
1.有限制選擇個(gè)數(shù)
由于當(dāng)選擇到最大可選數(shù)時(shí)千所,即使把checkbox設(shè)為disable也無(wú)法控制選中狀態(tài)狂魔,所以需要在代碼里置為未選中狀態(tài)setChecked(!isChecked),但是由于setChecked也會(huì)調(diào)用onCheckedChanged方法,導(dǎo)致引起死循環(huán)淫痰,所以需要加鎖進(jìn)行控制
if (mCheckStates.size() == maxSelected) {
//不然cbx改變狀態(tài).
lockState = true;
buttonView.setChecked(!isChecked);
lockState = false;
Toast.makeText(context, "最多可選" + maxSelected + "個(gè)", Toast.LENGTH_SHORT).show();
return;
}
實(shí)際效果:
[圖片上傳失敗...(image-588799-1521086193168)]
2.無(wú)限制選擇個(gè)數(shù)
無(wú)限制就不需要做什么額外的操作最楷,默認(rèn)就是無(wú)限制的,只需要返回選中的數(shù)據(jù)集就??了.
實(shí)際效果:
[圖片上傳失敗...(image-8c6eba-1521086193168)]
接著獲取選中的集合
public ArrayList<T> getSelectedItems() {
selectItems.clear();
for (int i = 0; i < mCheckStates.size(); i++) {
if (mCheckStates.valueAt(i)) {
selectItems.add(mList.get(mCheckStates.keyAt(i)));
}
}
return selectItems;
}
最后
在PickerDialog中寫一個(gè)回調(diào)接口,將選中的數(shù)據(jù)集傳遞回去籽孙,就大功告成了
/**
* 選擇后的回調(diào)烈评,返回選中的list集合
* * @param <T>
*/
public interface OnSelectedListener <T extends IContent> {
void onSelected(List<T> contents);
}
擴(kuò)展:
- 在屏幕旋轉(zhuǎn)時(shí)保存選中的數(shù)據(jù)
復(fù)寫onSaveInstanceState和onViewStateRestored函數(shù),當(dāng)改變屏幕方向時(shí)會(huì)調(diào)用這兩個(gè)方法
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//保存數(shù)據(jù)
//···
outState.putInt(SELECTED_NUM, hasSelectedNum);
outState.putString(SELECTED_POS_SET, adapter.getSelectedPos());
}
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
//可以在這里設(shè)置格數(shù)犯建,橫屏有3格讲冠,豎屏兩格
Configuration newConfig = getActivity().getResources().getConfiguration();
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
layoutManager.setSpanCount(2);
} else {
layoutManager.setSpanCount(3);
}
recyclerView.setLayoutManager(layoutManager);
if (savedInstanceState == null) {
return;
}
//恢復(fù)數(shù)據(jù)
//···
hasSelectedNum = savedInstanceState.getInt(SELECTED_NUM, 0);
selectedPos = savedInstanceState.getString(SELECTED_POS_SET);
tipsTv.setText(String.format(getString(R.string.has_selected), String.valueOf(hasSelectedNum)));
if (adapter != null) {
adapter.setSelectedPosSet(getSelectedPos());
adapter.setList(mList);
}
最后的最后
項(xiàng)目地址:https://github.com/vienan/PickerDialog
更新:
2018-3-15:
為了避免列表中checkbox的復(fù)用問題,除了以上的方法還可以使用DataBinding技術(shù)去改變對(duì)應(yīng)對(duì)象的值适瓦,不過最好的方法還是避免在列表中使用checkbox或RadioButton等等的東西竿开,使用StateListDrawable來(lái)代替他們,效果一樣犹菇,但省去了復(fù)用的麻煩德迹,也不必處理前面??兩者的事件