平時(shí)開(kāi)發(fā)的時(shí)候我們總會(huì)碰到這樣的需求。

有時(shí)是多選,有時(shí)是單選,這樣的頁(yè)面基本都是用RecyclerView來(lái)做的,而如果每次做操作的時(shí)候都要去寫(xiě)這個(gè)單選框/多選框的邏輯,那就太麻煩了,所以我就想把這樣的單選/多選列表功能給封裝起來(lái)。
一.思路
這種功能的基本布局頁(yè)面都是這樣

或者有些需要把選擇框放在右邊

我的思路是外層還是用RecyclerView來(lái)做,里層(就是實(shí)線內(nèi))我打算用ViewModel來(lái)做,這樣既能封裝外層的選擇框邏輯,對(duì)內(nèi)層也低耦合。
二.效果展示
1.單選模式

2.多選模式

PS:左邊的布局是個(gè)ViewModel可以自由定義,因?yàn)闀r(shí)間的問(wèn)題我沒(méi)時(shí)間再寫(xiě)另外的布局來(lái)展示。
三.調(diào)用方法
調(diào)用方法很簡(jiǎn)單:
1.定義自己的ViewModel
2.調(diào)用
checkRecyclerView = new CheckRecyclerView.Builder<ChoseShopEntity>(this,"com.berchina.o2omerchant.ui.viewModel.shop.ChoseShopViewModel")
.setItemDecoration(new XXXItemDecoration(10))
.setBackgroupColorResID(R.color.divider_grey)
// .setCheckboxResID(R.drawable.cb_shop_create)
.setIsRadio(false)
.builder();
framelayout.addView(checkRecyclerView.getContentView());
Builder中的第二個(gè)參數(shù)我傳的就是ViewModel的類名。
3.設(shè)置數(shù)據(jù)
checkRecyclerView.setAdapter(datalist);
封裝好了用起來(lái)就是快,不會(huì)每次都要重復(fù)去寫(xiě)單選和復(fù)選的邏輯。
四.CheckRecyclerView內(nèi)的操作
我這里把RecyclerView、Adapter和ViewHolder都寫(xiě)在一個(gè)類中,仿照RecyclerView源碼的寫(xiě)法,當(dāng)然分開(kāi)寫(xiě)也行。
先貼全部代碼吧
public class CheckRecyclerView<T extends CheckRecyclerView.CheckRecyclerEntity> {
protected Context context;
protected RecyclerView recyclerView;
protected static CheckRecyclerAdapter adapter;
// 0 表示都不顯示,1表示顯示左邊,2表示顯示右邊
protected static int isShow;
protected static String vmName;
protected static int checkboxResID;
protected static boolean isRadio;
protected static RadioObserver radioObserver; // 監(jiān)聽(tīng)單選
protected static MultiObserver multiObserver; // 監(jiān)聽(tīng)多選
protected Builder builder;
// 單選情況下的觀察者
private static Action1<Integer> observer1 = new Action1<Integer>() {
@Override
public void call(Integer integer) {
if (adapter != null){
adapter.checkChange(integer);
}
}
};
// 多選情況下的觀察者
private static Action1<Integer> observer2 = new Action1<Integer>() {
@Override
public void call(Integer integer) {
if (adapter != null){
adapter.multiChose(integer);
}
}
};
private CheckRecyclerView(Context context,Builder builder){
this.context = context;
this.builder = builder;
this.isShow = builder.isShow;
this.vmName = builder.vmName;
this.checkboxResID = builder.checkboxResID;
this.isRadio = builder.isRadio;
this.radioObserver = builder.radioObserver;
this.multiObserver = builder.multiObserver;
initView();
}
private void initView(){
recyclerView = new RecyclerView(context);
recyclerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
recyclerView.setBackgroundResource(builder.backgroupColorResID);
recyclerView.setLayoutManager(builder.layoutManager);
if (builder.itemDecoration != null) {
recyclerView.addItemDecoration(builder.itemDecoration);
}
}
/**
* 設(shè)置適配器
*/
public void setAdapter(){
List<T> datalist = builder.datalist;
if (datalist == null){
datalist = new ArrayList<T>();
}
adapter = new CheckRecyclerAdapter(context,datalist);
recyclerView.setAdapter(adapter);
}
/**
* 設(shè)置適配器
*/
public void setAdapter(List<T> datalist){
adapter = new CheckRecyclerAdapter(context,datalist);
recyclerView.setAdapter(adapter);
}
/**
* get and set 方法
*/
public RecyclerView getContentView() {
return recyclerView;
}
public static CheckRecyclerAdapter getAdapter() {
return adapter;
}
public void setIsShow(int isShow) {
this.isShow = isShow;
}
public static void setVmName(String vmName) {
CheckRecyclerView.vmName = vmName;
}
/**
* 獲取到選擇的結(jié)果
* 規(guī)則:-1表示沒(méi)有選擇
*/
// 單選左
public int getLeftRadio(){
if (adapter != null && adapter.getDatas() != null && adapter.getDatas().size() > 0){
return adapter.getLeftRadio();
}
return -1;
}
// 單選右
public int getRightRadio(){
if (adapter != null && adapter.getDatas() != null && adapter.getDatas().size() > 0){
return adapter.getRightRadio();
}
return -1;
}
// 多選左 返回被選中的數(shù)組
public List<Integer> getLeftMulti(){
List<Integer> multiList = new ArrayList<>();
if (adapter != null && adapter.getDatas() != null && adapter.getDatas().size() > 0){
for (int i = 0; i < adapter.getDatas().size(); i++) {
if (((CheckRecyclerEntity)adapter.getDatas().get(i)).leftCheck == true) {
multiList.add(i);
}
}
}
return multiList;
}
// 多選右 返回被選中的數(shù)組
public List<Integer> getRightMulti(){
List<Integer> multiList = new ArrayList<>();
if (adapter != null && adapter.getDatas() != null && adapter.getDatas().size() > 0){
for (int i = 0; i < adapter.getDatas().size(); i++) {
if (((CheckRecyclerEntity)adapter.getDatas().get(i)).rightCheck == true) {
multiList.add(i);
}
}
}
return multiList;
}
/**
* 判斷在多選的情況下是否全部選擇
*/
public boolean isAllChose(){
Boolean isAllChose = true;
if (adapter != null && adapter.getDatas() != null && adapter.getDatas().size() > 0) {
if (isShow == 1) {
for (int i = 0; i < adapter.getDatas().size(); i++) {
if (((CheckRecyclerEntity)adapter.getDatas().get(i)).leftCheck == false){
return false;
}
}
} else if (isShow == 2) {
for (int i = 0; i < adapter.getDatas().size(); i++) {
if (((CheckRecyclerEntity)adapter.getDatas().get(i)).rightCheck == false){
return false;
}
}
} else {
return false;
}
}else {
isAllChose = false;
}
return isAllChose;
}
/**
* 外部控制內(nèi)部進(jìn)行單選的操作
*/
public void setRadio(int position){
if (adapter != null){
adapter.checkChange(position);
}
}
/**
* 設(shè)置全選/全不選
* 規(guī)則:true表示全選 false表示全不選
*/
public void setAllMulti(Boolean bol){
if (adapter != null){
adapter.setAllMulti(bol);
}
}
/**
* recycelerView 的 adapter
*/
public static class CheckRecyclerAdapter<T extends CheckRecyclerEntity> extends XXXRecyclerViewBaseAdapter<T>{
// 記錄單選的選項(xiàng)(用空間換時(shí)間)
private int leftRadio = -1;
private int rightRadio = -1;
public CheckRecyclerAdapter(Context context, List dataSource) {
super(context, dataSource);
}
@Override
protected BerRecyclerViewHolder getViewHolder(ViewGroup parent) {
return new CheckRecyclerViewHolder(LayoutInflater.from(context).inflate(R.layout.item_check_recycler,parent,false),context);
}
/**
* 單選
*/
public void checkChange(int position){
if (position < dataSource.size()) {
// 先將所有都設(shè)為未點(diǎn)擊
for (int i = 0; i < dataSource.size(); i++) {
dataSource.get(i).leftCheck = false;
dataSource.get(i).rightCheck = false;
}
// 再設(shè)置點(diǎn)擊的Item
if (isShow == 1) {
dataSource.get(position).leftCheck = true;
leftRadio = position;
}else if (isShow == 2){
dataSource.get(position).rightCheck = true;
rightRadio = position;
}
// 做響應(yīng)
if (radioObserver != null) {
radioObserver.radioClick(position, isShow);
}
this.notifyDataSetChanged();
}
}
/**
* 多選
*/
public void multiChose(int position){
// 點(diǎn)擊后展示相反的情況
if (isShow == 1) {
dataSource.get(position).leftCheck = !dataSource.get(position).leftCheck;
}else if (isShow == 2){
dataSource.get(position).rightCheck = !dataSource.get(position).rightCheck;
}
// 做響應(yīng)
if (multiObserver != null) {
multiObserver.multiClick(position, isShow);
}
this.notifyDataSetChanged();
}
/**
* 設(shè)置全選
*/
public void setAllMulti(Boolean bol){
if (isShow == 1) {
for (int i = 0; i < dataSource.size(); i++) {
dataSource.get(i).leftCheck = bol;
}
this.notifyDataSetChanged();
}else if (isShow == 2){
for (int i = 0; i < dataSource.size(); i++) {
dataSource.get(i).rightCheck = bol;
}
this.notifyDataSetChanged();
}
}
// get and set
public int getLeftRadio() {return leftRadio;}
public int getRightRadio() {return rightRadio;}
}
/**
* recycelerView 的 viewholder
*/
public static class CheckRecyclerViewHolder<T extends CheckRecyclerEntity> extends XXXRecyclerViewHolder<T>{
@InjectView(R.id.cb_left)
CheckBox cbLeft;
@InjectView(R.id.cb_right)
CheckBox cbRight;
@InjectView(R.id.fl_content)
FrameLayout flContent;
private BaseViewModel viewmodel;
public CheckRecyclerViewHolder(View itemView, Context context) {
super(itemView, context);
ButterKnife.inject(this,itemView);
if (isShow == 0){
cbLeft.setVisibility(View.GONE);
cbRight.setVisibility(View.GONE);
}else if (isShow == 1){
cbLeft.setVisibility(View.VISIBLE);
cbRight.setVisibility(View.GONE);
}else if (isShow == 2){
cbLeft.setVisibility(View.GONE);
cbRight.setVisibility(View.VISIBLE);
}
// 設(shè)置CheckBox的樣式
if (checkboxResID != 0) {
cbLeft.setButtonDrawable(checkboxResID);
cbRight.setButtonDrawable(checkboxResID);
}
// 應(yīng)反射創(chuàng)建viewmodel對(duì)象
try {
Class<?> myClass = Class.forName(vmName);//完整類名
Class[] paramTypes = { Context.class };
Object[] params = {context};
Constructor con = myClass.getConstructor(paramTypes);
viewmodel = (BaseViewModel) con.newInstance(params);
flContent.addView(viewmodel.getContentView());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
public void setDataToView() {
super.setDataToView();
// 處理選擇框的點(diǎn)擊 , 只做數(shù)據(jù)展示,邏輯放在外邊處理
cbLeft.setChecked(data.leftCheck);
cbRight.setChecked(data.rightCheck);
//為viewmodel添加數(shù)據(jù)
viewmodel.setData(data);
}
/**
* 接下來(lái)做 單選 和 多選 的操作 todo 先測(cè)試單選看看能不能用
*/
@OnClick({R.id.cb_left,R.id.cb_right})
public void itemClick(View v){
switch (v.getId()){
case R.id.cb_left:
if (isRadio) {
// 單選
Observable.just(getAdapterPosition()).subscribe(observer1);
}else {
// 多選
Observable.just(getAdapterPosition()).subscribe(observer2);
}
break;
case R.id.cb_right:
if (isRadio) {
Observable.just(getAdapterPosition()).subscribe(observer1);
}else {
Observable.just(getAdapterPosition()).subscribe(observer2);
}
break;
}
}
}
/**
* 定義兩個(gè)數(shù)據(jù)來(lái)記錄是否點(diǎn)擊選擇框
* 規(guī)則:調(diào)用封裝的數(shù)據(jù)類要繼承這個(gè)類
*/
public static class CheckRecyclerEntity{
public boolean leftCheck;
public boolean rightCheck;
}
/**
* =======================================================================
* Builder方法
* ========================================================================
*/
public static class Builder<T extends CheckRecyclerView.CheckRecyclerEntity>{
private Context context;
private int isShow = 1; // 默認(rèn)為顯示左邊的
private String vmName;
private List<T> datalist;
//RecyclerView相關(guān)
private RecyclerView.ItemDecoration itemDecoration;
private RecyclerView.LayoutManager layoutManager;
private int backgroupColorResID; //RecyclerView背景顏色,默認(rèn)為白色
// checkbox相關(guān)
private int checkboxResID = 0;
private boolean isRadio = true;// 定義單選(true)和多選(true)
private RadioObserver radioObserver; // 監(jiān)聽(tīng)單選
private MultiObserver multiObserver; // 監(jiān)聽(tīng)多選
// 這個(gè)一定要傳完整 包名+類名
public Builder(Context context,String vmName){
this.context = context;
this.vmName = vmName;
layoutManager = new LinearLayoutManager(context);
backgroupColorResID = R.color.white;
}
public Builder setIsShow(int isShow) {
this.isShow = isShow;
return this;
}
public Builder setDatalist(List<T> datalist) {
this.datalist = datalist;
return this;
}
public Builder setItemDecoration(RecyclerView.ItemDecoration itemDecoration) {
this.itemDecoration = itemDecoration;
return this;
}
public Builder setLayoutManager(RecyclerView.LayoutManager layoutManager) {
this.layoutManager = layoutManager;
return this;
}
public Builder setCheckboxResID(int checkboxResID) {
this.checkboxResID = checkboxResID;
return this;
}
public Builder setBackgroupColorResID(int backgroupColorResID) {
this.backgroupColorResID = backgroupColorResID;
return this;
}
public Builder setIsRadio(boolean radio) {
isRadio = radio;
return this;
}
public Builder setRadioObserver(RadioObserver radioObserver) {
this.radioObserver = radioObserver;
return this;
}
public Builder setMultiObserver(MultiObserver multiObserver) {
this.multiObserver = multiObserver;
return this;
}
public CheckRecyclerView builder(){
return new CheckRecyclerView(context,this);
}
}
/**
* 單選時(shí)的響應(yīng)
*/
public interface RadioObserver{
public void radioClick(int position,int isShow);
}
/**
* 多選時(shí)的響應(yīng)
*/
public interface MultiObserver{
public void multiClick(int position,int isShow);
}
}
代碼也不能說(shuō)長(zhǎng),500行這樣,其實(shí)還好。也只封裝了一些基本的功能操作,之后還可以擴(kuò)展。其實(shí)我還算挺良心的,加了很多注解,我個(gè)人習(xí)慣先寫(xiě)注解再寫(xiě)方法。
基類的XXXRecyclerViewBaseAdapter和XXXRecyclerViewHolder是我自己寫(xiě)的Adapter和ViewHolder的基類
1.基本的流程
我有注解也不一行一行解釋,主要是用了Builder模式,在外邊使用時(shí)可以addView,用自定義寫(xiě)XML控件也行。
定義單選和多選的規(guī)則:
(1)protected static int isShow; 0 表示都不顯示,1表示顯示左邊,2表示顯示右邊,默認(rèn)為1
(2)protected static boolean isRadio; 表示單選/多選,true表示單選,false表示多選
定義RecyclerView
private void initView(){
recyclerView = new RecyclerView(context);
recyclerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
recyclerView.setBackgroundResource(builder.backgroupColorResID);
recyclerView.setLayoutManager(builder.layoutManager);
if (builder.itemDecoration != null) {
recyclerView.addItemDecoration(builder.itemDecoration);
}
}
數(shù)據(jù)要繼承
public static class CheckRecyclerEntity{
public boolean leftCheck;
public boolean rightCheck;
}
表示左邊是否點(diǎn)擊和右邊是否點(diǎn)擊
最關(guān)鍵是ViewHolder的兩個(gè)方法:
(1)initView
public CheckRecyclerViewHolder(View itemView, Context context) {
super(itemView, context);
ButterKnife.inject(this,itemView);
if (isShow == 0){
cbLeft.setVisibility(View.GONE);
cbRight.setVisibility(View.GONE);
}else if (isShow == 1){
cbLeft.setVisibility(View.VISIBLE);
cbRight.setVisibility(View.GONE);
}else if (isShow == 2){
cbLeft.setVisibility(View.GONE);
cbRight.setVisibility(View.VISIBLE);
}
// 設(shè)置CheckBox的樣式
if (checkboxResID != 0) {
cbLeft.setButtonDrawable(checkboxResID);
cbRight.setButtonDrawable(checkboxResID);
}
// 應(yīng)反射創(chuàng)建viewmodel對(duì)象
try {
Class<?> myClass = Class.forName(vmName);//完整類名
Class[] paramTypes = { Context.class };
Object[] params = {context};
Constructor con = myClass.getConstructor(paramTypes);
viewmodel = (BaseViewModel) con.newInstance(params);
flContent.addView(viewmodel.getContentView());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
首先判斷isShow 是單選/多選/隱藏 來(lái)展示頁(yè)面。然后設(shè)置CheckBox的樣式。
最后創(chuàng)建ViewModel對(duì)象添加到最上邊圖中實(shí)線的區(qū)域中。但是我們展示的頁(yè)面并不是固定的,也就是說(shuō)我們會(huì)在不同的情況創(chuàng)建不同的viewmodel對(duì)象,所以這里不能直接寫(xiě),我用了反射來(lái)添加viewmodel,所以在上邊調(diào)用的地方傳了viewmodel的包名+類名,是為了用類名創(chuàng)建相應(yīng)的viewmodel對(duì)象添加到區(qū)域中。
(2)setDataToView
@Override
public void setDataToView() {
super.setDataToView();
// 處理選擇框的點(diǎn)擊 , 只做數(shù)據(jù)展示,邏輯放在外邊處理
cbLeft.setChecked(data.leftCheck);
cbRight.setChecked(data.rightCheck);
//為viewmodel添加數(shù)據(jù)
viewmodel.setData(data);
}
這個(gè)就很簡(jiǎn)單了,根據(jù)leftCheck和rightCheck的值設(shè)置左右checkbox的展示,然后給viewmodel設(shè)置數(shù)據(jù)。
2.點(diǎn)擊時(shí)的處理
做完展示時(shí)的處理之后就要做點(diǎn)擊時(shí)的邏輯處理。
@OnClick({R.id.cb_left,R.id.cb_right})
public void itemClick(View v){
switch (v.getId()){
case R.id.cb_left:
if (isRadio) {
// 單選
Observable.just(getAdapterPosition()).subscribe(observer1);
}else {
// 多選
Observable.just(getAdapterPosition()).subscribe(observer2);
}
break;
case R.id.cb_right:
if (isRadio) {
Observable.just(getAdapterPosition()).subscribe(observer1);
}else {
Observable.just(getAdapterPosition()).subscribe(observer2);
}
break;
}
}
先判斷點(diǎn)左還是點(diǎn)右,再判斷是單選模式還是多選模式,我這里用了觀察者模式。
// 單選情況下的觀察者
private static Action1<Integer> observer1 = new Action1<Integer>() {
@Override
public void call(Integer integer) {
if (adapter != null){
adapter.checkChange(integer);
}
}
};
// 多選情況下的觀察者
private static Action1<Integer> observer2 = new Action1<Integer>() {
@Override
public void call(Integer integer) {
if (adapter != null){
adapter.multiChose(integer);
}
}
};
點(diǎn)擊之后會(huì)在adapter中做響應(yīng)操作。
/**
* 單選
*/
public void checkChange(int position){
if (position < dataSource.size()) {
// 先將所有都設(shè)為未點(diǎn)擊
for (int i = 0; i < dataSource.size(); i++) {
dataSource.get(i).leftCheck = false;
dataSource.get(i).rightCheck = false;
}
// 再設(shè)置點(diǎn)擊的Item
if (isShow == 1) {
dataSource.get(position).leftCheck = true;
leftRadio = position;
}else if (isShow == 2){
dataSource.get(position).rightCheck = true;
rightRadio = position;
}
// 做響應(yīng)
if (radioObserver != null) {
radioObserver.radioClick(position, isShow);
}
this.notifyDataSetChanged();
}
}
/**
* 多選
*/
public void multiChose(int position){
// 點(diǎn)擊后展示相反的情況
if (isShow == 1) {
dataSource.get(position).leftCheck = !dataSource.get(position).leftCheck;
}else if (isShow == 2){
dataSource.get(position).rightCheck = !dataSource.get(position).rightCheck;
}
// 做響應(yīng)
if (multiObserver != null) {
multiObserver.multiClick(position, isShow);
}
this.notifyDataSetChanged();
}
注釋都有,算法也不難,不用詳細(xì)去講了。主要封裝的邏輯其實(shí)也就這些,而其他的方法基本都是和外邊交互時(shí)用到的方法,我都寫(xiě)了注釋。
比如
/**
* 外部控制內(nèi)部進(jìn)行單選的操作
*/
public void setRadio(int position){
if (adapter != null){
adapter.checkChange(position);
}
}
這個(gè)就是外面要求里面單選哪個(gè)。比如說(shuō)有些需求是要默認(rèn)選第一個(gè),所以可以在外面調(diào)用checkRecyclerView.setRadio(0);
/**
* 設(shè)置全選/全不選
* 規(guī)則:true表示全選 false表示全不選
*/
public void setAllMulti(Boolean bol){
if (adapter != null){
adapter.setAllMulti(bol);
}
}
這是設(shè)置全選,比如外面點(diǎn)擊哪個(gè)按鈕后里面顯示全選,那就在外面調(diào)用checkRecyclerView.setAllMulti(true);
其它的什么我都加了注釋,就不都說(shuō)了。
3.Builder的屬性
簡(jiǎn)單介紹在Builder中定義的屬性吧
(1)isShow 顯示選擇框在左還是右
(2)datalist需要適配的數(shù)據(jù)
(3)itemDecoration RecyclerView的分割線
(4)layoutManager RecyclerView的布局
(5)backgroupColorResID RecyclerView的顏色
(6)checkboxResID Checkbox的樣式ID
(7)isRadio 單選還是多選
(8)radioObserver 單選的監(jiān)聽(tīng),要實(shí)現(xiàn)這個(gè)接口
(9)multiObserver 多選的監(jiān)聽(tīng)
(10)vmName viewmodel的包名+類名
注意要傳包名+類名,只傳類名不行嗎?不行,因?yàn)椴煌陌驴梢杂邢嗤念惷?,我怎么懂是要哪個(gè)。
其實(shí)我覺(jué)得代碼也不算多,也不難理解,最近沒(méi)時(shí)間也沒(méi)能寫(xiě)個(gè)demo放到github上,之后閑下來(lái)再弄吧。然后也沒(méi)經(jīng)過(guò)嚴(yán)格的測(cè)試,可能一些地方寫(xiě)得不是很好。
更新
后來(lái)我用這個(gè)東西發(fā)現(xiàn)一個(gè)錯(cuò)的地方,就是內(nèi)部類和成員都不要使用static,不然在多處使用的時(shí)候會(huì)出錯(cuò)。當(dāng)初習(xí)慣性的會(huì)下意思使用靜態(tài)內(nèi)部類,后來(lái)發(fā)現(xiàn)用著用著就有問(wèn)題了。還是太粗心了。
把代碼中的靜態(tài)內(nèi)部類和靜態(tài)成員改成非靜態(tài)的就行。