Android实现四级联动地址选择器
迷路国王 人气:0效果如下:
代码传送阵:
address_selector.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/ll_container" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:alpha="0" android:background="#b2000000" android:orientation="vertical" tools:alpha="1"> <android.support.constraint.ConstraintLayout android:id="@+id/cl_container" android:clickable="true" android:focusable="true" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/selector_bg"> <TextView android:id="@+id/textViewProvince" android:layout_width="wrap_content" android:layout_height="@dimen/dp_42" android:layout_marginStart="@dimen/dp_18" android:ellipsize="end" android:paddingTop="@dimen/dp_13" android:maxWidth="@dimen/dp_80" android:maxLines="1" android:text="请选择" android:textColor="@drawable/selector_item_text_color" android:textSize="@dimen/sp_14" android:textStyle="bold" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="请选择请选择" tools:visibility="visible" /> <TextView android:id="@+id/textViewCity" android:layout_width="wrap_content" android:layout_height="@dimen/dp_44" android:layout_marginStart="@dimen/dp_21" android:ellipsize="end" android:paddingTop="@dimen/dp_13" android:maxWidth="@dimen/dp_80" android:maxLines="1" android:text="请选择" android:textColor="@drawable/selector_item_text_color" android:textSize="@dimen/sp_14" android:textStyle="bold" android:visibility="invisible" app:layout_constraintLeft_toRightOf="@id/textViewProvince" app:layout_constraintTop_toTopOf="parent" tools:text="请选择请选择" tools:visibility="visible" /> <TextView android:id="@+id/textViewArea" android:layout_width="wrap_content" android:layout_height="@dimen/dp_44" android:layout_marginStart="@dimen/dp_21" android:ellipsize="end" android:paddingTop="@dimen/dp_13" android:maxWidth="@dimen/dp_80" android:maxLines="1" android:text="请选择" android:textColor="@drawable/selector_item_text_color" android:textSize="@dimen/sp_14" android:textStyle="bold" android:visibility="invisible" app:layout_constraintLeft_toRightOf="@id/textViewCity" app:layout_constraintTop_toTopOf="parent" tools:text="请选择请选择" tools:visibility="visible" /> <TextView android:id="@+id/textViewStreet" android:layout_width="wrap_content" android:layout_height="@dimen/dp_44" android:layout_marginStart="@dimen/dp_21" android:ellipsize="end" android:paddingTop="@dimen/dp_13" android:maxWidth="@dimen/dp_80" android:maxLines="1" android:text="请选择" android:textColor="@drawable/selector_item_text_color" android:textSize="@dimen/sp_14" android:textStyle="bold" android:visibility="invisible" app:layout_constraintLeft_toRightOf="@id/textViewArea" app:layout_constraintTop_toTopOf="parent" tools:text="请选择请选择" tools:visibility="visible" /> <android.support.v7.widget.AppCompatImageView android:id="@+id/iv_close" android:layout_width="@dimen/dp_23" android:layout_height="@dimen/dp_23" android:layout_marginTop="@dimen/dp_12" android:layout_marginEnd="@dimen/dp_12" android:padding="@dimen/dp_5" android:src="@drawable/address_close" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <View android:id="@+id/indicator" android:layout_width="@dimen/dp_24" android:layout_height="@dimen/dp_2" android:background="#000000" app:layout_constraintLeft_toLeftOf="@id/textViewProvince" app:layout_constraintTop_toBottomOf="@id/textViewProvince" /> <View android:id="@+id/divider" android:layout_width="match_parent" android:layout_height="0.5dp" android:background="#e5e5e5" app:layout_constraintTop_toBottomOf="@id/indicator" /> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/divider" /> </android.support.constraint.ConstraintLayout> </LinearLayout>
AddressDialog.java
public class AddressDialog extends DialogFragment { @BindView(R.id.textViewProvince) TextView textViewProvince; @BindView(R.id.textViewCity) TextView textViewCity; @BindView(R.id.textViewArea) TextView textViewArea; @BindView(R.id.textViewStreet) TextView textViewStreet; @BindView(R.id.iv_close) AppCompatImageView ivClose; @BindView(R.id.indicator) View indicator; @BindView(R.id.divider) View divider; @BindView(R.id.recycler_view) RecyclerView recyclerView; @BindView(R.id.cl_container) ConstraintLayout clContainer; @BindView(R.id.ll_container) LinearLayout llContainer; Unbinder unbinder; private CompositeDisposable compositeDisposable = new CompositeDisposable(); private float measuredHeight = 0F; // 当前tab 0省1市2区 private int currentTab = 0; // 省 private ArrayList<County> proviceList = new ArrayList<>(); // 市 private ArrayList<County> cityList = new ArrayList<>(); private ConcurrentHashMap<String, ArrayList<County>> cityMap = new ConcurrentHashMap<>(); // 区 private ArrayList<County> areaList = new ArrayList<>(); private ConcurrentHashMap<String, ArrayList<County>> areaMap = new ConcurrentHashMap<>(); //街道 private ArrayList<County> streetList = new ArrayList<>(); private ConcurrentHashMap<String, ArrayList<County>> streetMap = new ConcurrentHashMap<>(); private BaseQuickAdapter<County, BaseViewHolder> mAdapter; private OnSelectListener listener; private String textProvince = ""; private String textCity = ""; private String textArea = ""; private String textStreet = ""; private ProgressDialog mProgressDialog; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.address_selector, container, false); unbinder = ButterKnife.bind(this, view); return view; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setStyle(STYLE_NO_TITLE, R.style.Style_Dialog_Transparent); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); textViewProvince.setSelected(true); float measureText = textViewProvince.getPaint().measureText("请选择"); indicator.getLayoutParams().width = (int) measureText; LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)clContainer.getLayoutParams(); layoutParams.topMargin = listener.getHeight() == 0 ? UIUtil.dip2px(MyApplication.applicationContext,180f) :listener.getHeight(); clContainer.setLayoutParams(layoutParams); initData(); initClick(); ViewUtils.postMeasured(clContainer, new Runnable() { @Override public void run() { llContainer.animate().alpha(1f).setDuration(150).start(); float measuredHeight = (float)clContainer.getMeasuredHeight(); ObjectAnimator translationY = ObjectAnimator.ofFloat(clContainer, "translationY", measuredHeight, 0f); translationY.setDuration(150); translationY.setInterpolator(new LinearInterpolator()); translationY.start(); } }); } private void initData() { recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); mAdapter=new BaseQuickAdapter<County, BaseViewHolder>(R.layout.item_area) { @Override protected void convert(@NonNull BaseViewHolder helper, final County item) { helper.setText(R.id.textView,item.name); helper.getConvertView().setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { switch (currentTab) { case 0: getCityList(new Consumer<ArrayList<County>>() { @Override public void accept(ArrayList<County> counties) throws Exception { // 本地是否存在 if (cityMap.containsKey(item.code)){ ArrayList<County> list = cityMap.get(item.code); if (list==null) { list=new ArrayList<>(); } notifyAdapter(list); }else { for (County next : cityList) { if (cityMap.containsKey(item.code) && item.code.equals(next.provinceCode)) { ArrayList<County> list = cityMap.get(item.code); if (list != null) { list.add(next); cityMap.put(next.provinceCode, list); } } else { @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") ArrayList<County> listCounty = new ArrayList<>(); listCounty.add(next); cityMap.put(next.provinceCode, listCounty); } } } ArrayList<County> list = cityMap.get(item.code); notifyAdapter(list); } }); break; case 1: getAreaList(new Consumer<ArrayList<County>>() { @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") @Override public void accept(ArrayList<County> list) throws Exception { if (areaMap.containsKey(item.code)) { ArrayList<County> arrayList = areaMap.get(item.code); if (arrayList==null) { arrayList =new ArrayList<>(); } notifyAdapter(arrayList); }else { for (County county : areaList) { if (areaMap.containsKey(item.code) && item.code.equals(county.cityCode)) { ArrayList<County> countyArrayList = areaMap.get(item.code); if (countyArrayList != null) { countyArrayList.add(county); areaMap.put(county.code, countyArrayList); } }else { ArrayList<County> arrayList = new ArrayList<>(); arrayList.add(county); areaMap.put(county.cityCode,arrayList); } } } ArrayList<County> arrayList = areaMap.get(item.code); notifyAdapter(arrayList); } }); break; case 2: if (mProgressDialog == null) { mProgressDialog = new ProgressDialog(getActivity()); } mProgressDialog.setCanceledOnTouchOutside(false); mProgressDialog.setCancelable(true); mProgressDialog.show(); getStreetList(new Consumer<ArrayList<County>>() { @Override public void accept(ArrayList<County> list) throws Exception { if (streetMap.containsKey(item.code)) { ArrayList<County> arrayList = streetMap.get(item.code); if (arrayList==null) { arrayList =new ArrayList<>(); } notifyAdapter(arrayList); }else { for (County county : streetList) { if (streetMap.containsKey(item.code) && item.code.equals(county.areaCode)) { ArrayList<County> countyArrayList = streetMap.get(item.code); if (countyArrayList != null) { countyArrayList.add(county); streetMap.put(county.code, countyArrayList); } }else { ArrayList<County> arrayList = new ArrayList<>(); arrayList.add(county); streetMap.put(county.areaCode,arrayList); } } } ArrayList<County> arrayList = streetMap.get(item.code); notifyAdapter(arrayList); if (mProgressDialog != null) { ThreadHelper.getInstance().runOnUiPostDelayed(new Runnable() { @Override public void run() { mProgressDialog.dismiss(); } },1000); } } }); } upTabStatus(item.name); indicatorAnim(); } }); } }; recyclerView.setAdapter(mAdapter); getProvinceList(new Consumer<ArrayList<County>>() { @Override public void accept(ArrayList<County> list) throws Exception { notifyAdapter(proviceList); } }); } private void initClick() { ivClose.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { closeDialog(); } }); llContainer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { closeDialog(); } }); } private void notifyAdapter(ArrayList<County> list){ mAdapter.getData().clear(); mAdapter.addData(list); } //indicator 动画 private void indicatorAnim(){ switch (currentTab){ case 1: float text = textViewProvince.getPaint().measureText(textProvince); indicator.animate().translationX(text+UIUtil.dip2pxf(Objects.requireNonNull(getContext()),21f)).setDuration(200).start(); break; case 2: float measureText = textViewProvince.getPaint().measureText(textProvince); float measureText1 = textViewCity.getPaint().measureText(textCity); indicator.animate().translationX(measureText+measureText1+UIUtil.dip2pxf(Objects.requireNonNull(getContext()),42f)).setDuration(200).start(); break; case 3: float measure = textViewProvince.getPaint().measureText(textProvince); float measure1 = textViewCity.getPaint().measureText(textCity); float measure2 = textViewArea.getPaint().measureText(textArea); indicator.animate().translationX(measure+measure1+measure2+UIUtil.dip2pxf(Objects.requireNonNull(getContext()),63f)).setDuration(200).start(); } } private void upTabStatus(String name){ switch (currentTab){ case 0: textProvince=name; textViewProvince.setText(name); textViewCity.setVisibility(View.VISIBLE); currentTab =1; break; case 1: textCity=name; textViewCity.setText(name); textViewArea.setVisibility(View.VISIBLE); currentTab =2; break; case 2: textArea=name; textViewArea.setText(name); textViewStreet.setVisibility(View.VISIBLE); currentTab=3; break; case 3: textStreet=name; textViewStreet.setText(name); dismissAllowingStateLoss(); break; } textViewProvince.setSelected(currentTab==0); textViewCity.setSelected(currentTab==1); textViewArea.setSelected(currentTab==2); textViewStreet.setSelected(currentTab==3); } private void getProvinceList(Consumer<ArrayList<County>> consumer){ Disposable subscribe1 = Observable.create((new ObservableOnSubscribe() { public final void subscribe(@NonNull ObservableEmitter it) { if (proviceList.isEmpty()) { String json = getJson("address/provinces.json", getContext()); ArrayList<County> list = fromJson(json,new TypeToken<ArrayList<County>>(){}.getType()); if (list==null) { list =new ArrayList<>(); } proviceList.addAll(list); } if (!proviceList.isEmpty()) { it.onNext(proviceList); } } })).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(consumer, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { } }); compositeDisposable.add(subscribe1); } private void getCityList(Consumer<ArrayList<County>> consumer){ Disposable subscribe1 = Observable.create((new ObservableOnSubscribe() { public final void subscribe(@NonNull ObservableEmitter it) { if (cityList.isEmpty()) { String json = getJson("address/cities.json", getContext()); ArrayList<County> list = fromJson(json,new TypeToken<ArrayList<County>>(){}.getType()); if (list==null) { list =new ArrayList<>(); } cityList.addAll(list); } if (!cityList.isEmpty()) { it.onNext(cityList); } } })).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(consumer, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { } }); compositeDisposable.add(subscribe1); } private void getAreaList(Consumer<ArrayList<County>> consumer){ Disposable subscribe1 = Observable.create((new ObservableOnSubscribe() { public final void subscribe(@NonNull ObservableEmitter it) { if (areaList.isEmpty()) { String json = getJson("address/areas.json", getContext()); ArrayList<County> list = fromJson(json,new TypeToken<ArrayList<County>>(){}.getType()); if (list==null) { list =new ArrayList<>(); } areaList.addAll(list); } if (!areaList.isEmpty()) { it.onNext(areaList); } } })).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(consumer, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { } }); compositeDisposable.add(subscribe1); } private void getStreetList(Consumer<ArrayList<County>> consumer){ Disposable subscribe1 = Observable.create((new ObservableOnSubscribe() { public final void subscribe(@NonNull ObservableEmitter it) { if (streetList.isEmpty()) { String json = getJson("address/streets.json", getContext()); ArrayList<County> list = fromJson(json,new TypeToken<ArrayList<County>>(){}.getType()); if (list==null) { list =new ArrayList<>(); } streetList.addAll(list); } if (!streetList.isEmpty()) { it.onNext(streetList); } } })).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(consumer, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { } }); compositeDisposable.add(subscribe1); } public String getJson(String fileName, Context context) { StringBuilder stringBuilder = new StringBuilder(); try { InputStream is = context.getAssets().open(fileName); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line); } } catch (IOException e) { e.printStackTrace(); } return stringBuilder.toString(); } @Nullable public <T> T fromJson(String json, Type typeOfT) { try { return initGson().fromJson(json, typeOfT); } catch (JsonSyntaxException e) { e.printStackTrace(); } return null; } private Gson initGson(){ GsonBuilder gsonBuilder = new GsonBuilder(); try { Class builder = gsonBuilder.getClass(); Field f = builder.getDeclaredField("instanceCreators"); f.setAccessible(true); Map<Type, InstanceCreator<?>> val = (Map<Type, InstanceCreator<?>>) f.get(gsonBuilder);//得到此属性的值 //注册数组的处理器 ConstructorConstructor constructorConstructor = new ConstructorConstructor(val); // gsonBuilder.registerTypeAdapterFactory(new ReflectiveTypeAdapterFactory(constructorConstructor, FieldNamingPolicy.IDENTITY, // Excluder.DEFAULT,new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor))); gsonBuilder.registerTypeAdapterFactory(new MapTypeAdapterFactory(constructorConstructor,false)); gsonBuilder.registerTypeAdapterFactory(new CollectionTypeAdapterFactory(constructorConstructor)); // gsonBuilder.registerTypeAdapter(String.class,new StringTypeAdapter()); gsonBuilder.registerTypeAdapter(int.class,new IntegerTypeAdapter()); gsonBuilder.registerTypeAdapter(Integer.class,new IntegerTypeAdapter()); // gsonBuilder.registerTypeAdapter(long.class,new LongTypeAdapter()); // gsonBuilder.registerTypeAdapter(Long.class,new LongTypeAdapter()); // gsonBuilder.registerTypeAdapter(double.class,new DoubleTypeAdapter()); // gsonBuilder.registerTypeAdapter(Double.class,new DoubleTypeAdapter()); // gsonBuilder.registerTypeAdapter(boolean.class,new BooleanTypeAdapter()); // gsonBuilder.registerTypeAdapter(Boolean.class,new BooleanTypeAdapter()); // gsonBuilder.registerTypeAdapter(float.class,new FloadTypeAdapter()); // gsonBuilder.registerTypeAdapter(Float.class,new FloadTypeAdapter()); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return gsonBuilder.enableComplexMapKeySerialization().create(); } public void closeDialog() { clContainer.animate().translationY(measuredHeight).setDuration(150).withEndAction(new Runnable() { @Override public void run() { dismissAllowingStateLoss(); } }).start(); } public void dismissAllowingStateLoss() { super.dismissAllowingStateLoss(); if (!TextUtils.isEmpty(textProvince) && !TextUtils.isEmpty(textCity) && !TextUtils.isEmpty(textArea)&&!TextUtils.isEmpty(textStreet)) { listener.onSelect(textProvince, textCity, textCity,textStreet); } listener.isMissing(); compositeDisposable.dispose(); } public void setOnSelectListener(OnSelectListener listener) { this.listener = listener; } public void setHeight(int top) { } @Override public void onDestroyView() { super.onDestroyView(); unbinder.unbind(); } public interface OnSelectListener { int getHeight(); void onSelect(String province, String city, String area,String street); void isMissing(); } }
使用方法
private void addressDialog() { AddressDialog addressDialog =new AddressDialog(); addressDialog.setOnSelectListener(new AddressDialog.OnSelectListener() { @Override public int getHeight() { return 0; } @SuppressLint("SetTextI18n") @Override public void onSelect(String province, String city, String area, String street) { tvPositionRtrCommunity.setText(province+" "+city+" "+area+" "+street); } @Override public void isMissing() { isShowDialog = true; } }); addressDialog.show(getSupportFragmentManager(),AddressDialog.class.getSimpleName()); }
资源下载:四级联动地址选择器
加载全部内容