詳解Android 多級聯動控件實現思路討論
最近有一個需求是選擇多級聯動數據,數據級別不固定,可能是五級,可能是兩級,具體看用戶等級。
所以就需要一個多級聯動選擇控件 ,在網上一番搜索或找到了這個控件, Android-PickerView
這個控件在三級以內的的聯動都沒有問題,但是最多只能到三級。
我在原有的基礎上做了一些擴展,主要是添加了兩個 picker
MultiWheelPickerView 可以根據數據動態生成多個滾輪,不再局限于兩個三個選項 DynamicWheelPickerView 也是動態生成,但可以一級一級的加載數據并追加滾輪。
在使用時,根據自身情況讓你的 JavaBean 實現 IWheelItem 或者 IDynamicWheelItem 就好。
這里記錄并分享一下我的思路和實現,也希望能和大家一起討論更好的實現方案。
起初,只是想根據獲取到的數據動態的生成滾輪,有多少級就生成多少個,自動排列出來就好。
在看了源碼后發現原來的 OptionsPickerView 里寫死了三個 WheelView ,所以最多只能是三個。
如果想動態生成 WheelView 就不能寫死,只能根據數據生成,所以我選擇使用代碼創建 WheelView,不使用 layout 布局固定數量了。
除了 WheelView 部分外,其他部分還都是使用原來的布局。
因為要動態顯示數據,就不能使用原來的 IPickerViewData 了,使用了一個新的 IWheelItem
public interface IWheelItem { /** * * @return 顯示在滾輪的文本 */ String getShowText(); /** * * @return 下一級的數據 */ <T extends IWheelItem> List<T> getNextItems();}
只有兩個方法,返回顯示數據用來顯示在滾輪上;在選擇了一級后自動獲取下一級內容顯示。
這種多級聯動的數據,明顯有著上下級關系,我就默認為這種結構了,一級套著一級。
并在 WheelView 里做了調整
/** * 獲取所顯示的數據源 * * @param item data resource * @return 對應顯示的字符串 */ private String getContentText(Object item) { if (item == null) { return ''; } else if (item instanceof IPickerViewData) { return ((IPickerViewData) item).getPickerViewText(); } else if (item instanceof Integer) { //如果為整形則最少保留兩位數. return getFixNum((int) item); }else if (item instanceof IWheelItem){ return ((IWheelItem)item).getShowText(); } return item.toString(); }
First of all, 確定數據的層級,根據層級決定生成 WheelView 的數量。
/** * 獲取當前 list 的層級,最深有多少層 * 需要根據層級確定多少個滾輪 * @param list 數據 * @return 最深層級 */ private int getLevel(List<T> list) { int level = 0; if (list != null && list.size() > 0) { level = 1; int childLevel = 0; for (T code : list) {List<T> children =code.getNextItems();int temp = getLevel(children);if (temp > childLevel) { childLevel = temp;} } level += childLevel; } return level; }
我使用的是一個 LinearLayout 橫向排列,用來承載動態生成的 WheelView 。
<?xml version='1.0' encoding='utf-8'?><LinearLayout xmlns:android='http://schemas.android.com/apk/res/android' android:layout_width='wrap_content' android:layout_height='wrap_content' android:orientation='vertical'> <include layout='@layout/include_pickerview_topbar' android:layout_width='match_parent' android:layout_height='@dimen/pickerview_topbar_height' /> <LinearLayout android: android:layout_width='match_parent' android:layout_height='match_parent' android:background='@android:color/white' android:gravity='center' android:minHeight='180dp' android:orientation='horizontal'> </LinearLayout></LinearLayout>
注意:這里有一個問題就是,如果生成的滾輪很多,會顯得比較擁擠。
知道了要生成多少個滾輪后,代碼創建直接添加到 LinearLayout 里了。
int level =getLevel(wheelItems);if (level > 0) { //生成 滾輪 for (int i = 0; i < level; i++) { WheelView wheelView = generateWheel(); mLlContainer.addView(wheelView); } //為滾輪賦值 ,都取第一個賦值 initWheel(wheelItems, 0);}
生成 WheelView 之后,就是給控件賦值了,我這里默認取第一個當做選中的值。
只要前邊一級選中了,那就獲取它的下一級數據給下一個控件賦值,如此遞歸到最后一個。
protected void initWheel(List<T> list, int wheelIndex) { WheelView wheelView = (WheelView) mLlContainer.getChildAt(wheelIndex); if (null == wheelView) { Log.d(MultiWheelPickerView.class.getSimpleName(), 'initWheel: 超出了范圍 ' + wheelIndex + ' > ' + mLlContainer.getChildCount()); return; } if (null != list && list.size() > 0) { wheelView.setAdapter(new MultiWheelAdapter(list)); wheelView.setCurrentItem(0); wheelView.setOnItemSelectedListener(new MultiWheelItemSelector(list, wheelIndex)); //默認選中第一項,添加到結果里。 T wheelItem = list.get(0); addToResult(wheelItem, wheelIndex); List<T> children = list.get(0).getNextItems(); //有子集,繼續添加 wheelIndex++; initWheel(children, wheelIndex); }else{ for (int i=wheelIndex;i<mLlContainer.getChildCount();i++){wheelView = (WheelView) mLlContainer.getChildAt(i);wheelView.setAdapter(new MultiWheelAdapter(null)); } } }
關于選中的數據和事件,和原來一樣,只是換了一種形式,使用 List 容器。
按照順序,把選中的數據都列在里面了,邏輯如下
protected void addToResult(T value, int index) { // 檢測是否發生了變化,需要對外釋放信號 int size = resultList.size(); Log.d(MultiWheelPickerView.class.getSimpleName(), 'addToResult: ' + index + '-->' + value + '; size->' + size); //上級換了人,下級全部移除掉 while (index < size) { resultList.remove(index); size = resultList.size(); } //已經把之后的刪除了,直接添加就行了 boolean isAddToResult =true; if (null!=listener){ // 這里可以從外部判斷是否可以選擇,有的 是不需要選擇的,例如 all, 或者 “” isAddToResult = listener.isAddToResult(value); } if (isAddToResult) { resultList.add(value); } if (null!=listener){ listener.onChange(resultList); } }
就這樣稍微改一改,一個動態多級關聯控件就有了,在使用時,讓你的 JavaBean 實現 IWheelItem 就好。
簡單使用方式如下
MultiWheelPickerView<CodeTable> fixedPickerView; private void fixedPicker() { if (null == fixedPickerView) { MultiWheelPickerBuilder<CodeTable> builder = new MultiWheelPickerBuilder<>(this, new MultiWheelSelectListener<CodeTable>() { @Override public void onChange(List<CodeTable> result) { //在滾輪選擇發生變化時會被調用 showChange(result); } @Override public void onSelect(List<CodeTable> result) { //在按下確定按鈕時會被調用 StringBuffer buffer = new StringBuffer(); int size = result.size(); for (int i = 0; i < size; i++) {if (i != 0) { buffer.append('->');}buffer.append(result.get(i).getShowText()); } mTvResult.setText(buffer.toString()); } @Override public boolean isAddToResult(CodeTable selectValue) { //此方法返回值會確定這個值是否可以被選中 return !selectValue.getCode().equalsIgnoreCase('all'); } }); fixedPickerView = builder.build(); fixedPickerView.setTitleText('行政區劃'); fixedPickerView.setWheelItems(getPickerData()); } fixedPickerView.show(); }
雖然實現了多級聯動,但是在實際使用時又發現了不可忽視的問題: 如果數據過多,就會加載很長時間,從省級到村級,會有數萬條記錄,一次獲取過來體驗太差了,而且有崩潰的風險。
更好的辦法是一級一級的去獲取數據,選中省級再去獲取下屬的市級并追加滾輪顯示,選中市級再去獲取縣級,如此類推。
So, 接續改,因為數據也是多次獲取了,就無法確定層級了,故需要每有新的層級時添加新的 WheelView 追加到顯示容器里(突然增加一個View會出現橫跳的情況,最好是加入一個動畫平滑一點)。
在選中一個數據時,也要判斷是否需要去加載下一級,在我的需求里,有的是需要到村級,有的則需要到縣級。
所以具體是否要加載下一級的配置要放出來,我這里放在了數據接口上,由數據自身判斷。
在 IWheelItem 的基礎上擴展了一個 IDynamicWheelItem
public interface IDynamicWheelItem extends IWheelItem { /** * @return 是否需要加載下一級 */ boolean isLoadNext() ;}
然后是在生成 WheelView 這里做了一些修改,根據傳入的數據生成。
也是默認選擇了第一項,如果能被選中,則繼續生成或者去加載子級數據。
protected void generateWheel(List<T> data) { if (data != null && data.size() > 0) { //需要生成 wheel WheelView wheelView = generateWheel(); wheelView.setAdapter(new ArrayWheelAdapter(data)); mLlContainer.addView(wheelView); int level = mLlContainer.getChildCount() - 1; wheelView.setOnItemSelectedListener(new DynamicWheelItemSelector(data, level)); T iWheelItem = data.get(0); addToResult(iWheelItem, level); if (canSelect(iWheelItem)) {List<T> nextItems = iWheelItem.getNextItems();if (null != nextItems && nextItems.size() > 0) { generateWheel(nextItems);} else { if (iWheelItem.isLoadNext()) { loadNext(iWheelItem, ++level); }} } } }
在選中一個數據后的滾輪賦值也做了修改,如果是判斷是否需要去加載下一級數據或者是否現有數據
在后續沒有數據的情況下,也沒有移除掉 WheelView 。一旦沒有數據就移除,會出現左右橫跳的情況(這里也可以做一個動畫,會顯得沒有那么突兀)。
/** * 設置下級Wheel 的數據 * * @param current 數據 * @param nextLevel 下一層 */ private void setupChildWheel(T current, int nextLevel) { if (mLlContainer.getChildCount() == nextLevel) { if (current.isLoadNext()) { //最后一級了,但是下一級仍然需要顯示loadNext(current, nextLevel); } return; } List<T> nextItems = current.getNextItems(); //對于下級wheel的設置上對應的數據,即使沒有那么多級的,也不能移除view,只能將數據設置為null WheelView wheelView = (WheelView) mLlContainer.getChildAt(nextLevel); if (null != nextItems && nextItems.size() > 0) { //有子集 //在 level ==count 時可能為空 if (wheelView == null) {wheelView = generateWheel(); } wheelView.setAdapter(new ArrayWheelAdapter(nextItems)); wheelView.setCurrentItem(0); wheelView.setOnItemSelectedListener(new DynamicWheelItemSelector(nextItems, nextLevel)); T wheelItem = nextItems.get(0); addToResult(wheelItem, nextLevel); nextLevel++; if (canSelect(wheelItem)) {setupChildWheel(wheelItem, nextLevel); }else{ //當前已經不能選擇了,之后的滾輪數據也必須置空for (int i = nextLevel; i < mLlContainer.getChildCount(); i++) { wheelView = (WheelView) mLlContainer.getChildAt(i); wheelView.setOnItemSelectedListener(null); wheelView.setAdapter(new MultiWheelAdapter(null));} } } else { //還需要判斷是否需要再次去獲取子集。 //沒有子集 全部置空 for (int i = nextLevel; i < mLlContainer.getChildCount(); i++) {wheelView = (WheelView) mLlContainer.getChildAt(i);wheelView.setOnItemSelectedListener(null);wheelView.setAdapter(new MultiWheelAdapter(null)); } //沒有數據,需要去加載 if (canSelect(current)&¤t.isLoadNext()) {loadNext(current, nextLevel); } } }
在加載數據成功后,要將數據追加到對應的滾輪上
public void appendWheel(List<T> list, int level) { WheelView wheelView = null; if (level < mLlContainer.getChildCount()) { wheelView = (WheelView) mLlContainer.getChildAt(level); } else { wheelView = generateWheel(); if (null != list && list.size() > 0)mLlContainer.addView(wheelView); } if (null != list && list.size() > 0) { wheelView.setAdapter(new MultiWheelAdapter(list)); wheelView.setCurrentItem(0); T codeTable = list.get(0); addToResult(codeTable,level); wheelView.setOnItemSelectedListener(new DynamicWheelItemSelector(list, level)); if (canSelect(codeTable)) { //合法數據,能被選擇。//需要加載下一級level++;setupChildWheel(codeTable,level); } } }
至此,改完了,比之前那個多放出來兩個方法。
在偵聽器里擴展了一個加載下級的方法。
public interface DynamicWheelSelectListener<T extends IDynamicWheelItem>extends MultiWheelSelectListener<T> { /** * 加載下一級的數據 * @param item 當前數據 * @param nextLevel 下一級的層級 */ void loadNextItems(T item, int nextLevel);}
使用辦法和上面的 MultiWheelPickerView 大同小異
DynamicWheelPickerView<CodeTable> dynamicPickerView; private void dynamicPicker() { if (null == dynamicPickerView) { dynamicPickerView =new DynamicWheelPickerBuilder<CodeTable>(this,new DynamicWheelSelectListener<CodeTable>() {@Overridepublic void loadNextItems(CodeTable item, int nextLevel) { //這里模擬的數據,在加載后將 isLoadNext 設置為 false。 List<CodeTable> child = getChild(random()); item.setChildren(child); item.setLoadNext(false); //將數據賦值到對應的控件上,nextLevel就是控件的位置。 dynamicPickerView.appendWheel(child, nextLevel);}@Overridepublic void onChange(List<CodeTable> result) { showChange(result);}@Overridepublic void onSelect(List<CodeTable> result) { StringBuffer buffer = new StringBuffer(); int size = result.size(); for (int i = 0; i < size; i++) { if (i != 0) { buffer.append('->'); } buffer.append(result.get(i).getShowText()); } mTvResult.setText(buffer.toString());}@Overridepublic boolean isAddToResult(CodeTable selectValue) { //是 0 的不能被選擇 return !selectValue.getCode().equalsIgnoreCase('0');} }) .build(); dynamicPickerView.setTitleText('行政區劃'); dynamicPickerView.setWheelItems(getChild(random())); } dynamicPickerView.show(); }
具體用法可以看代碼,在這里 TestMultiWheelActivity
其他想法:
目前使用 LinearLayout 包裹的,是否可以換成 RecyclerView 呢,是否能更好的控制在一行超出多少個后換行,避免擁擠。 目前在動態追加滾輪時是很生硬的追加上去的,可以優化為使用動畫平滑的過渡可能體驗更好些。目前把代碼放在了這里 Android-PickerView
我的實現方式就是這樣,希望能和大家討論更好的方式。
到此這篇關于詳解Android 多級聯動控件實現思路討論的文章就介紹到這了,更多相關Android 多級聯動內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!
相關文章: