上一篇博客《安卓可输入可选择的下拉框》写了一个可输入、可选择的下拉框,今天讲讲如何利用它关联性使用。
思路分析
实际上下面介绍的使用方法受场景的限制,这肯定不是读者想要的东西,也不是我想要分析的初衷,所以,我想说清思路,有兴趣的读者可以继续看下面的代码。
关联性
相互关联的下拉框之间存在关联性,即前面的下拉框选择会影响后面下拉框的下拉数据,那如何确定这种关联性呢?比如下面介绍的使用场景的两种关联性,一个是下拉框数据全部由关联的下拉框决定,一个是下拉框数据被关联的下拉框部分影响。
对于第一种完全关联关系,我们可以通过关联的下拉框的数据(key),拿到被关联的下拉框的数据(value),然后进行全部更新。类似于从 map 中拿数据更新。
对于第二种部分关联关系,我们可以拿到关联的下拉框的数据(value1),更改被关联的下拉框的数据(value2),然后更新。类似于两个数据求交集。
动态更新
因为存在关联性,所以下拉框需要动态更新下拉的数据,那么如何更新呢?
考虑一个下拉框可能涉及的东西,下拉框对象、下拉数据、列表适配器,不外乎这三样。如果我们把这三样看成一个整体,关联的下拉框发生修改,我们只要拿到这三个东西,便能动态修改。
这里要注意下拉数据(list),修改里面的数据,并不会改变这个 list 对象本身,修改完数据之后,使用列表适配器通知数据修改,就能达到动态修改下拉数据的目的。
好了,到这实际思路已经很清晰了,下面看如何实现。
设计场景
这里需要设计的是一个频点选择功能,有三个下拉框,它们之间有相互关联性,第一个下拉框用来选择频段,第二个下拉框用来选择频点,第三个下拉框也是选频点,但是第三个下拉框中可选的频点中要除去第二个下拉框选中的频点。
因为我们的下拉框还能手动输入,这里我们还要传进去频点的范围,输入不合法的时候提示错误,合法的时候因为没有确认按钮,希望它自动保存。
最终设计效果如下(部分功能去除了):
准备资源
因为涉及频点的选择,首先应该提供频点的范围,下面数据储存在 values - array.xml 中
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="none">
<item>- -</item>
</string-array>
<string-array name="band">
<item>- -</item>
<item>B01</item>
<item>B03</item>
<item>B05</item>
<item>B08</item>
<item>B38</item>
<item>B39</item>
<item>B40</item>
<item>B41</item>
</string-array>
<integer-array name="bandMin">
<item>0</item>
<item>0</item>
<item>1200</item>
<item>2400</item>
<item>3450</item>
<item>37750</item>
<item>38250</item>
<item>38650</item>
<item>39650</item>
</integer-array>
<integer-array name="bandMax">
<item>-1</item>
<item>599</item>
<item>1949</item>
<item>2649</item>
<item>3799</item>
<item>38249</item>
<item>38649</item>
<item>39649</item>
<item>41598</item>
</integer-array>
<string-array name="B01">
<item>75</item>
<item>100</item>
<item>350</item>
<item>375</item>
<item>400</item>
<item>450</item>
<item>500</item>
<item>525</item>
<item>575</item>
</string-array>
<string-array name="B05">
<item>2452</item>
</string-array>
<string-array name="B03">
<item>1275</item>
<item>1300</item>
<item>1350</item>
<item>1375</item>
<item>1396</item>
<item>1400</item>
<item>1506</item>
<item>1524</item>
<item>1525</item>
<item>1650</item>
<item>1825</item>
<item>1850</item>
</string-array>
<string-array name="B08">
<item>3590</item>
<item>3621</item>
<item>3641</item>
<item>3682</item>
<item>3683</item>
<item>3715</item>
<item>3725</item>
<item>3741</item>
<item>3768</item>
<item>3769</item>
<item>3770</item>
<item>3775</item>
</string-array>
<string-array name="B38">
<item>38098</item>
<item>37900</item>
</string-array>
<string-array name="B39">
<item>38350</item>
<item>38400</item>
<item>38496</item>
<item>38544</item>
</string-array>
<string-array name="B40">
<item>38950</item>
<item>39148</item>
<item>39292</item>
<item>38850</item>
<item>39450</item>
</string-array>
<string-array name="B41">
<item>40936</item>
<item>40340</item>
<item>40540</item>
<item>41134</item>
<item>41140</item>
</string-array>
</resources>
这里只是部分常用频点,供下拉的时候选择,其中 bandMin、bandMax 是各个频段的上下界,在我们判断输入频点的时候需要使用到。
代码使用
//这里本来有三组的,我简化了一下啊
private EditableSpinner band1Spinner, band2Spinner, band3Spinner;
//频段数据
private List<String> bands;
//频段上下界,对应频段数据
private int[] bandMaxValues;
private int[] bandMinValues;
//储存 EditableSpinner 的列表,便于集中操作
private List<EditableSpinner> spinnerList;
//对应上面 EditableSpinner 中的数据列表
private List<List<String>> spinnerContents;
//对应上面 EditableSpinner 中的适配器
private List<ArrayAdapter<String>> spinnerAdapters;
//储存的数据 band 频段 - mFreq 主频点 - aFreq 副频点
private String[] bfData = {"- -","- -","- -"};
//private String[][] bfData = {{"- -","- -","- -"},{"- -","- -","- -"},{"- -","- -","- -"}};
@Override
protected void onCreate(Bundle savedInstanceState) {
...
//初始化数组数据
initBandRange();
//初始化下拉列表
initSpinnerList();
//设置下拉框事件
setBandAndFreqChangeAction();
}
//初始化数组数据
private void initBandRange() {
bands = Arrays.asList(getResources().getStringArray(getArrayId("band")));
bandMaxValues = getResources().getIntArray(getArrayId("bandMax"));
bandMinValues = getResources().getIntArray(getArrayId("bandMin"));
}
//通过名称获取数组数据id
private int getArrayId(String select){
try {
int id;
ApplicationInfo appInfo = MyApplication.getContext().getApplicationInfo();
id = MyApplication.getContext().getResources().getIdentifier(select, "array", appInfo.packageName);
return id == 0 ? R.array.none : id;
}catch (Exception e){
return R.array.B01;
}
}
//初始化下拉列表
private void initSpinnerList() {
spinnerList = Arrays.asList( //注意生成的是视图,不可修改
band1Spinner, mFreq1Spinner, aFreq1Spinner,
//band2Spinner, mFreq2Spinner, aFreq2Spinner,
//band3Spinner, mFreq3Spinner, aFreq3Spinner
);
spinnerContents = new ArrayList<>();
spinnerAdapters = new ArrayList<>();
//设置初始数据
for (int i = 0; i < spinnerList.size(); i++) {
EditableSpinner spinner = spinnerList.get(i);
List<String> contentList = new ArrayList<>();
if ((i % 3) == 0) {
contentList.addAll(bands);
}else {
//无选中时数据
contentList.add("- -");
}
//下拉框列表适配器
ArrayAdapter<String> arrayAdapter =
new ArrayAdapter<>(mActivity, android.R.layout.simple_spinner_item, contentList);
spinner.setAdapter(arrayAdapter);
//更新数据显示
arrayAdapter.notifyDataSetChanged();
//添加到全局变量去,后面要用到,靠这动态修改下拉列表
spinnerContents.add(contentList);
spinnerAdapters.add(arrayAdapter);
}
}
//设置下拉框事件
private void setBandAndFreqChangeAction() {
for (final EditableSpinner spinner : spinnerList) {
//下拉列表点击
spinner.setOnItemClickListener(position -> {
//下拉框文字数据
String selectStr = spinner.getEditString();
//下拉框序号,0 频段、1 主频点,2 副频点
int index = spinnerList.indexOf(spinner);
switch (index % 3) {
case 0:
//选择频段,需要修改主频点下拉数据
setAllowedMainFreq(index, selectStr);
break;
case 1:
//选择主频点,需要修改副频点下拉数据
setAllowedAssiFreq(index);
break;
case 2:
break;
default:
}
});
//文字输入变化
if ((spinnerList.indexOf(spinner) % 3) != 0) {
spinner.enableTextChangeListener(() -> {
int spinnerIndex = spinnerList.indexOf(spinner);
if (TextUtils.isEmpty(spinner.getEditString())) return;
//输入数据符合情况,保存数据
onSaveBandAndFreq(spinnerIndex, spinner.getEditString());
});
}
}
}
//保存数据
private void onSaveBandAndFreq(int spinnerIndex, String value) {
bfData[spinnerIndex] = value;
//如果有多组可以按下面保存
//int kind = spinnerIndex % 3;
//int index = spinnerIndex / 3;
//bfData[kind][index] = value;
}
//选择频段,需要修改主频点下拉数据
private void setAllowedMainFreq(int index, String band) {
EditableSpinner spinner = spinnerList.get(index + 1);
ArrayAdapter<String> adapter = spinnerAdapters.get(index + 1);
List<String> contents = spinnerContents.get(index + 1);
//默认第一个为"- -",表示不选
contents.clear();
contents.add("- -");
spinner.setEditString("- -");
//添加对应频段所有下拉数据
if (!band.equals("- -")) {
contents.addAll(getMFreqArrayFromBand(band));
}
adapter.notifyDataSetChanged();
//设置频段可输入范围
spinner.setMaxAndMinValue(getBandMinValue(band), getBandMaxValue(band));
}
//选择主频点,需要修改副频点下拉数据
private void setAllowedAssiFreq(int index) {
//注意此处 index 是对应主频点的 index
//频段
String band = spinnerList.get(index - 1).getEditString();
//主频点
String mFreq = spinnerList.get(index).getEditString();
EditableSpinner spinner = spinnerList.get(index + 1);
ArrayAdapter<String> adapter = spinnerAdapters.get(index + 1);
List<String> contents = spinnerContents.get(index + 1);
//默认第一个为"- -",表示不选
contents.clear();
contents.add("- -");
spinner.setEditString("- -");
//添加对应频段所有下拉数据
if (!band.equals("- -")) {
//添加去除主频点后的数据
contents.addAll(getAFreqArrayFromBand(band, mFreq));
}
adapter.notifyDataSetChanged();
//设置频段可输入范围
spinner.setMaxAndMinValue(getBandMinValue(band), getBandMaxValue(band));
}
private int getBandMaxValue(String band) {
int index = getIndexOfList(band, bands);
return bandMaxValues[index];
}
private int getBandMinValue(String band) {
int index = getIndexOfList(band, bands);
return bandMinValues[index];
}
//获取主频段的数组数据
private ArrayList<String> getMFreqArrayFromBand(String band) {
return new ArrayList<>(Arrays.asList(getResources().getStringArray(getArrayId(band))));
}
//获取副频段的数组数据,即要去除选中的主频点
private List<String> getAFreqArrayFromBand(String band, String mFreq) {
List<String> aFreq = getMFreqArrayFromBand(band);
Iterator<String> iterator = aFreq.iterator();
while (iterator.hasNext()) {
String freq = iterator.next();
if (freq.equals(mFreq)) {
iterator.remove();
}
}
return aFreq;
}
//获取String列表中mStr的下标
private int getIndexOfList(String mStr, List<String> lsit) {
int index = 0, i = 0;
for (String temp : lsit) {
if (temp.equals(mStr)) {
index = i;
}
i++;
}
return index;
}
代码比较多,注意关注下 onCreate 内的三个函数,其他都是在里面调用的。
结语
上面的代码和业务相关,设计的不是很好,但可以提供一个思路,特别是当时我开发的时候要求是三组、六频点选择,真是花了一番功夫。
希望上面代码对读者有所帮助把!
end