自定義組合控件:SherlockSpinner

如果你覺(jué)得從頭開(kāi)始自定義一個(gè)View比較麻煩,那么組合幾個(gè)系統(tǒng)現(xiàn)有控件來(lái)實(shí)現(xiàn)需求的功能徒恋,會(huì)是你很好的一個(gè)選擇。

一欢伏、前言

最近在項(xiàng)目中入挣,需要使用Spinner來(lái)實(shí)現(xiàn)下拉選擇功能,UI方面倒是要求不多硝拧。但是難點(diǎn)在于一個(gè)界面中有多個(gè)Spinner径筏,并且有聯(lián)動(dòng)關(guān)系葛假,數(shù)據(jù)需要在點(diǎn)擊Spinner的時(shí)候請(qǐng)求服務(wù)器。而且當(dāng)前Spinner數(shù)據(jù)依賴于前面選擇的一個(gè)或多個(gè)結(jié)果滋恬,當(dāng)獲取到最新數(shù)據(jù)后聊训,才顯示下拉選項(xiàng)。

比如說(shuō)我需要先選擇一個(gè)倉(cāng)庫(kù)恢氯,再選擇項(xiàng)目(依賴前面選擇的倉(cāng)庫(kù))魔眨,再選擇一個(gè)批次(依賴前面選擇的倉(cāng)庫(kù)和項(xiàng)目),如果我在選擇完倉(cāng)庫(kù)時(shí)酿雪,就去判斷來(lái)預(yù)加載項(xiàng)目和批次的數(shù)據(jù)遏暴,會(huì)使依賴邏輯變得非常復(fù)雜。

這時(shí)候就想在每次點(diǎn)擊一個(gè)Spinner的時(shí)候指黎,去判斷依賴的選項(xiàng)是否已經(jīng)選擇朋凉,未選擇就提示需先選擇;如果已選擇醋安,則進(jìn)行網(wǎng)絡(luò)請(qǐng)求杂彭,加載數(shù)據(jù)顯示到下拉選項(xiàng)中。

(本例使用選擇語(yǔ)言來(lái)進(jìn)行演示吓揪。)

二亲怠、使用系統(tǒng)Spinner

1. 首先,我想到的便是使用系統(tǒng)的Spinner柠辞,說(shuō)干就干团秽,XML先上:

<Spinner
    android:id="@+id/spn_languages"
    android:layout_width="match_parent"
    android:layout_height="36dp"/>

2. 然后代碼設(shè)置Adapter匹配數(shù)據(jù)、設(shè)置OnItemSelectedListener綁定item選擇的事件:

spnLanguages = (Spinner) findViewById(R.id.spn_languages);
ArrayAdapter<String> mAdapterSystemSpinner = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mLanguages);
spnLanguages.setAdapter(mAdapterSystemSpinner);
spnLanguages.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        showMessage("Select " + mLanguages[position]);
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        showMessage("Select Nothing.");
    }
});

OK叭首,現(xiàn)在Spinner可以使用了:

3. 接著我們來(lái)設(shè)置點(diǎn)擊事件

spnLanguages.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        showMessage("Click Spinner");
    }
});

什么情況习勤,運(yùn)行后直接崩了?

FATAL EXCEPTION: main
Process: com.sherlockshi.widget.sherlockspinner, PID: 11757
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.sherlockshi.widget.sherlockspinner/com.sherlockshi.widget.MainActivity}: java.lang.RuntimeException: Don't call setOnClickListener for an AdapterView. You probably want setOnItemClickListener instead
   ...
Caused by: java.lang.RuntimeException: Don't call setOnClickListener for an AdapterView. You probably want setOnItemClickListener instead
   at android.widget.AdapterView.setOnClickListener(AdapterView.java:798)
   at com.sherlockshi.widget.MainActivity.initSystemSpinner(MainActivity.java:169)
   at com.sherlockshi.widget.MainActivity.onCreate(MainActivity.java:48)
   ...

簡(jiǎn)單來(lái)說(shuō)就是焙格,AdapterView不能設(shè)置Click事件图毕,看下Spinner源碼,確實(shí)是繼承自AdapterView眷唉。(至于為什么AdapterView不能設(shè)置Click事件予颤,這個(gè)暫未深究。)

4. 那我們就設(shè)置Touch事件嘍:

spnLanguages.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                showMessage("Touch Spinner");
                break;
        }
        return false;
    }
});

這下確實(shí)是可以響應(yīng)點(diǎn)擊事件了冬阳,但是響應(yīng)完蛤虐,下拉選擇就直接顯示出來(lái)了,無(wú)法滿足我們的需求摩泪。

而且Spinner還有個(gè)問(wèn)題笆焰,一進(jìn)入界面的時(shí)候,默認(rèn)就會(huì)選擇第一項(xiàng)见坑,而我們并不需要這樣的默認(rèn)值嚷掠。

二捏检、CustomSpinner = EditText + ListPopupWindow

那我們是否可以使用別的控件,來(lái)實(shí)現(xiàn)相同的功能呢不皆?答案是肯定的贯城,我們用EditText來(lái)接收點(diǎn)擊事件,而在請(qǐng)求完數(shù)據(jù)之后霹娄,使用ListPopupWindow來(lái)顯示下拉選項(xiàng)能犯,選擇EditText的原因主要有以下幾點(diǎn):

  • 默認(rèn)在底部會(huì)有帶顏色的橫線,Material Design風(fēng)格的EditText看起來(lái)效果很不賴
  • EditText可以方便的配置上犬耻、下踩晶、左、右四個(gè)位置的小圖標(biāo)枕磁,我們可以在右側(cè)放置一個(gè)向下的三角箭頭渡蜻,使它看起來(lái)像一個(gè)系統(tǒng)的Spinner

而選擇ListPopupWindow則是因?yàn)椋?/p>

  • 可以方便的使用下拉列表
  • 可以自由設(shè)置錨點(diǎn)

1. XML布局

布局文件依舊很簡(jiǎn)單,只要一個(gè)簡(jiǎn)單的EditText计济,配上一個(gè)右側(cè)的下拉圖標(biāo):

<EditText
    android:id="@+id/et_languages"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="right"
    android:drawableRight="@drawable/ic_dropdown"
    android:hint="Please Select..."/>

2. 代碼中創(chuàng)建ListPopupWindow

此部分包含以下邏輯:

  • 初始化ListPopupWindow茸苇,并關(guān)聯(lián)到EditText上
  • 當(dāng)點(diǎn)擊EditText時(shí),請(qǐng)求數(shù)據(jù)沦寂,請(qǐng)求完成后学密,顯示ListPopupWindow
  • 選中ListPopupWindow的某一項(xiàng)后,將此項(xiàng)內(nèi)容更新到EditText中传藏,并隱藏ListPopupWindow
etLanguages = (EditText) findViewById(R.id.et_languages);
etLanguages.setKeyListener(null); // 設(shè)置EditText不可編輯腻暮,等同于在xml中設(shè)置editable="false"
lpwLanguages = new ListPopupWindow(this);
mAdapterLanguages = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mLanguages);
lpwLanguages.setAdapter(mAdapterLanguages);
lpwLanguages.setAnchorView(etLanguages); //設(shè)置ListPopupWindow的錨點(diǎn),即關(guān)聯(lián)PopupWindow的顯示位置
lpwLanguages.setModal(true); // 是否為模態(tài)漩氨,當(dāng)設(shè)置為true時(shí)西壮,會(huì)處理返回按鍵的事件

lpwLanguages.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        showMessage("Select " + mLanguages[position]);

        etLanguages.setText(mLanguages[position]);
        lpwLanguages.dismiss();
    }
});

// 如果使用onClick事件遗增,會(huì)出現(xiàn)第一次點(diǎn)擊只獲取焦點(diǎn)叫惊,第二次點(diǎn)擊才出現(xiàn)下拉
etLanguages.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            // Do what you want
            getDataFromNet();
        }
        return false;
    }
});

public void getDataFromNet() {
    // 延時(shí)2秒后,修改源數(shù)據(jù)做修,用來(lái)模擬網(wǎng)絡(luò)請(qǐng)求
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            mLanguages[1] = "C+++++++++++++";
            mAdapterLanguages.notifyDataSetChanged();

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    lpwLanguages.show();
                }
            });
        }
    }).start();
}

實(shí)現(xiàn)效果如下所示:

3. 小結(jié)

EditText配合ListPopupWindow組合實(shí)現(xiàn)Spinner的功能霍狰,使用起來(lái)倒是簡(jiǎn)單,邏輯也挺清晰的饰及,但是如果界面上有三四個(gè)Spinner蔗坯,那不是就得把類似的代碼寫上三四遍?

其實(shí)我們并不關(guān)心內(nèi)部是用ListPopupWindow或者其它的控件來(lái)實(shí)現(xiàn)燎含,也不想處理任何關(guān)于ListPopupWindow的細(xì)節(jié)宾濒。我們關(guān)心的只有Spinner的初始化、適配數(shù)據(jù)屏箍、Item選擇事件(ItemClick事件)绘梦,如果可以橘忱,就再加上Spinner的點(diǎn)擊事件(Click或Touch事件)、自由控制Spinner的顯示時(shí)機(jī)卸奉。

那有沒(méi)有簡(jiǎn)單易用的方法钝诚,可以直接像使用系統(tǒng)的Spinner一樣,來(lái)使用EditText和ListPopupWindow的組合呢榄棵?并且可以提供Spinner的點(diǎn)擊事件凝颇?答案是肯定的,詳情且看下一節(jié)疹鳄。

三拧略、自定義組合控件

在第二部分我們可以看出,我們的控件要滿足以下兩大功能:

  1. 像系統(tǒng)Spinner一樣簡(jiǎn)單易用:初始化瘪弓、適配數(shù)據(jù)辑鲤、Item選擇事件(ItemClick事件)
  2. 支持點(diǎn)擊事件(Click或Touch事件),自由控制下拉框顯示時(shí)機(jī)

由于控件源碼稍長(zhǎng)杠茬,就不貼出來(lái)了月褥,有興趣可以點(diǎn)擊文末的Github鏈接,源碼也比較簡(jiǎn)單瓢喉,只是進(jìn)行控件的組合宁赤,并提供相應(yīng)的方法進(jìn)行調(diào)用,下面主要介紹下使用方法栓票。

1. 引入依賴

dependencies {
    ...
    compile 'com.sherlockshi.widget:sherlockspinner:1.0.2'
}

2. 使用方法

2.1 像使用系統(tǒng)Spinner一樣决左,在XML文件中使用:

<com.sherlockshi.widget.SherlockSpinner
    android:id="@+id/sherlock_spinner"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:lineColor="#00FF00"
    android:hint="Please Select..."/>

SherlockSpinner有以下屬性:
lineColor: 設(shè)置底部橫線的顏色
同時(shí)支持使用代碼進(jìn)行配置:mSherlockSpinner.setLineColor(0x00FF00);

由于SherlockSpinner繼承自EditText, 所以你可以使用EditText的其它屬性,例如gravity走贪、textSize佛猛、textColor...

2.2 還是像使用系統(tǒng)Spinner一樣,在代碼中設(shè)置AdapterItemClickListener:

mSherlockSpinner = (SherlockSpinner) findViewById(R.id.sherlock_spinner);
ArrayAdapter<String> mAdapterLanguages = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mLanguages);
mSherlockSpinner.setAdapter(mAdapterLanguages);
mSherlockSpinner.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        showMessage("Select " + mLanguages[position]);
    }
});

以上坠狡,就可以像系統(tǒng)Spinner一樣使用SherlockSpinner了继找,如果沒(méi)有別的需求,這樣也就夠用了逃沿。如果你有點(diǎn)擊請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)婴渡,再異步顯示下拉框的需求,可以看第3步的使用方法凯亮。

2.3 (可選) 如果你想在異步加載數(shù)據(jù)后边臼,再顯示出更新后的數(shù)據(jù),你可以使用它的點(diǎn)擊事件來(lái)處理

記准傧:在獲取數(shù)據(jù)后柠并,你必須手動(dòng)調(diào)用sherlockSpinner.show()方法來(lái)顯示SherlockSpinner的下拉選項(xiàng)

mSherlockSpinner.setOnClickListener(new SherlockSpinner.OnClickListener() {
    @Override
    public void onClick(View v) {
        getDataFromNet();
    }
});

public void getDataFromNet() {
    // after delay 2s, modify the source data, to simulate net request
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            mLanguages[4] = "Javaaaaaaaaaaa";
            mAdapterLanguages.notifyDataSetChanged();

            // then you must manually call sherlockSpinner.show()
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mSherlockSpinner.show();
                }
            });
        }
    }).start();
}

3. 更多屬性

  • 由于SherlockSpinner繼承自EditText,所以你可以使用EditText的其它屬性,例如gravity臼予、textSize亿傅、textColor...
  • SherlockSpinner還有一個(gè)屬性,可以設(shè)置下拉框的顯示位置瘟栖,即錨點(diǎn)設(shè)置:
mSherlockSpinner.setAnchorView(findViewById(R.id.llyt_anchor));

效果如下圖中4和5的區(qū)別葵擎,在第4部分中,下拉框桶胗矗靠在Spinner上酬滤;而第5部分中,下拉框驮⒄牵靠在Spinner所在的整行布局上盯串,寬度更大。

四戒良、其它

另外体捏,一個(gè)小小的Tip:
在styles.xml文件中配置以下代碼,可使下拉框帶上漂亮的分割線:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    ...
    <item name="android:dropDownListViewStyle">@style/mySpinnerStyle</item>
</style>

<style name="mySpinnerStyle" parent="android:Widget.ListView.DropDown">
    <item name="android:divider">#E0E0E0</item>
    <item name="android:dividerHeight">0.1dp</item>
</style>

項(xiàng)目代碼已共享到Github:SherlockSpinner
歡迎fork糯崎、star几缭、issue。

PS:歡迎關(guān)注SherlockShi博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沃呢,一起剝皮案震驚了整個(gè)濱河市年栓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌薄霜,老刑警劉巖某抓,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惰瓜,居然都是意外死亡否副,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門崎坊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)备禀,“玉大人,你說(shuō)我怎么就攤上這事流强”越欤” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵打月,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蚕捉,道長(zhǎng)奏篙,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮秘通,結(jié)果婚禮上为严,老公的妹妹穿的比我還像新娘。我一直安慰自己肺稀,他們只是感情好第股,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著话原,像睡著了一般夕吻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上繁仁,一...
    開(kāi)封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天涉馅,我揣著相機(jī)與錄音,去河邊找鬼黄虱。 笑死稚矿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捻浦。 我是一名探鬼主播晤揣,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼右冻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼险绘!你這毒婦竟也來(lái)了讳癌?” 一聲冷哼從身側(cè)響起凫海,我...
    開(kāi)封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤柔昼,失蹤者是張志新(化名)和其女友劉穎误续,沒(méi)想到半個(gè)月后这刷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體百匆,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡环疼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年习霹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炫隶。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淋叶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伪阶,到底是詐尸還是另有隱情煞檩,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布栅贴,位于F島的核電站斟湃,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏檐薯。R本人自食惡果不足惜凝赛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一注暗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧墓猎,春花似錦捆昏、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至左胞,卻和暖如春寇仓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背罩句。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工焚刺, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人门烂。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓乳愉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親屯远。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蔓姚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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

  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,754評(píng)論 22 665
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,071評(píng)論 25 707
  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程,因...
    小菜c閱讀 6,397評(píng)論 0 17
  • 都說(shuō)這個(gè)時(shí)候的日子是我們最美好的時(shí)候慨丐,我們?nèi)蘸蟊貢?huì)懷念坡脐,可是現(xiàn)在的我,感覺(jué)好難熬房揭,每天自殺這個(gè)念頭無(wú)數(shù)遍從腦中浮現(xiàn)...
    花橙君閱讀 174評(píng)論 0 0
  • 安安靜靜地從內(nèi)心發(fā)出聲音 讓自己健健康康的 美美的滋養(yǎng)
    心源寶貝閱讀 267評(píng)論 0 0