欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

仿京东地址选择器

程序员文章站 2022-05-25 08:18:44
...

一、思路

看页面效果可以知道,上层Tab为选择的地址,下层为省、市、区、街道等数据,那么这种效果可以使用TabLayout+Fragment+ViewPager实现。一级数据一个Tab+Fragment,Tab为选择的或对应的数据含义(即:Tab(省)、Tab(市)、Tab(区)、Tab(街道)), Fragment为对应的数据列表。初始化的时候自动生成一个Tab+Fragment,往后选择一个地址如(广西壮族自治区)生成一个新的Tab+Fragment,如果重新选择地址,先清空对应的下级Tab+Fragment再按原来的逻辑生成新的Tab+Fragment。例如:开始的时候选择广西壮族自治区->贵港市->平南县,这时候应该分别生成省Tab+Fragment、市Tab+Fragment、区/县Tab+Fragment这三个,重新选择省(北京)的时候,市级、区/县级的Tab+Fragment都移除,然后再重新生成省(北京)对应的市级Tab+Fragment,然后使用ViewPager切换到生成的Fragment。

生成Tab+Fragment关键代码:

    public void setProvinceData(List<AddressListBean> provinceData) {
        tabLayout.addTab(tabLayout.newTab().setText("请选择"));
        AddressListFragment provinceFrag = new AddressListFragment();
        provinceFrag.setAddressData(provinceData);
        provinceFrag.setAddressListClickListener(new AddressListClickListener() {
            @Override
            public void onClick(AddressListBean address) {
                provinceBean = address;
                cityBean = null;
                countryBean = null;
                streetBean = null;
                //清除原有省份对应的市级、县级、镇级(街道)数据
                adapter.clearDataByProvince();
                tabLayout.getTabAt(0).setText(address.getName());
                while (tabLayout.getTabCount() > 1) {
                    tabLayout.removeTabAt(1);
                }
                if (mOnProvinceClickListener != null) {
                    mOnProvinceClickListener.onClick(address);
                }
            }
        });
        adapter.addTab(provinceFrag);
    }

二、TabLayout使用

TabLayout新增Tab:

tabLayout.addTab(tabLayout.newTab().setText("请选择"));

由于TabLayout没有动态修改Tab字体、style、指示器颜色的API,需要使用反射实现。

    public static void updateTabTextView(TabLayout.Tab tab, boolean isSelect) {
     if (isSelect) {
         try {
             java.lang.reflect.Field fieldView = tab.getClass().getDeclaredField("mView");
             fieldView.setAccessible(true);
             View view = (View) fieldView.get(tab);
             java.lang.reflect.Field fieldTxt = view.getClass().getDeclaredField("mTextView");
             fieldTxt.setAccessible(true);
             TextView tabSelect = (TextView) fieldTxt.get(view);
             tabSelect.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
             tabSelect.setText(tab.getText());
         } catch (Exception e) {
             e.printStackTrace();
         }
     } else {
         try {
             java.lang.reflect.Field fieldView = tab.getClass().getDeclaredField("mView");
             fieldView.setAccessible(true);
             View view = (View) fieldView.get(tab);
             java.lang.reflect.Field fieldTxt = view.getClass().getDeclaredField("mTextView");
             fieldTxt.setAccessible(true);
             TextView tabSelect = (TextView) fieldTxt.get(view);
             tabSelect.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
             tabSelect.setText(tab.getText());
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }

    public static void changeTextColor(TabLayout tabLayout, int resColor) {
     if (resColor == 0) {
         return;
     }
     try {
         //拿到tabLayout的mTabStrip属性
         Field field = TabLayout.class.getDeclaredField("mTabStrip");
         field.setAccessible(true);
         //拿mTabStrip属性里面的值
         Object mTabStrip = field.get(tabLayout);
         //通过mTabStrip对象来获取class类,不能用field来获取class类,参数不能写Integer.class
         Method method = mTabStrip.getClass().getDeclaredMethod("setSelectedIndicatorColor", int.class);
         method.setAccessible(true);
         method.invoke(mTabStrip, resColor);
     } catch (Exception e) {
         e.printStackTrace();
     }
 }

三、ViewPager

ViewPager来进行Fragment的切换,由于ViewPager自带缓存,所以使用常规的FragmentPagerAdapter来进行Fragment切换时会存在一个问题,Fragment被替换之后展示的还是原来的Fragment数据。比如原本选择的是省(广西壮族自治区)、市(贵港市),然后点击Tab切换到省对应的Fragment下,选择省(北京)、市(市辖区),这时候会发现省(北京)对应的数据还是广西对应的市级数据。需要重写getItemPosition、instantiateItem、destroyItem来解决:

 @Override
 public int getItemPosition(Object object) {
     if (!((Fragment) object).isAdded() || !fragments.contains(object)) {
         return PagerAdapter.POSITION_NONE;
     }
     return fragments.indexOf(object);
 }
 @Override
 public Object instantiateItem(ViewGroup container, int position) {
     Fragment instantiateItem = ((Fragment) super.instantiateItem(container, position));
     Fragment item = fragments.get(position);
     if (instantiateItem == item) {
         return instantiateItem;
     } else {
         //如果集合中对应下标的fragment和fragmentManager中的对应下标的fragment对象不一致,那么就是新添加的,所以自己add进入;这里为什么不直接调用super方法呢,因为fragment的mIndex搞的鬼,以后有机会再补一补。
         fm.beginTransaction().add(container.getId(), item).commitNowAllowingStateLoss();
         return item;
     }
 }
 @Override
 public void destroyItem(ViewGroup container, int position, Object object) {
     Fragment fragment = (Fragment) object;
     //如果getItemPosition中的值为PagerAdapter.POSITION_NONE,就执行该方法。
     if (fragments.contains(fragment)) {
         super.destroyItem(container, position, fragment);
         return;
     }
     //自己执行移除。因为mFragments在删除的时候就把某个fragment对象移除了,所以一般都得自己移除在fragmentManager中的该对象。
     fm.beginTransaction().remove(fragment).commitNowAllowingStateLoss();
 }
四、自定义View

简单定义AddressPickerView,实现用户直接使用该View即可实现地址选择,支持使用默认的地址数据、外部传入数据两种方式,可以根据需求修改指示器颜色。
添加View对应的属性:

<declare-styleable name="AddressPickerView">
       <attr name="dataMode" format="enum">
           <enum name="def" value="0"/>
           <enum name="outside" value="1"/>
       </attr>
       <attr name="tabIndicatorColor" format="color"/>
 </declare-styleable>

关键代码:

    public AddressPickerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
       super(context, attrs, defStyleAttr);
       TypedArray array=context.obtainStyledAttributes(attrs,R.styleable.AddressPickerView);
       int mode=array.getInt(R.styleable.AddressPickerView_dataMode,0);
       resColor=array.getColor(R.styleable.AddressPickerView_tabIndicatorColor,0);
       if (mode==1){
           mPickDataMode=PICK_DATA_MODE.OUTSIDE;
       }
       array.recycle();
       initView();
       setListener();
       initData();
   }

五、三级、四级、五级地址数据

*行政区划:省级(省份直辖市自治区)、 地级(城市)、 县级(区县)、 乡级(乡镇街道)、 村级(村委会居委会) ,中国省市区镇村二级三级四级五级联动地址数据 Node.js 爬虫。
数据地址

六、效果

仿京东地址选择器
仿京东地址选择器

本案例代码下载地址:Demo