詳解Android 多級(jí)聯(lián)動(dòng)控件實(shí)現(xiàn)思路討論
最近有一個(gè)需求是選擇多級(jí)聯(lián)動(dòng)數(shù)據(jù),數(shù)據(jù)級(jí)別不固定,可能是五級(jí),可能是兩級(jí),具體看用戶等級(jí)。
所以就需要一個(gè)多級(jí)聯(lián)動(dòng)選擇控件 ,在網(wǎng)上一番搜索或找到了這個(gè)控件, Android-PickerView
這個(gè)控件在三級(jí)以內(nèi)的的聯(lián)動(dòng)都沒有問題,但是最多只能到三級(jí)。
我在原有的基礎(chǔ)上做了一些擴(kuò)展,主要是添加了兩個(gè) picker
MultiWheelPickerView 可以根據(jù)數(shù)據(jù)動(dòng)態(tài)生成多個(gè)滾輪,不再局限于兩個(gè)三個(gè)選項(xiàng) DynamicWheelPickerView 也是動(dòng)態(tài)生成,但可以一級(jí)一級(jí)的加載數(shù)據(jù)并追加滾輪。
在使用時(shí),根據(jù)自身情況讓你的 JavaBean 實(shí)現(xiàn) IWheelItem 或者 IDynamicWheelItem 就好。
這里記錄并分享一下我的思路和實(shí)現(xiàn),也希望能和大家一起討論更好的實(shí)現(xiàn)方案。

起初,只是想根據(jù)獲取到的數(shù)據(jù)動(dòng)態(tài)的生成滾輪,有多少級(jí)就生成多少個(gè),自動(dòng)排列出來就好。
在看了源碼后發(fā)現(xiàn)原來的 OptionsPickerView 里寫死了三個(gè) WheelView ,所以最多只能是三個(gè)。
如果想動(dòng)態(tài)生成 WheelView 就不能寫死,只能根據(jù)數(shù)據(jù)生成,所以我選擇使用代碼創(chuàng)建 WheelView,不使用 layout 布局固定數(shù)量了。
除了 WheelView 部分外,其他部分還都是使用原來的布局。
因?yàn)橐獎(jiǎng)討B(tài)顯示數(shù)據(jù),就不能使用原來的 IPickerViewData 了,使用了一個(gè)新的 IWheelItem
public interface IWheelItem {
/**
*
* @return 顯示在滾輪的文本
*/
String getShowText();
/**
*
* @return 下一級(jí)的數(shù)據(jù)
*/
<T extends IWheelItem> List<T> getNextItems();
}
只有兩個(gè)方法,返回顯示數(shù)據(jù)用來顯示在滾輪上;在選擇了一級(jí)后自動(dòng)獲取下一級(jí)內(nèi)容顯示。
這種多級(jí)聯(lián)動(dòng)的數(shù)據(jù),明顯有著上下級(jí)關(guān)系,我就默認(rèn)為這種結(jié)構(gòu)了,一級(jí)套著一級(jí)。
并在 WheelView 里做了調(diào)整
/**
* 獲取所顯示的數(shù)據(jù)源
*
* @param item data resource
* @return 對(duì)應(yīng)顯示的字符串
*/
private String getContentText(Object item) {
if (item == null) {
return "";
} else if (item instanceof IPickerViewData) {
return ((IPickerViewData) item).getPickerViewText();
} else if (item instanceof Integer) {
//如果為整形則最少保留兩位數(shù).
return getFixNum((int) item);
}else if (item instanceof IWheelItem){
return ((IWheelItem)item).getShowText();
}
return item.toString();
}
First of all, 確定數(shù)據(jù)的層級(jí),根據(jù)層級(jí)決定生成 WheelView 的數(shù)量。
/**
* 獲取當(dāng)前 list 的層級(jí),最深有多少層
* 需要根據(jù)層級(jí)確定多少個(gè)滾輪
* @param list 數(shù)據(jù)
* @return 最深層級(jí)
*/
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;
}
我使用的是一個(gè) LinearLayout 橫向排列,用來承載動(dòng)態(tài)生成的 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:id="@+id/ll_multi_picker"
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>
注意:這里有一個(gè)問題就是,如果生成的滾輪很多,會(huì)顯得比較擁擠。
知道了要生成多少個(gè)滾輪后,代碼創(chuàng)建直接添加到 LinearLayout 里了。
int level =getLevel(wheelItems);
if (level > 0) {
//生成 滾輪
for (int i = 0; i < level; i++) {
WheelView wheelView = generateWheel();
mLlContainer.addView(wheelView);
}
//為滾輪賦值 ,都取第一個(gè)賦值
initWheel(wheelItems, 0);
}
生成 WheelView 之后,就是給控件賦值了,我這里默認(rèn)取第一個(gè)當(dāng)做選中的值。
只要前邊一級(jí)選中了,那就獲取它的下一級(jí)數(shù)據(jù)給下一個(gè)控件賦值,如此遞歸到最后一個(gè)。
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));
//默認(rèn)選中第一項(xiàng),添加到結(jié)果里。
T wheelItem = list.get(0);
addToResult(wheelItem, wheelIndex);
List<T> children = list.get(0).getNextItems();
//有子集,繼續(xù)添加
wheelIndex++;
initWheel(children, wheelIndex);
}else{
for (int i=wheelIndex;i<mLlContainer.getChildCount();i++){
wheelView = (WheelView) mLlContainer.getChildAt(i);
wheelView.setAdapter(new MultiWheelAdapter(null));
}
}
}
關(guān)于選中的數(shù)據(jù)和事件,和原來一樣,只是換了一種形式,使用 List 容器。
按照順序,把選中的數(shù)據(jù)都列在里面了,邏輯如下
protected void addToResult(T value, int index) {
// 檢測(cè)是否發(fā)生了變化,需要對(duì)外釋放信號(hào)
int size = resultList.size();
Log.d(MultiWheelPickerView.class.getSimpleName(), "addToResult: " + index + "-->" + value + "; size->" + size);
//上級(jí)換了人,下級(jí)全部移除掉
while (index < size) {
resultList.remove(index);
size = resultList.size();
}
//已經(jīng)把之后的刪除了,直接添加就行了
boolean isAddToResult =true;
if (null!=listener){
// 這里可以從外部判斷是否可以選擇,有的 是不需要選擇的,例如 all, 或者 “”
isAddToResult = listener.isAddToResult(value);
}
if (isAddToResult) {
resultList.add(value);
}
if (null!=listener){
listener.onChange(resultList);
}
}
就這樣稍微改一改,一個(gè)動(dòng)態(tài)多級(jí)關(guān)聯(lián)控件就有了,在使用時(shí),讓你的 JavaBean 實(shí)現(xiàn) IWheelItem 就好。
簡(jiǎn)單使用方式如下
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) {
//在滾輪選擇發(fā)生變化時(shí)會(huì)被調(diào)用
showChange(result);
}
@Override
public void onSelect(List<CodeTable> result) {
//在按下確定按鈕時(shí)會(huì)被調(diào)用
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) {
//此方法返回值會(huì)確定這個(gè)值是否可以被選中
return !selectValue.getCode().equalsIgnoreCase("all");
}
});
fixedPickerView = builder.build();
fixedPickerView.setTitleText("行政區(qū)劃");
fixedPickerView.setWheelItems(getPickerData());
}
fixedPickerView.show();
}
雖然實(shí)現(xiàn)了多級(jí)聯(lián)動(dòng),但是在實(shí)際使用時(shí)又發(fā)現(xiàn)了不可忽視的問題: 如果數(shù)據(jù)過多,就會(huì)加載很長(zhǎng)時(shí)間,從省級(jí)到村級(jí),會(huì)有數(shù)萬條記錄,一次獲取過來體驗(yàn)太差了,而且有崩潰的風(fēng)險(xiǎn)。
更好的辦法是一級(jí)一級(jí)的去獲取數(shù)據(jù),選中省級(jí)再去獲取下屬的市級(jí)并追加滾輪顯示,選中市級(jí)再去獲取縣級(jí),如此類推。
So, 接續(xù)改,因?yàn)閿?shù)據(jù)也是多次獲取了,就無法確定層級(jí)了,故需要每有新的層級(jí)時(shí)添加新的 WheelView 追加到顯示容器里(突然增加一個(gè)View會(huì)出現(xiàn)橫跳的情況,最好是加入一個(gè)動(dòng)畫平滑一點(diǎn))。
在選中一個(gè)數(shù)據(jù)時(shí),也要判斷是否需要去加載下一級(jí),在我的需求里,有的是需要到村級(jí),有的則需要到縣級(jí)。
所以具體是否要加載下一級(jí)的配置要放出來,我這里放在了數(shù)據(jù)接口上,由數(shù)據(jù)自身判斷。
在 IWheelItem 的基礎(chǔ)上擴(kuò)展了一個(gè) IDynamicWheelItem
public interface IDynamicWheelItem extends IWheelItem {
/**
* @return 是否需要加載下一級(jí)
*/
boolean isLoadNext() ;
}
然后是在生成 WheelView 這里做了一些修改,根據(jù)傳入的數(shù)據(jù)生成。
也是默認(rèn)選擇了第一項(xiàng),如果能被選中,則繼續(xù)生成或者去加載子級(jí)數(shù)據(jù)。
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);
}
}
}
}
}
在選中一個(gè)數(shù)據(jù)后的滾輪賦值也做了修改,如果是判斷是否需要去加載下一級(jí)數(shù)據(jù)或者是否現(xiàn)有數(shù)據(jù)
在后續(xù)沒有數(shù)據(jù)的情況下,也沒有移除掉 WheelView 。一旦沒有數(shù)據(jù)就移除,會(huì)出現(xiàn)左右橫跳的情況(這里也可以做一個(gè)動(dòng)畫,會(huì)顯得沒有那么突兀)。
/**
* 設(shè)置下級(jí)Wheel 的數(shù)據(jù)
*
* @param current 數(shù)據(jù)
* @param nextLevel 下一層
*/
private void setupChildWheel(T current, int nextLevel) {
if (mLlContainer.getChildCount() == nextLevel) {
if (current.isLoadNext()) { //最后一級(jí)了,但是下一級(jí)仍然需要顯示
loadNext(current, nextLevel);
}
return;
}
List<T> nextItems = current.getNextItems();
//對(duì)于下級(jí)wheel的設(shè)置上對(duì)應(yīng)的數(shù)據(jù),即使沒有那么多級(jí)的,也不能移除view,只能將數(shù)據(jù)設(shè)置為null
WheelView wheelView = (WheelView) mLlContainer.getChildAt(nextLevel);
if (null != nextItems && nextItems.size() > 0) {
//有子集
//在 level ==count 時(shí)可能為空
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{ //當(dāng)前已經(jīng)不能選擇了,之后的滾輪數(shù)據(jù)也必須置空
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));
}
//沒有數(shù)據(jù),需要去加載
if (canSelect(current)&¤t.isLoadNext()) {
loadNext(current, nextLevel);
}
}
}
在加載數(shù)據(jù)成功后,要將數(shù)據(jù)追加到對(duì)應(yīng)的滾輪上
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)) { //合法數(shù)據(jù),能被選擇。
//需要加載下一級(jí)
level++;
setupChildWheel(codeTable,level);
}
}
}
至此,改完了,比之前那個(gè)多放出來兩個(gè)方法。
在偵聽器里擴(kuò)展了一個(gè)加載下級(jí)的方法。
public interface DynamicWheelSelectListener<T extends IDynamicWheelItem>extends MultiWheelSelectListener<T> {
/**
* 加載下一級(jí)的數(shù)據(jù)
* @param item 當(dāng)前數(shù)據(jù)
* @param nextLevel 下一級(jí)的層級(jí)
*/
void loadNextItems(T item, int nextLevel);
}
使用辦法和上面的 MultiWheelPickerView 大同小異
DynamicWheelPickerView<CodeTable> dynamicPickerView;
private void dynamicPicker() {
if (null == dynamicPickerView) {
dynamicPickerView =new DynamicWheelPickerBuilder<CodeTable>(this,new DynamicWheelSelectListener<CodeTable>() {
@Override
public void loadNextItems(CodeTable item, int nextLevel) {
//這里模擬的數(shù)據(jù),在加載后將 isLoadNext 設(shè)置為 false。
List<CodeTable> child = getChild(random());
item.setChildren(child);
item.setLoadNext(false);
//將數(shù)據(jù)賦值到對(duì)應(yīng)的控件上,nextLevel就是控件的位置。
dynamicPickerView.appendWheel(child, nextLevel);
}
@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) {
//是 0 的不能被選擇
return !selectValue.getCode().equalsIgnoreCase("0");
}
})
.build();
dynamicPickerView.setTitleText("行政區(qū)劃");
dynamicPickerView.setWheelItems(getChild(random()));
}
dynamicPickerView.show();
}
具體用法可以看代碼,在這里 TestMultiWheelActivity
其他想法:
- 目前使用 LinearLayout 包裹的,是否可以換成 RecyclerView 呢,是否能更好的控制在一行超出多少個(gè)后換行,避免擁擠。
- 目前在動(dòng)態(tài)追加滾輪時(shí)是很生硬的追加上去的,可以優(yōu)化為使用動(dòng)畫平滑的過渡可能體驗(yàn)更好些。
目前把代碼放在了這里 Android-PickerView
我的實(shí)現(xiàn)方式就是這樣,希望能和大家討論更好的方式。
到此這篇關(guān)于詳解Android 多級(jí)聯(lián)動(dòng)控件實(shí)現(xiàn)思路討論的文章就介紹到這了,更多相關(guān)Android 多級(jí)聯(lián)動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 最好用的Android省市區(qū)三級(jí)聯(lián)動(dòng)選擇效果
- Android自定義WheelView地區(qū)選擇三級(jí)聯(lián)動(dòng)
- Android日期選擇器實(shí)現(xiàn)年月日三級(jí)聯(lián)動(dòng)
- Android省市區(qū)三級(jí)聯(lián)動(dòng)控件使用方法實(shí)例講解
- android-wheel控件實(shí)現(xiàn)三級(jí)聯(lián)動(dòng)效果
- Android使用android-wheel實(shí)現(xiàn)省市縣三級(jí)聯(lián)動(dòng)
- Android實(shí)現(xiàn)聯(lián)動(dòng)下拉框二級(jí)地市聯(lián)動(dòng)下拉框功能
- Android實(shí)現(xiàn)省市區(qū)三級(jí)聯(lián)動(dòng)
- android實(shí)現(xiàn)下拉菜單三級(jí)聯(lián)動(dòng)
相關(guān)文章
Android EditText實(shí)現(xiàn)關(guān)鍵詞批量搜索示例
本篇文章主要介紹了Android EditText實(shí)現(xiàn)關(guān)鍵詞批量搜索示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
Android上使用ZXing識(shí)別條形碼與二維碼的方法
這篇文章主要介紹了Android上使用ZXing識(shí)別條形碼與二維碼的方法,需要的朋友可以參考下2014-08-08
Android進(jìn)階KOOM線上APM監(jiān)控全面剖析
這篇文章主要為大家介紹了Android進(jìn)階KOOM線上APM監(jiān)控全面剖析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
關(guān)于Kotlin寫界面時(shí)諸多控件的點(diǎn)擊事件
這篇文章主要介紹了關(guān)于Kotlin寫界面時(shí)諸多控件的點(diǎn)擊事件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03
app 請(qǐng)求服務(wù)器json數(shù)據(jù)實(shí)例代碼
下面小編就為大家分享一篇app 請(qǐng)求服務(wù)器json數(shù)據(jù)實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01
Android結(jié)合kotlin使用coroutine的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Android結(jié)合kotlin使用coroutine的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Android編程實(shí)現(xiàn)QQ表情的發(fā)送和接收完整實(shí)例(附源碼)
這篇文章主要介紹了Android編程實(shí)現(xiàn)QQ表情的發(fā)送和接收的方法,涉及Android圖片資源、正則表達(dá)式及對(duì)話框的相關(guān)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
Android開發(fā)之獲取單選與復(fù)選框的值操作示例
這篇文章主要介紹了Android開發(fā)之獲取單選與復(fù)選框的值操作,結(jié)合實(shí)例形式分析了Android針對(duì)單選按鈕、復(fù)選框的事件響應(yīng)、數(shù)值獲取等相關(guān)操作技巧,需要的朋友可以參考下2019-04-04

