实现京东商城地址选择效果(效果还挺一致的)
前言部分
最近新項(xiàng)目要中設(shè)計(jì)一個(gè)地址選擇的效果(效果和某東的商城一樣一樣的),雖然網(wǎng)上已經(jīng)有現(xiàn)成的方案了,但是最近剛好時(shí)間還算充裕所以想了想還是準(zhǔn)備自己來(lái)做一個(gè),順便加深一下對(duì)自定義dialog的認(rèn)識(shí)。
Demo中主要實(shí)現(xiàn)了京東的地址選擇的控件效果,開(kāi)始本來(lái)計(jì)劃用dialog來(lái)實(shí)現(xiàn)的,但是網(wǎng)上似乎說(shuō)推薦使用DialogFragment來(lái)封裝,剛好我還沒(méi)使用過(guò)DialogFragment來(lái)做一個(gè),說(shuō)是封裝其實(shí)只是定義了一個(gè)AddressSelectDialog對(duì)外提供了一些基礎(chǔ)的方法,如果需要使用可以自行修改。
放個(gè)效果圖鎮(zhèn)樓
內(nèi)容部分
寫(xiě)一個(gè)功能的基本流程:構(gòu)思5分,代碼3分,完善2分。
因?yàn)橐郧拔覍?xiě)功能都是上來(lái)就開(kāi)寫(xiě),一口氣寫(xiě)個(gè)差不多完事,可是完成后你會(huì)發(fā)現(xiàn)寫(xiě)的很混亂,因?yàn)閷?xiě)之前沒(méi)有很好的構(gòu)思項(xiàng)目,這樣看似效率很高,但是后來(lái)你回來(lái)改的時(shí)間會(huì)很多,這也就是為什么軟件開(kāi)發(fā)一直都是強(qiáng)調(diào)架構(gòu)的原因吧。
進(jìn)入正題
其實(shí)這個(gè)地址選擇權(quán)很以前的三級(jí)聯(lián)動(dòng)一樣不過(guò)是樣式上不同,個(gè)人覺(jué)得還是比較舒服的。
功能分析拆解一下,做起來(lái)更有條不紊。
我們首先需要頭部三個(gè)tab,這三個(gè)tab也不是一個(gè)固定的可能發(fā)生增減。
下方上一個(gè)地址的列表,省–市--區(qū)。
省市區(qū)的列表是有關(guān)聯(lián)關(guān)系的,但是實(shí)際上同一時(shí)間只會(huì)有一個(gè)列表出現(xiàn)。
具體的實(shí)現(xiàn)過(guò)程
第一部分是頭部這里我使用了design包中的TabLayout來(lái)實(shí)現(xiàn),用起來(lái)還是比較舒服,下面看一下布局和相關(guān)代碼:
<android.support.design.widget.TabLayoutandroid:id="@+id/tb_head_indicator"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"app:tabIndicatorColor="@color/red"app:tabSelectedTextColor="@color/red" />上面的布局匯總很簡(jiǎn)單,這里說(shuō)明一下注意地方:
- 其實(shí)你觀(guān)察源碼會(huì)發(fā)現(xiàn)TabLayout是繼承了HorizontalScrollView類(lèi),這也是可以滾動(dòng)的原因所在。然后tabIndicatorColor是設(shè)置下面的指示標(biāo)的顏色,tabSelectedTextColor設(shè)置選中文字的顏色。這里沒(méi)有什么特殊的樣式,其實(shí)TabLayout的功能還是很強(qiáng)大的,基本上你所需要的實(shí)現(xiàn)樣式都提供了。
下面就是一個(gè)列表了啊,這個(gè)地方我們發(fā)現(xiàn)同時(shí)出現(xiàn)的只有一個(gè)列表,雖然是說(shuō)省市區(qū)三個(gè)類(lèi)表。這里的方案我想到三種:
-
一個(gè)是分別弄三個(gè)RecyclerView(或者ListView)來(lái)實(shí)現(xiàn),然后重疊在這個(gè)布局中,通過(guò)標(biāo)記來(lái)設(shè)置顯示哪一個(gè)RecyclerView,這個(gè)方案的好處就是只需要初始化一次就可以了,并且選中的狀態(tài)也可以直接保留在列表中。
-
一個(gè)是使用一個(gè)RecyclerView并給初始化三個(gè)不同的Adapter來(lái)實(shí)現(xiàn),這個(gè)方案使得RecyclerView一只在界面中存在,通過(guò)給RecyclerView綁定不同的RecyclerView來(lái)實(shí)現(xiàn)切換。本Demo就是通過(guò)實(shí)現(xiàn)這個(gè)方案來(lái)做的。
-
這種就是RecyclerView和RecyclerView用同一個(gè)通過(guò)切換tab來(lái)更換里面的數(shù)據(jù)來(lái)做,其實(shí)這個(gè)和上面的很像,上面的方案也很容易改成這樣。
以上我覺(jué)得都還好吧,寫(xiě)起來(lái)難以程度略不同,第2和第3 差不多,主要的邏輯都在通過(guò)標(biāo)識(shí)位不斷的更新數(shù)據(jù)上,唯一的區(qū)別就是一個(gè)更新數(shù)據(jù)源,一個(gè)直接把適配器換掉了;第一個(gè)寫(xiě)起來(lái)應(yīng)該算是比較簡(jiǎn)單,因?yàn)楠?dú)立出來(lái)三個(gè)不同的列表,只第一次把數(shù)據(jù)初始化進(jìn)去就好了,后期只通過(guò)標(biāo)識(shí)位來(lái)切換顯示就可以。
下面貼出部分代碼片段:
//初始化了三個(gè)adapter,先把第一個(gè)省的adapter綁定到RecyclerView上了。 private void initRecyclerView() {rvTabExample.setLayoutManager(new LinearLayoutManager(getContext()));rvTabExample.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));//省地址recyclerViewTabAdapter = new RecyclerViewTabAdapter(getContext());//市地址recyclerViewCityAdapter = new RecyclerViewCityAdapter(getContext());//區(qū)地址recyclerViewAreaAdapter = new RecyclerViewAreaAdapter(getContext());recyclerViewTabAdapter.setAddressList(cityBeanList);recyclerViewTabAdapter.setBaseItemClickListener(this);recyclerViewCityAdapter.setBaseItemClickListener(this);recyclerViewAreaAdapter.setBaseItemClickListener(this);rvTabExample.setAdapter(recyclerViewTabAdapter);}處理adapter更新的代碼部分,主要通過(guò)標(biāo)記tabCurrentPosition當(dāng)前顯示的tab來(lái)區(qū)分顯示,不同tab下adapter傳入的類(lèi)型不同,這里如果使用同一個(gè)的話(huà)需要在adapter的內(nèi)部來(lái)進(jìn)行判斷了,或者在外面把數(shù)據(jù)做統(tǒng)一處理,傳入adapter的都統(tǒng)一一下。
private void upDataArray() {if (tabCurrentPosition == oldTabCurrentPosition) {return;}//設(shè)置顏色LogUtil.i("upDataArray--更新列表啊:::" + tabCurrentPosition);switch (tabCurrentPosition) {case 0:recyclerViewTabAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition));recyclerViewTabAdapter.setAddressList(cityBeanList);rvTabExample.setAdapter(recyclerViewTabAdapter);break;case 1:cityIndex = currentSelectMap.get(0) == -1 ? initPosition : currentSelectMap.get(0);recyclerViewCityAdapter.setAddressList(cityBeanList.get(cityIndex).getCity());recyclerViewCityAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition));rvTabExample.setAdapter(recyclerViewCityAdapter);break;case 2:cityIndex = currentSelectMap.get(0) == -1 ? initPosition : currentSelectMap.get(0);int cityInnerIndex = currentSelectMap.get(1) == -1 ? initPosition : currentSelectMap.get(1);recyclerViewAreaAdapter.setAddressList(cityBeanList.get(cityIndex).getCity().get(cityInnerIndex).getArea());recyclerViewAreaAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition));rvTabExample.setAdapter(recyclerViewAreaAdapter);break;default:break;}oldTabCurrentPosition = tabCurrentPosition;}因?yàn)槁?lián)動(dòng)的關(guān)系,所以每次點(diǎn)擊tab或者點(diǎn)擊item的時(shí)候都會(huì)出發(fā)更新的操作。
這里是比較關(guān)鍵的部分,主要是處理了點(diǎn)擊事件,當(dāng)點(diǎn)擊事件發(fā)生的時(shí)候要伴隨著tab的切換操作。
這里我們有一個(gè)標(biāo)記的當(dāng)前tab的標(biāo)識(shí)tabCurrentPosition,并且我會(huì)把每一個(gè)列表里選中的position存儲(chǔ)到map中,并且使用tabCurrentPosition當(dāng)作key,這樣在切換tab回顯的時(shí)候就方便一些,這里以前我使用過(guò)List但是效果不好,因?yàn)椴粩嗲袚QTab的話(huà)還要去重制List。
@Override public void onItemClick(View view, int position) { //設(shè)置一下tab上的文字switch (tabCurrentPosition) {case 0:mTabLayout.getTabAt(tabCurrentPosition).setText(recyclerViewTabAdapter.getAddressList().get(position).getName());break;case 1:mTabLayout.getTabAt(tabCurrentPosition).setText(recyclerViewCityAdapter.getAddressList().get(position).getName());break;case 2:mTabLayout.getTabAt(tabCurrentPosition).setText(recyclerViewAreaAdapter.getAddressList().get(position));break;default:break;}//相同tab下進(jìn)行的position變化處理,這里要提醒一下,當(dāng)滿(mǎn)足這個(gè)條件的時(shí)候,就是說(shuō)明你下一個(gè)tab對(duì)應(yīng)的數(shù)據(jù)需要重新初始化了。if (currentSelectMap.get(tabCurrentPosition) != position) {int removeTab = mTabLayout.getTabCount() - 1;while (removeTab > tabCurrentPosition) {if (mTabLayout.getTabAt(removeTab) != null) {mTabLayout.removeTabAt(removeTab);currentSelectMap.put(removeTab, -1);}removeTab--;}//防止越界啊,目前只寫(xiě)了三級(jí)。其實(shí)是可以寫(xiě)成很多。這里沒(méi)有擴(kuò)展性if (tabCurrentPosition >= 2) {tabCurrentPosition = 2;} else {mTabLayout.addTab(mTabLayout.newTab().setText("請(qǐng)選擇"), false);currentSelectMap.put(tabCurrentPosition + 1, -1);}}//設(shè)置當(dāng)前tab下的選中狀態(tài)currentSelectMap.put(tabCurrentPosition, position);//滾動(dòng)到下一個(gè)tabtabCurrentPosition++;//越界的話(huà)處理為最后一個(gè)tabif (tabCurrentPosition >= mTabLayout.getTabCount()) {tabCurrentPosition = mTabLayout.getTabCount() - 1;if (selectAddressResultListener != null) {int[] result = {currentSelectMap.get(0), currentSelectMap.get(1), currentSelectMap.get(2)};selectAddressResultListener.selectAddressResult(result);dismiss();}} else {mTabLayout.setScrollPosition(tabCurrentPosition, 0f, true);}LogUtil.i("tabCurrentPosition--" + tabCurrentPosition + "tabCount" + mTabLayout.getTabCount());//如果在最后一個(gè)列表中切換的話(huà)這里單獨(dú)處理一下,因?yàn)閡pDataArray限制里更新的頻率。if (tabCurrentPosition == oldTabCurrentPosition) {switch (tabCurrentPosition) {case 0:recyclerViewTabAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition));recyclerViewTabAdapter.notifyDataSetChanged();break;case 1:recyclerViewCityAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition));recyclerViewCityAdapter.notifyDataSetChanged();break;case 2:recyclerViewAreaAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition));recyclerViewAreaAdapter.notifyDataSetChanged();break;default:break;}} else {upDataArray();}}其實(shí)上面基本就算主要步驟了,包含初始化—數(shù)據(jù)填充—點(diǎn)擊處理,完成的話(huà)基本上就實(shí)現(xiàn)了效果,下面我稍微完善一下這個(gè)控件。
數(shù)據(jù)回顯,這是個(gè)比較常規(guī)的需求,所以也加了上來(lái)。如下使用了兩個(gè)方法,因?yàn)槲野l(fā)現(xiàn)如果show頁(yè)面后直接更新數(shù)據(jù)的話(huà)會(huì)報(bào)錯(cuò)空指針,如果延遲一會(huì)就不出現(xiàn)問(wèn)題,感覺(jué)是初始化界面的未完成就使用控件導(dǎo)致。所以稍微延遲一點(diǎn)來(lái)繞過(guò)這個(gè)問(wèn)題。
public void setSelectAddress(final int[] selectAddress) {new Handler().postDelayed(new Runnable() {@Overridepublic void run() {setSelectAddress(selectAddress, true);}}, 60);}private void setSelectAddress(int[] selectAddress, boolean isUpdata) {if (selectAddress != null && selectAddress.length != 0 && cityBeanList != null) {for (int i = 0; i < 3; i++) {//保存選擇currentSelectMap.put(i, selectAddress[i]);}CityBean cityBean = cityBeanList.get(currentSelectMap.get(0));CityBean.CityBeanInner cityBeanInner = cityBean.getCity().get(currentSelectMap.get(1));String name = cityBeanInner.getArea().get(currentSelectMap.get(2));mTabLayout.removeAllTabs();//初始化tabmTabLayout.addTab(mTabLayout.newTab().setText(cityBean.getName()), false);mTabLayout.addTab(mTabLayout.newTab().setText(cityBeanInner.getName()), false);mTabLayout.addTab(mTabLayout.newTab().setText(name), true);tabCurrentPosition = currentSelectMap.size() - 1;//選擇最后mTabLayout.setScrollPosition(tabCurrentPosition, 0f, true);//更新集合upDataArray();}}數(shù)據(jù)回顯的實(shí)現(xiàn)是通過(guò)傳入標(biāo)識(shí)來(lái)完成,因?yàn)槲以赿ialog內(nèi)部維護(hù)了這個(gè)標(biāo)志,外部只需要傳如標(biāo)識(shí)并且更新一下界面就可以,界面的話(huà)需要更新三個(gè)tab和最后一個(gè)列表就可以了。由于初期就設(shè)計(jì)了三個(gè)列表所以這個(gè)標(biāo)識(shí)我只是用了前三個(gè)長(zhǎng)度(偷懶一下)。
以上步驟就基本完成了這個(gè)功能了
剩下一些零碎東西,如:數(shù)據(jù)源的設(shè)置,你需要參考一下我的CityBean實(shí)體;監(jiān)聽(tīng)設(shè)置,當(dāng)點(diǎn)擊最后列表的條目時(shí)候就把完整的數(shù)據(jù)返回去取了。
結(jié)束部分
現(xiàn)在android開(kāi)源的東西太多,我們似乎正在淪為代碼搬運(yùn)工,但是我認(rèn)為有些些東西還是要親自去做,能看懂和能寫(xiě)出來(lái)中間差了很長(zhǎng)一段路。再次共勉,共同進(jìn)步。
傳送到GitHub
有問(wèn)題歡迎糾正,謝謝啦
如果對(duì)你有幫助就點(diǎn)個(gè)贊把,你的鼓勵(lì)是我寫(xiě)作的動(dòng)力。
總結(jié)
以上是生活随笔為你收集整理的实现京东商城地址选择效果(效果还挺一致的)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java游戏项目之黄金矿工
- 下一篇: 大话TCP协议