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

效果和淘宝地址选择一模一样,放个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点击处理的隔离

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,566评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,200评论 0 17
  • 壬辰九月,值菊花怒放之时,"欣然"而入皖省示范之中学。期年之间,功无成,名不就。每夜深人静,独处一间,窃窃私泪...
    淮北师范大学石珠林阅读 370评论 0 0
  • 文章源地址:http://www.xiaomeiti.com/note/3625 这里要说的是这个推送证书的有效期...
    puple瞳眸阅读 2,072评论 0 3
  • 宝宝是农历正月初七的生日。回顾宝宝出生的日子,最大的感受是:幸福、喜悦,累。 镜头一:腊月二十八产检,胎心胎动都正...
    evergbo阅读 538评论 0 0