實(shí)現(xiàn)單選及多選的選擇對(duì)話框

先看效果圖:


pick.png

思路:

使用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ù)用的麻煩德迹,也不必處理前面??兩者的事件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市揭芍,隨后出現(xiàn)的幾起案子胳搞,更是在濱河造成了極大的恐慌,老刑警劉巖称杨,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肌毅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡姑原,警方通過查閱死者的電腦和手機(jī)悬而,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)锭汛,“玉大人笨奠,你說(shuō)我怎么就攤上這事』脚梗” “怎么了般婆?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)朵逝。 經(jīng)常有香客問我蔚袍,道長(zhǎng),這世上最難降的妖魔是什么配名? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任啤咽,我火速辦了婚禮,結(jié)果婚禮上渠脉,老公的妹妹穿的比我還像新娘宇整。我一直安慰自己,他們只是感情好芋膘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布鳞青。 她就那樣靜靜地躺著涩哟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盼玄。 梳的紋絲不亂的頭發(fā)上贴彼,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音埃儿,去河邊找鬼器仗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛童番,可吹牛的內(nèi)容都是我干的精钮。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼剃斧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼轨香!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起幼东,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤臂容,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后根蟹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脓杉,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年简逮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了球散。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡散庶,死狀恐怖蕉堰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悲龟,我是刑警寧澤屋讶,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站躲舌,受9級(jí)特大地震影響丑婿,放射性物質(zhì)發(fā)生泄漏性雄。R本人自食惡果不足惜没卸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秒旋。 院中可真熱鬧约计,春花似錦、人聲如沸迁筛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至尉桩,卻和暖如春筒占,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜘犁。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工翰苫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人这橙。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓奏窑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親屈扎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子埃唯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容