在项目开发中或多或少都会遇到地址的选择如淘宝,美团等。在ios中有PickerView可以很轻松的实现地区之间的轮滑和级联,在Android中,并没有可以直接实现此功能的控件,为了实现功能和效果的美观,只有通过自定义控件。在GitHub上有一个叫做Android-wheel的开源控件:https://github.com/maarek/android-wheel。也可以使用TimePickerDialog:https://github.com/JZXiang/TimePickerDialogTimePickerDialog集成了Android-wheel。Android-wheel是一个非常好用的组件,对于数据适配接口的抽取和事件的回调都做了抽取,代码的耦合度低,唯一不足就是在界面的定制这块,通过源码来实现的三级联动开起来比较丑如果想要体现出更好的效果,则需要自己手动的修改源码。下面是实际项目中的一个项目:
1、下载Android-wheel项目,并导入wheel依赖
2、讲数据文件本项目中为json格式的如下:
[ { "parent_id": "1", "region_id": "2", "region_name": "北京", "sub": [ { "parent_id": "2", "region_id": "52", "region_name": "北京", "sub": [ { "parent_id": "52", "region_id": "500", "region_name": "东城区" }, { "parent_id": "52", "region_id": "501", "region_name": "西城区" },
3、在XML布局中设置WheelView的控件,如下图:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="请选择城市" android:gravity="center"/> <LinearLayout android:id="@+id/line" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <kankan.wheel.widget.WheelView android:id = "@+id/province" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"/> <kankan.wheel.widget.WheelView android:id = "@+id/city" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"/> <kankan.wheel.widget.WheelView android:id = "@+id/area" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"/> </LinearLayout> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="确定" android:layout_gravity="right"/> </LinearLayout>4、主函数(在这里城市的选择是在PopupWindow中通过点击弹出来的)
public class MainActivity extends AppCompatActivity implements OnWheelChangedListener { /** * 所有省 */ private String[] mProvinceDatas; /** * key - 省 value - 市s */ private Map<String, String[]> mCitisDatasMap = new HashMap<String, String[]>(); /** * key - 市 values - 区s */ private Map<String, String[]> mAreaDatasMap = new HashMap<String, String[]>(); /** * 省的名称和id * key--name values--ID */ private Map<String,String> mProvinceName_Id = new HashMap<>(); /** * 市的名称和id * key--name values--ID */ private Map<String,String> mCityName_Id = new HashMap<>(); /** * 区的名称和id * key--name values--ID */ private Map<String,String> mAreaName_Id = new HashMap<>(); /** * 当前省的名称 */ private String mCurrentProviceName; /** * 当前市的名称 */ private String mCurrentCityName; /** * 当前区的名称 */ private String mCurrentAreaName =""; private String mJson; private WheelView mMProvince; private WheelView mCity; private WheelView mMArea; private Button mMButton; private TextView mAddressText; private TextView mAddressIdText; private LinearLayout mMLine; private PopupWindow mMPopupWindow; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mAddressText = (TextView) findViewById(R.id.address); mAddressIdText = (TextView)findViewById(R.id.address_id); init(); } public void btnClick(View view){ getSelect(); showData(); } //显示PopupWindow窗口 private void showData() { //获取父布局 View rootView = LayoutInflater.from(this).inflate(R.layout.activity_main,null); //相对父布局的位置在底部弹出 mMPopupWindow.showAtLocation(rootView, Gravity.BOTTOM, 0, 0); } //初始化PopupWindow private void init(){ View v = LayoutInflater.from(this).inflate(R.layout.popwindow_layout,null); //设置弹窗的宽和高 mMPopupWindow = new PopupWindow(v, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); //点击外部弹窗消失 mMPopupWindow.setBackgroundDrawable(new BitmapDrawable()); mMPopupWindow.setOutsideTouchable(true); mMLine = (LinearLayout)v. findViewById(R.id.line); //省的控件 mMProvince = (WheelView)v. findViewById(R.id.province); //市的控件 mCity = (WheelView)v. findViewById(R.id.city); //区的控件 mMArea = (WheelView)v. findViewById(area); mMButton = (Button)v. findViewById(R.id.btn); //点击确定按钮,把选择的地址返回,同事弹窗消失 mMButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showChoose(v); getValue(); //把当前所选地赋值给mAddressText mAddressText.setText(mCurrentProviceName+"--"+mCurrentCityName+"--"+mCurrentAreaName); mMPopupWindow.dismiss(); } }); } //PopupWindow界面的显示效果 public void getSelect(){ String json = initJsonData(); parseJson(json); mMProvince.setViewAdapter(new ArrayWheelAdapter<String>(this,mProvinceDatas)); //添加change事件 mMProvince.addChangingListener(this); //添加change事件 mCity.addChangingListener(this); //添加change事件 mMArea.addChangingListener(this); //同一时间可见的item个数 mMProvince.setVisibleItems(6); mCity.setVisibleItems(6); mMArea.setVisibleItems(6); updateCities(); updateAreas(); } /** * 根据当前的市,更新区 WheelView的信息 */ private void updateAreas(){ int pCurrent = mCity.getCurrentItem(); //当前市的名称 mCurrentCityName = mCitisDatasMap.get(mCurrentProviceName)[pCurrent]; String[] areas = mAreaDatasMap.get(mCurrentCityName); if(areas == null){ areas = new String[]{""}; } mMArea.setViewAdapter(new ArrayWheelAdapter<String>(this,areas)); mMArea.setCurrentItem(0); //得到区名字的初始值为第0个位置的值 mCurrentAreaName = mAreaDatasMap.get(mCurrentCityName)[0]; } /** * 根据当前的省,更新市WheelView的信息 */ private void updateCities() { int pCurrent = mMProvince.getCurrentItem(); //当前省的名称 mCurrentProviceName = mProvinceDatas[pCurrent]; String[] cities = mCitisDatasMap.get(mCurrentProviceName); if (cities == null) { cities = new String[] { "" }; } mCity.setViewAdapter(new ArrayWheelAdapter<String>(this, cities)); mCity.setCurrentItem(0); updateAreas(); } //读取本地json生成json字符串 private String initJsonData(){ try { InputStream is = getResources().getAssets().open("地区json.json"); byte[] buffer = new byte[is.available()]; is.read(buffer); mJson = new String(buffer,"UTF-8"); } catch (IOException e) { e.printStackTrace(); } return mJson; } //解析json,并把解析出来的数据存放到不同的集合和Map中 private void parseJson(String str){ try { JSONArray jsonArray = new JSONArray(str); mProvinceDatas = new String[jsonArray.length()]; for(int i = 0;i<jsonArray.length();i++){ JSONObject jsonp = jsonArray.getJSONObject(i); String provinceName = jsonp.getString("region_name"); String provinceId = jsonp.getString("region_id"); mProvinceName_Id.put(provinceName,provinceId); mProvinceDatas[i] = provinceName; JSONArray jsonc = jsonp.getJSONArray("sub"); String [] citiesData = new String[jsonc.length()]; for(int j = 0;j<jsonc.length();j++){ JSONObject jsoncity = jsonc.getJSONObject(j); String cityName = jsoncity.getString("region_name"); String cityId = jsoncity.getString("region_id"); mCityName_Id.put(cityName,cityId); citiesData[j] = cityName; JSONArray jsona = jsoncity.getJSONArray("sub"); String [] areaData = new String[jsona.length()]; for(int k = 0;k<jsona.length();k++){ String areaName = jsona.getJSONObject(k).getString("region_name"); String areaId = jsona.getJSONObject(k).getString("region_id"); mAreaName_Id.put(areaName,areaId); areaData[k] = areaName; } mAreaDatasMap.put(cityName,areaData); } mCitisDatasMap.put(provinceName,citiesData); } } catch (JSONException e) { e.printStackTrace(); } } //活动省或市或区的时候触发 @Override public void onChanged(WheelView wheel, int oldValue, int newValue) { if(wheel == mMProvince){ updateCities(); }else if(wheel == mCity){ updateAreas(); }else if(wheel == mMArea){ mCurrentAreaName = mAreaDatasMap.get(mCurrentCityName)[newValue]; } } //获得选择地址的id private void getValue(){ //获取省的ID Object provinceId = new Object(); provinceId = mProvinceName_Id.get(mCurrentProviceName); //获取市的ID Object cityId = new Object(); cityId = mCityName_Id.get(mCurrentCityName); //获取区的ID Object areaId = new Object(); areaId = mAreaName_Id.get(mCurrentAreaName); //将ID赋值给TextView(传服务器) mAddressIdText.setText(provinceId+"----"+cityId+"----"+areaId); } public void showChoose(View view) { Toast.makeText(this, mCurrentProviceName + mCurrentCityName + mCurrentAreaName, Toast.LENGTH_LONG).show(); } }由于代码中都有详细的注释,这里不再赘述,源生的代码所取得的结果如下:
通过更改源码可以使得选择器更简洁漂亮一些,我做出的效果如下: