仿淘宝,京东多级地址选择器

作者: 达峰a | 来源:发表于2018-04-01 13:58 被阅读539次

    效果和淘宝地址选择一模一样,放个GIF。


    多级关联地址选择.gif

    效果是上周写出来的,gif录的早,后面又把选中item置顶加上了。

    先扯一下地址数据源问题,必须是层层关联,递进关系,例如省级用 01,02,03,到了市级01001,县区01001001,网上的数据大多数也是这个关系。
    github上有个数据源:中国省市区镇村三级四级五级联动地址数据
    [新增] 全国省市区县行政区划查询_易源数据

    思路:

    最开始,想用tablayout + viewpager 做,但是写了一半发现用viewpager代码量太多,太复杂了(viewpager中放fragment,里面再放recycleview),然后改用tablayout + recycleview ,好在recycleview部分代码不变,所以最开始也没白写。

    所以这个效果是用tablayout + recycleview写出来的,稍微提一下原生tablayout的用法(平时都用自定义的,原生都快忘了)
    XML:

    app:tabMode="scrollable"  从头显示,不会居中
    android:scrollbars="none" 去掉滑动到头的阴影效果
    

    添加,删除Tab:

    tablayout .addTab(tablayout .newTab().setText("请选择"), true); 添加tab,true表示立即选中添加的tab
    tablayout .getTabAt(position).setText(name);  给指定tab重新settext
    tablayout .removeTabAtposition);  删除指定tab
    int tabCount = tablayout .getTabCount(); 获取tab总数,注意这里不能.getChildCount
    

    其实这玩意儿就那么点东西,tablayout的增加删除,recycleview的重新绑定,还有的都是小细节。

    模块划分(1).jpg
    用户点击时,如果点击tablayout(红色块) 可以看为展示对应地址列表操作,这时候只需要刷新recycleview,如果点击的是recycleview的item(绿色块),则作为一个选择地址操作,不仅要刷新recycleview,还要添加删除tab。

    实施

    主要类.jpg

    看这个结构也知道没多少东西。
    布局还是看一眼:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#ffffff"
        android:orientation="vertical">
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:layout_marginTop="15dp">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:gravity="center"
                android:text="配送至"
                android:textColor="#000000"
                android:textSize="16sp" />
    
            <ImageView
                android:id="@+id/close"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:layout_marginRight="15dp"
                android:foreground="?android:attr/selectableItemBackground"
                android:padding="10dp"
                android:src="@mipmap/sic_close" />
        </RelativeLayout>
    
        <android.support.design.widget.TabLayout
            android:id="@+id/tablayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scrollbars="none"
            app:tabBackground="@drawable/item_bg"
            app:tabMode="scrollable"
            app:tabSelectedTextColor="#000000"
            app:tabTextAppearance="@style/ChooseView"
            app:tabTextColor="#000000" />
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:orientation="vertical">
    
            <utils.LoadingUtil.NewLoadingView
                android:id="@+id/loading"
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:visibility="gone" />
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recyclerview"
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:overScrollMode="never" />
        </LinearLayout>
    
    </LinearLayout>
    

    NewLoadingView是站位view,加载中,网络错误,其他异常用到的。

    弹窗我用的popupwindow,先创建一个:

        private PopupWindow popupWindow;
        private Context mContext;
        private Activity mActivity;
        private ImageView mIv_close;
        private TabLayout mTabLayout;
        private RecyclerView mRecyclerView;
        private NewLoadingView mLoadingView;
        //地址数据集合 数据源
        private List<AraeData> mAraeDatas;
        //地址数据集合  结果集
        private List<AraeDataResult> mResultList = new ArrayList<>();
        //标示  用来判断是否为最后一级数据
        private boolean isLast = false;
        //标示  用来区分是列表item点击 还是tab点击
        private boolean itemClick = false;
        //当前选中的tab
        private int mTabCurrent = 0;
        //地址选择完成的监听
        private OnSelectOkListener mlistener;
    
    
        //初始化
        private void init() {
            View pop_view = LayoutInflater.from(mContext).inflate(R.layout.address_choose_pop, null);
            popupWindow = new PopupWindow(pop_view, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            // 点击其他区域关闭
            popupWindow.setFocusable(true);
            popupWindow.setOutsideTouchable(true);
            popupWindow.setAnimationStyle(android.R.style.Animation_Toast);
            popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
                @Override
                public void onDismiss() {
                    Window window = mActivity.getWindow();
                    WindowManager.LayoutParams params = window.getAttributes();
                    params.alpha = 1.0F;
                    window.setAttributes(params);
                    if (isLast) {
                        mlistener.Select(mResultList);
                    }
                }
            });
            mIv_close = pop_view.findViewById(R.id.close);
            mTabLayout = pop_view.findViewById(R.id.tablayout);
            mLoadingView = pop_view.findViewById(R.id.loading);
            mRecyclerView = pop_view.findViewById(R.id.recyclerview);
            mIv_close.setOnClickListener(this);
            mTabLayout.addOnTabSelectedListener(this);
        }
    

    注意popup的dismiss监听,在每次dismiss时都会给外部发选择完成的地址(isLast决定是否发送)。

    • 网络获取数据用AraeData接收,类中有2个字段,地址id和地址名称
    • 用户选择地址的结果集存在AraeDataResult里面,相比地址集多了一个position字段,用来存放用户选中的item的position,方便把选中的item移动到视图顶部。

    最主要的,还是2个监听:
    recycleview的item点击监听
    tablayout切换监听(只用到3个方法中的onTabSelected)

    //recycleivew的item点击监听,也就是用户选择地址了
    RvAdapter.OnSelectorListener mListener = new RvAdapter.OnSelectorListener() {
        @Override
        public void setSelect(String name, String id, int sPosition) {
            int tabCount = mTabLayout.getTabCount();
            //重新点击新地址后  循环删除 旧tab
            if (mTabCurrent < tabCount - 1) {
                for (int i = 0; i < tabCount - 1 - mTabCurrent; i++) {
                    mTabLayout.removeTabAt(tabCount - 1 - i);
                    mResultList.remove(tabCount - 1 - i);
                }
            }
            mTabLayout.getTabAt(mTabCurrent).setText(name);
            mResultList.add(new AraeDataResult(id, name, sPosition));
    
            itemClick = true;
            getdata(id, "0", 0, false);
        }
    };
    
    //tablayout中某个tab选中的监听
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        mTabCurrent = tab.getPosition();
    
        if (itemClick) {
    
        } else {
            String nowID = "0";
            int sPosition = 0;
            int tabCount = mTabLayout.getTabCount();
            //如果是最后一个tab  则不需要默认选中 并且移动recycle位置
            if (mTabCurrent >= tabCount - 1) {
                nowID = "0";
                sPosition = 0;
            } else {
                nowID = mResultList.get(mTabCurrent + 1).getAreaId();
                sPosition = mResultList.get(mTabCurrent + 1).getPosition();
            }
            getdata(mResultList.get(mTabCurrent).getAreaId(), nowID, sPosition, true);
        }
    
    }
    
    //添加Tab
    private void addTab() {
        mTabLayout.addTab(mTabLayout.newTab().setText("请选择"), true);
    }
    
    /**
     * 网络获取数据
     *
     * @param pId       每级地址ID
     * @param nowId     当前选中的地址id   在recycleview的选中效果中使用
     * @param sposition 当前选中的item的position 用来把选中的item移动到第一位置
     * @param onlyShow  用来判断点击的为item 还是tab   如果是item 则要添加tab
     */
    private void getdata(final String pId, final String nowId, final int sposition, final boolean onlyShow) {
        isLast = false;
        HttpManager manager = new HttpManager.Builder();
        manager.setListener(new HttpManagerListen() {
            @Override
            public void onSucceed(String request) {
                mAraeDatas = GsonManager.getInstance().getGson().fromJson(request, new TypeToken<List<AraeData>>() {
                }.getType());
                if (mAraeDatas == null || mAraeDatas.size() == 0) {
                    //没有数据了  改变标示
                    isLast = true;
                    itemClick = false;
                }
                //无数据标示成立,说明是最后一级
                if (isLast) {
                    popupWindow.dismiss();
                    return;
                }
                //如果是点击item,则添加tab
                if (!onlyShow) {
                    addTab();
                }
    
                RvAdapter adapter = new RvAdapter(mContext, nowId, mAraeDatas, mListener);
                mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
                mRecyclerView.setAdapter(adapter);
    
                //显示item为视图第一个
                if (onlyShow) {
                    mRecyclerView.scrollToPosition(sposition);
                }
                itemClick = false;
            }
    

    全场的难点就在这2个监听里面,getdata()中是网络获取数据onSucceed请求成功这部分代码。

    • recycleivew的item点击监听里面有个for循环需要注意,这个主要针对用户已经选了几级地址的情况下,他要点击tab返回重新选,这个时候要删除掉已经选择的部分地址,从代码上来说就是remove掉部分tab,并且从选中地址的结果集mResultList中remove掉一部分。
    • tablayout中tab选中监听里面有一堆的判断,说明为什么有这些判断,因为用户点击item和点击tab是不一样的,相同点都是需要重新请求网络给recycleview绑定数据,不同点是如果点击的是item还另外需要添加tab,而我们添加tab中是默认选中添加的tab,这个时候又触发了tab 的点击,用判断把这2种点击事件的处理隔离开,每次仅仅处理一种点击。
      private boolean itemClick;就是为此而生的:
      1.如果确认用户点击的是item,则在选中tab的监听中不做操作。
      2.如果确认用户点击的是tab,则需要重新绑定tab那一级的数据给用户看,因为这一级数据是用户选中过的,我们还要通过nowID 和sPosition 字段,为用户在列表中标记并置顶选中的item。标记就是那个黄色对号,在recycleview的adapter中做出效果,置顶在getdata()方法中的最后scrollToPosition()。
    • getdata()中,如果接收到的数据为空,那么说明已经到了最后一级,就调用popupwin.dismiss关掉popup,而dimiss的监听中会把选择地址结果集mResultList 发出去。

    总结

    总体实现不难,需要注意的地方有:

    • recycleview单选实现以及item移动到顶部
    • tablayout子tab的管理
    • 选择结果集List<AraeDataResult> mResultList的维护
    • item点击处理与tab点击处理的隔离



    欢迎评论区提问
    点个赞呗


    对于生活理想,应该像宗教徒对待宗教一样充满虔诚与热情!

    相关文章

      网友评论

      • 啟风了:楼主有源码吗,发一份,邮箱499164592@qq.com,万分感谢
      • 风子_16ed:是如何网络获取数据的?
        达峰a:@风子_16ed 根据上一级的id,请求下一级数据,每个id保持全局唯一。
      • 自由小子0918:楼主能放其他的源码不?
        达峰a:@自由小子0918 代码没多少,就2个监听和adapter。

      本文标题:仿淘宝,京东多级地址选择器

      本文链接:https://www.haomeiwen.com/subject/kduocftx.html