1. 程式人生 > >【Android】快速實現仿美團選擇城市介面,微信通訊錄介面

【Android】快速實現仿美團選擇城市介面,微信通訊錄介面

概述

本文是這個系列的第三篇,不出意外也是終結篇。因為使用經過重構後的控制元件已經可以快速實現市面上帶 索引導航、懸停分組的列表介面了。
在前兩篇裡,我們從0開始,一步一步實現了仿微信通訊錄、餓了麼選餐介面。
第一篇戳我 第二篇戳我
這篇文章作為終結篇,和前文相比,主要涉及以下內容:

  • 重構懸停分組,將TitleItemDecoration更名為SuspensionDecoration,資料來源依賴ISuspensionInterface介面。
  • 重構索引導航,將IndexBar對資料來源的操作,如排序,轉拼音等分離出去,以介面IIndexBarDataHelper通訊。
  • 有N多兄弟給我留言、加QQ問的:如何實現美團選擇城市列表頁面,
  • 新增一個不帶懸停分組的HeaderView(微信通訊錄介面)

老規矩,先上圖:

美團選擇城市介面,先重新整理Body主體資料,再定向重新整理頭部資料

微信通訊錄介面

本文將先舉例子如何寫,並對其中涉及到的重構部分進行講解。
如有不明者,建議先觀看(第一篇戳我 第二篇戳我),
以及下載Demo,邊看程式碼邊閱讀,效果更佳。

微信通訊錄介面寫法

先從簡單的用法看起,微信通訊錄介面和普通的 分組懸停&索引導航 的列表相比:
* 多了四個HeaderView
* 這些HeaderView佈局和主體Item一樣
* 這些HeaderView 沒有分組懸停title
* 這些HeaderView是一組的,索引title自定義

實現:
HeaderView不是本文討論重點,隨意實現之。我用的是我自己之前寫的,戳我

佈局和主體Item一致

由於佈局一致,則我們肯定偷懶直接用主體Item的Bean,將city設定為相應的資料即可,如 “新的朋友”:

public class CityBean extends BaseIndexPinyinBean {
    private String city;//城市名字

沒有分組懸停

去掉分組懸停,我們需要重寫isShowSuspension()方法,返回false。

索引title自定義

它們是一組的,則索引title一致,且需要自定義。
四個頭部的Bean呼叫setBaseIndexTag()

方法,set自定義的title,且一致即可。

        mDatas.add((CityBean) new CityBean("新的朋友").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
        mDatas.add((CityBean) new CityBean("群聊").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
        mDatas.add((CityBean) new CityBean("標籤").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
        mDatas.add((CityBean) new CityBean("公眾號").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));

核心程式碼:

CityBean裡引入一個欄位 isTop

public class CityBean extends BaseIndexPinyinBean {
    private String city;//城市名字
    private boolean isTop;//是否是最上面的 不需要被轉化成拼音的
    ...
    @Override
    public String getTarget() {
        return city;
    }
    @Override
    public boolean isNeedToPinyin() {
        return !isTop;
    }
    @Override
    public boolean isShowSuspension() {
        return !isTop;
    }
}

初始化:

        mRv.addItemDecoration(mDecoration = new SuspensionDecoration(this, mDatas));
        //indexbar初始化
        mIndexBar.setmPressedShowTextView(mTvSideBarHint)//設定HintTextView
                .setNeedRealIndex(true)//設定需要真實的索引
                .setmLayoutManager(mManager);//設定RecyclerView的LayoutManager

資料載入:

        mDatas = new ArrayList<>();
        //微信的頭部 也是可以右側IndexBar導航索引的,
        // 但是它不需要被ItemDecoration設一個標題titile
        mDatas.add((CityBean) new CityBean("新的朋友").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
        mDatas.add((CityBean) new CityBean("群聊").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
        mDatas.add((CityBean) new CityBean("標籤").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
        mDatas.add((CityBean) new CityBean("公眾號").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
        for (int i = 0; i < data.length; i++) {
            CityBean cityBean = new CityBean();
            cityBean.setCity(data[i]);//設定城市名稱
            mDatas.add(cityBean);
        }
        ...
        mIndexBar.setmSourceDatas(mDatas)//設定資料
                .invalidate();
        mDecoration.setmDatas(mDatas);

涉及到的重構程式碼

上文提到,重構後,SuspensionDecoration資料來源依賴的介面是ISuspensionInterface
如下:

public interface ISuspensionInterface {
    //是否需要顯示懸停title
    boolean isShowSuspension();
    //懸停的title
    String getSuspensionTag();
}

BaseIndexBean裡實現,預設顯示懸停,分組title和IndexBar的Tag是一樣的。

public abstract class BaseIndexBean implements ISuspensionInterface {
    private String baseIndexTag;//所屬的分類(城市的漢語拼音首字母)

    @Override
    public String getSuspensionTag() {
        return baseIndexTag;
    }

    @Override
    public boolean isShowSuspension() {
        return true;
    }
}

BaseIndexPinyinBean類,現在如下:

public abstract class BaseIndexPinyinBean extends BaseIndexBean {
    private String baseIndexPinyin;//城市的拼音

    //是否需要被轉化成拼音, 類似微信頭部那種就不需要 美團的也不需要
    //微信的頭部 不需要顯示索引
    //美團的頭部 索引自定義
    //預設應該是需要的
    public boolean isNeedToPinyin() {
        return true;
    }

    //需要轉化成拼音的目標欄位
    public abstract String getTarget();

}

所以我們需要實現微信那種效果,只需要重寫isShowSuspension()isNeedToPinyin()這兩個方法,並setBaseIndexTag()直接設定tag即可。

仿美團選擇城市

這個頁面還是挺麻煩的,所以步驟也最多。建議結合程式碼閱讀Demo及庫地址
分析美團選擇城市列表:
* 主體部分仍舊是一個普通的 分組懸停&索引導航 的列表(美團沒有懸停功能)。
* 頭部是由若干複雜HeaderView組成。
* 從右側索引欄可以看出,定位、最近、熱門這三個Item對應了列表三個HeaderView。
* 最頂部的HeaderView是不需要分組,也不需要索引的。

那麼逐一實現:

主體部分

很簡單,根據前文最後的封裝( 第二篇戳我),如果只有主體部分,我們需要讓主體部分的JavaBean繼承自BaseIndexPinyinBean,然後正常構建資料,最終設定給IndexBar和SuspensionDecoration即可。

public class MeiTuanBean extends BaseIndexPinyinBean {
    private String city;//城市名字
    ...
    @Override
    public String getTarget() {
        return city;
    }
}

頭部若干HeaderViews

這裡不管是通過HeaderView新增進來頭部佈局,還是通過itemViewType自己去實現,核心都是通過itemViewType去做的。
也就是說頭部的HeaderView也是RecyclerView的Item。
既然是Item一定對應著相應的JavaBean。
我們需要針對這些JavaBean讓其分別繼承BaseIndexPinyinBean
具體怎麼實現頭部佈局不是本文重點,不再贅述,Demo裡有程式碼可細看Demo及庫地址

定、近、熱三個HeaderView的處理

定、近、熱三個HeaderView有如下特點:
* 右側導航索引的title 為自定義,不是拼音首字母則也不需要排序。
* 懸停分組的title 和 右側導航索引的title 不一樣,則懸停分組的title也需要自定義

做法:
不過既然是RecyclerView裡的Item,又有 懸停分組、索引導航 特性。那麼就要繼承BaseIndexPinyinBean
* 不需要轉化成拼音且不排序,則重寫isNeedToPinyin()返回false,並呼叫setBaseIndexTag(indexBarTag)給右側索引賦值。
* 需要自定義懸停分組的title,則重寫getSuspensionTag()返回title。

public class MeituanHeaderBean extends BaseIndexPinyinBean {
    private List<String> cityList;
    //懸停ItemDecoration顯示的Tag
    private String suspensionTag;

    public MeituanHeaderBean(List<String> cityList, String suspensionTag, String indexBarTag) {
        this.cityList = cityList;
        this.suspensionTag = suspensionTag;
        this.setBaseIndexTag(indexBarTag);
    }

    @Override
    public String getTarget() {
        return null;
    }

    @Override
    public boolean isNeedToPinyin() {
        return false;
    }

    @Override
    public String getSuspensionTag() {
        return suspensionTag;
    }


}

private List<MeituanHeaderBean> mHeaderDatas; 儲存定、近、熱頭部資料來源,最終需要將其設定給IndexBarSuspensionDecoration

        mHeaderDatas = new ArrayList<>();
        List<String> locationCity = new ArrayList<>();
        locationCity.add("定位中");
        mHeaderDatas.add(new MeituanHeaderBean(locationCity, "定位城市", "定"));
        List<String> recentCitys = new ArrayList<>();
        mHeaderDatas.add(new MeituanHeaderBean(recentCitys, "最近訪問城市", "近"));
        List<String> hotCitys = new ArrayList<>();
        mHeaderDatas.add(new MeituanHeaderBean(hotCitys, "熱門城市", "熱"));

最頂部的HeaderView

最頂部的HeaderView,由於不需要右側索引,也沒有懸停分組。它只是一個普通的HeaderView即可。
對於這種需求的HeaderView,只需要將它們的數量傳給IndexBarSuspensionDecoration 即可。
在內部我已經做了處理,保證聯動座標和資料來源下標的正確。

mDecoration.setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size()));
mIndexBar.setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size());

這裡用headerView一共的count=4,減去上步中mHeaderDatas的size =3,得出不需要右側索引,也沒有懸停分組 頭部的數量。

將主體資料集和頭部資料集合並

我們前幾步中,設計到了三部分資料集,
一部分是主體資料集,

    //主體部分資料來源(城市資料)
    private List<MeiTuanBean> mBodyDatas;

第二部分是需要特性的頭部資料集

    //頭部資料來源
    private List<MeituanHeaderBean> mHeaderDatas;

第三部分是不需要特性的資料集,這裡忽略。我們只用到它的count。
我們需要將第一和第二部分融合,並且設定給IndexBarSuspensionDecoration
則我們利用它們共同的基類,BaseIndexPinyinBean來儲存。
核心程式碼如下:

    //設定給InexBar、ItemDecoration的完整資料集
    private List<BaseIndexPinyinBean> mSourceDatas;

    mSourceDatas.addAll(mHeaderDatas);
    mSourceDatas.addAll(mBodyDatas);

設定給IndexBar

        mIndexBar.setmPressedShowTextView(mTvSideBarHint)//設定HintTextView
                .setNeedRealIndex(true)//設定需要真實的索引
                .setmLayoutManager(mManager)//設定RecyclerView的LayoutManager
                .setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size());
                .setmSourceDatas(mSourceDatas)//設定資料

設定給SuspensionDecoration

        mRv.addItemDecoration(new SuspensionDecoration(this, mSourceDatas)
                .setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size()));

效果圖如文首。

核心程式碼

這裡再提一點,我已經將排序功能抽離至IndexBarIIndexBarDataHelper型別變數中去做,
mIndexBar.setmSourceDatas(mSourceDatas)時會自動排序。
也可以手動呼叫mIndexBar.getDataHelper().sortSourceDatas(mBodyDatas);排序。
像本節的案例,可以選擇先排序bodyDatas,然後再合併至sourceDatas,最終設定給IndexBarSuspensionDecoration
如:

                //先排序
                mIndexBar.getDataHelper().sortSourceDatas(mBodyDatas);
                mSourceDatas.addAll(mBodyDatas);
                mIndexBar.setmSourceDatas(mSourceDatas)//設定資料
                        .invalidate();
                mDecoration.setmDatas(mSourceDatas);

涉及到的重構程式碼:

除了上節提到的那些資料結構的重構,
我還將以前在IndexBar裡完成的:

  • 1 將漢語轉成拼音
  • 2 填充indexTag
  • 3 排序源資料來源
  • 4 根據排序後的源資料來源->indexBar的資料來源

抽成一個介面表示,與IndexBar分離。

/**
 * 介紹:IndexBar 的 資料相關幫助類
 * 1 將漢語轉成拼音
 * 2 填充indexTag
 * 3 排序源資料來源
 * 4 根據排序後的源資料來源->indexBar的資料來源
 * 作者:zhangxutong
 * 郵箱:[email protected]
 * 主頁:http://blog.csdn.net/zxt0601
 * 時間: 2016/11/28.
 */

public interface IIndexBarDataHelper {
    //漢語-》拼音
    IIndexBarDataHelper convert(List<? extends BaseIndexPinyinBean> data);

    //拼音->tag
    IIndexBarDataHelper fillInexTag(List<? extends BaseIndexPinyinBean> data);

    //對源資料進行排序(RecyclerView)
    IIndexBarDataHelper sortSourceDatas(List<? extends BaseIndexPinyinBean> datas);

    //對IndexBar的資料來源進行排序(右側欄),在 sortSourceDatas 方法後呼叫
    IIndexBarDataHelper getSortedIndexDatas(List<? extends BaseIndexPinyinBean> sourceDatas, List<String> datas);
}

IndexBar內部持有這個介面的變數,呼叫其中方法完成需求:

 public IndexBar setmSourceDatas(List<? extends BaseIndexPinyinBean> mSourceDatas) {
        this.mSourceDatas = mSourceDatas;
        initSourceDatas();//對資料來源進行初始化
        return this;
    }


    /**
     * 初始化原始資料來源,並取出索引資料來源
     *
     * @return
     */
    private void initSourceDatas() {
        //add by zhangxutong 2016 09 08 :解決源資料為空 或者size為0的情況,
        if (null == mSourceDatas || mSourceDatas.isEmpty()) {
            return;
        }
        if (!isSourceDatasAlreadySorted) {
            //排序sourceDatas
            mDataHelper.sortSourceDatas(mSourceDatas);
        } else {
            //漢語->拼音
            mDataHelper.convert(mSourceDatas);
            //拼音->tag
            mDataHelper.fillInexTag(mSourceDatas);
        }
        if (isNeedRealIndex) {
            mDataHelper.getSortedIndexDatas(mSourceDatas, mIndexDatas);
            computeGapHeight();
        }
    }

我在sortSourceDatas()實現裡,已經呼叫了convert(datas);fillInexTag(datas);

    @Override
    public IIndexBarDataHelper sortSourceDatas(List<? extends BaseIndexPinyinBean> datas) {
        if (null == datas || datas.isEmpty()) {
            return this;
        }
        convert(datas);
        fillInexTag(datas);
        //對資料來源進行排序
        Collections.sort(datas, new Comparator<BaseIndexPinyinBean>() {
            @Override
            public int compare(BaseIndexPinyinBean lhs, BaseIndexPinyinBean rhs) {
                if (!lhs.isNeedToPinyin()) {
                    return 0;
                } else if (!rhs.isNeedToPinyin()) {
                    return 0;
                } else if (lhs.getBaseIndexTag().equals("#")) {
                    return 1;
                } else if (rhs.getBaseIndexTag().equals("#")) {
                    return -1;
                } else {
                    return lhs.getBaseIndexPinyin().compareTo(rhs.getBaseIndexPinyin());
                }
            }
        });
        return this;
    }

通過如下變數控制,是否需要排序,是否需要提取索引:

     //是否需要根據實際的資料來生成索引資料來源(例如 只有 A B C 三種tag,那麼索引欄就 A B C 三項)
    private boolean isNeedRealIndex;
    //源資料 已經有序?
    private boolean isSourceDatasAlreadySorted;

好處

這樣做的好處是,當你不喜歡我這種排序方式,亦或你想自定義特殊字元的索引,現在是”#”,你都可以通過繼承重寫IndexBarDataHelperImpl類的方法來完成。或者乾脆實現IIndexBarDataHelper介面,這就能滿足擴充套件和不同的定製需求,不用每次修改IndexBar類。

總結

靈活重寫ISuspensionInterface介面中的方法,可控制:
* 是否需要顯示懸停title
* 懸停顯示的titles

靈活重寫BaseIndexPinyinBean中的方法,可控制:
* 是否需要被轉化成拼音, 類似微信頭部那種就不需要 美團的也不需要
* 微信的頭部 不需要顯示索引
* 美團的頭部 索引自定義
* 預設應該是需要的
* 在isNeedToPinyin()返回false時,不要忘了手動setBaseIndexTag()設定IndexBar的Tag值.

IndexBarIIndexBarDataHelper都提供了setHeaderViewCount(int headerViewCount)方法,供設定 不需要右側索引,也沒有懸停分組的HeaderView數量。

相關推薦

Android快速實現仿選擇城市介面通訊錄介面

概述 本文是這個系列的第三篇,不出意外也是終結篇。因為使用經過重構後的控制元件已經可以快速實現市面上帶 索引導航、懸停分組的列表介面了。 在前兩篇裡,我們從0開始,一步一步實現了仿微信通訊錄、餓了麼選餐介面。 (第一篇戳我 第二篇戳我) 這篇文章作為終結

Android 仿選擇城市通訊錄、餓了麼點餐列表的導航懸停分組索引列表

SuspensionIndexBar簡介:快速實現分組懸停、右側索引導航聯動 列表。如 美團選擇城市介面,微信通訊錄介面、餓了麼點餐介面等。SuspensionIndexBar相關博文:喜歡隨手點個star 多謝在哪裡找到我:我的github:我的CSDN部落格:我的稀土掘金

Android快速開發偷懶必備(二) 支援DataBinding啦~爽炸一行實現花式列表

概述 在前文快速開發偷懶必備(一)中,我們利用Adapter模式封裝了一個庫,能快速為任意ViewGroup新增子View。 有如下特點: * 快速簡單使用 * 支援任意ViewGroup * 無耦合 * 無侵入性 * Item支援多種型別

Android如何實現Android發送短

ted param close ase find array 短信 red phone 第一種:調用系統短信接口直接發送短信;主要代碼如下: /** * 直接調用短信接口發短信 * @param phoneNumber * @

android 動畫--幀動畫--仿載入中小人

1 把資源圖片放到drawable中 2 在drawable中寫動畫的xml檔案animation01 Animation01.xml <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:a

fragment實現仿下拉篩選功能

1、前言 在開發APP中,大家基本都會用到篩選功能,而美團、房天下、淘寶等都會有一個下拉篩選功能,其實實現起來並不是很難,先上圖看一看,樣式可能不太好看,還請見諒。頁面篩選時有動畫效果。 2、思路總結和原始碼 (1)首先是一個xml頁面,整體思路就是上方按鈕正常佈局,下方通過f

Android簡單實現使用WebView元件實現在App內開啟web

關於WebView元件 如何實現在App內嵌web 在新建的android專案裡,如果要實現內嵌Web,一定要在AndroidManifest.xml設定訪問網路許可權: <uses-permission android:name="andr

AndroidEditText實現搜尋功能把鍵盤迴車鍵改為搜尋;EditText隱藏游標

1、xml檔案中設定屬性 android:imeOptions="actionSearch"                 android:singleLine="true" 注:android:singleLine 已過期,不過設定為maxlines = 1  不會將回

Android- OkHttp實現斷點續傳

1.匯入依賴包 //retrofit, 基於Okhttp,考慮到專案中經常會用到retrofit,就匯入這個了。 compile 'com.squareup.retrofit2:retrofit:2.1.0' //ButterKnife compile 'com.jakew

javawebJQ實現商品的左右選擇

實現效果: 步驟分析:1. 匯入JQ的檔案                 2. 文件載入函式 :頁面初始化       

javawebJS實現商品的左右選擇

實現效果: 步驟分析: 1. 確定事件: 點選事件 :onclick事件                    2. 事件要觸發函式 selectOne    

Android呼叫系統圖庫獲取影象並裁剪安卓 4.4可用

3.區分Android系統版本,解析uri 若系統版本低於4.4,uri=data.getData()直接可用,4.4及以上要根據uri中的id來查詢檔案路徑,然後自己構造新的uri 下圖為安卓4.4呼叫相簿訪問圖片的路徑,預設返回的字首為content:// ,最後的3A741是圖片id 根據id我們轉

Android狀態列相關適配(判斷MIUIFlyme狀態列圖示顏色切換獲取狀態列高度沉浸式狀態列相關等)

對於狀態列相關適配這個事情,真是讓人頭疼的一個模組。因為負責的專案主題色偏偏是白色,不但要去適配 MIUI ,Flyme(因為這兩個都可以實現沉浸式,並且圖示可以切換成黑色),也要分別適配 Android 6.0 以下, Android 6.0 起兩種不同情況(6.0 起原

iOS -(仿城市選擇器 + 自動定位 + 字母索引

今天給大家分享一個仿美團城市選擇器效果的;幾行程式碼即可將集三級城市選擇、定位、搜尋和字母索引於一身的城市選擇器整合到你的專案中,極其簡單輕便! JFCitySelector效果展示: JFCitySelector效果展示.gif JFCitySelector使用方法: 注意:因為此專案使用了Masonr

學習06 爬蟲使用代理地址爬取搜狗文章

實現功能 根據登陸後的cookie製作header,請求搜尋微信文章url需要使用urlencode拼接使用代理避免IP被封使用pyquery解析得到需要的欄位資訊爬取文章詳情頁並存儲到M

AndroidAndroid開發實現帶有反彈效果仿IOS反彈scrollview詳解教程

作者:程式設計師小冰,GitHub主頁:https://github.com/QQ986945193 新浪微博:http://weibo.com/mcxiaobing 首先給大家看一下我們今天這個最終實現的效果圖: 這個是ios中的反彈效果。當然我

Android仿知乎夜間模式的實現

1.簡介 目前很多App都有夜間模式的功能,網上教程也是很多,最近專案不忙,抽空學習了下,在這做下記錄,希望能幫到正在看部落格的你,我們先來看下知乎的效果: 看我的效果: 臥槽,好像啊,哈

Android 仿網,探索ListView的A-Z字母排序功能實現選擇城市

記得在我剛開始接觸到美團網的時候就對美團網這個城市定位、選擇城市功能很感興趣,覺得它做得很棒。有如下幾個點: 一:實現ListView的A-Z字母排序功能 二:根據輸入框的輸入值改變來過濾搜尋結果,如果輸入框裡面的值為空,更新為原來的列表,否則為過濾資料列表

AndroidAndroid開發之常用的loading等待效果實現仿博等待動畫。兩種實現方式詳解

長期維護的Android專案,裡面包括常用功能實現,以及知識點詳解, 當然還有Java中的知識點。 具體請看github:https://github.com/QQ986945193/DavidAndroidProjectTools 首先大家都知道,當我

Android 點評校招一面17/9/21

到了秋招季,身為大四狗自然是苦逼的找工作。 由於沒有參加培訓,對於各種筆試,面試(尤其是面試)充滿恐懼心理; 感謝美團給我這次面試的機會,這是我的第三次面試。 前兩次分別面的陳昇寶實習生,和秋招的搜狗。 場面十分的尷尬。。。不忍回憶!T.T… 由於我當