仿淘寶,京東多級地址選擇器

效果和淘寶地址選擇如出一轍,放個GIF。 android

多級關聯地址選擇.gif
效果是上週寫出來的,gif錄的早,後面又把選中item置頂加上了。

先扯一下地址數據源問題,必須是層層關聯,遞進關係,例如省級用 01,02,03,到了市級01001,縣區01001001,網上的數據大多數也是這個關係。github上有個數據源:中國省市區鎮村三級四級五級聯動地址數據git

思路:

最開始,想用tablayout + viewpager 作,可是寫了一半發現用viewpager代碼量太多,太複雜了(viewpager中放fragment,裏面再放recycleview),而後改用tablayout + recycleview ,好在recycleview部分代碼不變,因此最開始也沒白寫。github

因此這個效果是用tablayout + recycleview寫出來的,稍微提一下原生tablayout的用法(平時都用自定義的,原生都快忘了)bash

XML:網絡

app:tabMode="scrollable"  從頭顯示,不會居中
android:scrollbars="none" 去掉滑動到頭的陰影效果
複製代碼

添加,刪除Tab:app

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的從新綁定,還有的都是小細節。 ide

模塊劃分(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,先建立一個:ui

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決定是否發送)。this

  • 網絡獲取數據用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 mResultList的維護
  • item點擊處理與tab點擊處理的隔離


對於生活理想,應該像宗教徒對待宗教同樣充滿虔誠與熱情!
相關文章
相關標籤/搜索