本文是這個系列的第三篇,不出意外也是終結篇。由於使用通過重構後的控件已經能夠快速實現市面上帶 索引導航、懸停分組的列表界面了。
在前兩篇裏,咱們從0開始,一步一步實現了仿微信通信錄、餓了麼選餐界面。
(第一篇戳我 第二篇戳我)
這篇文章做爲終結篇,和前文相比,主要涉及如下內容:git
代碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/S…github
老規矩,先上圖:
。
api
本文將先舉例子如何寫,並對其中涉及到的重構部分進行講解。
若有不明者,建議先觀看(第一篇戳我 第二篇戳我),
以及下載Demo,邊看代碼邊閱讀,效果更佳。微信
轉載請標明出處: gold.xitu.io/post/583c13…
本文出自:【張旭童的稀土掘金】(gold.xitu.io/user/56de21…)
代碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/S…數據結構
先從簡單的用法看起,微信通信錄界面和普通的 分組懸停&索引導航 的列表相比:ide
實現:
HeaderView不是本文討論重點,隨意實現之。我用的是我本身以前寫的,戳我佈局
因爲佈局一致,則咱們確定偷懶直接用主體Item的Bean,將city設置爲相應的數據便可,如 「新的朋友」:post
public class CityBean extends BaseIndexPinyinBean {
private String city;//城市名字複製代碼
去掉分組懸停,咱們須要重寫isShowSuspension()
方法,返回false。this
它們是一組的,則索引title一致,且須要自定義。
四個頭部的Bean調用setBaseIndexTag()
方法,set自定義的title,且一致便可。spa
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及庫地址。
分析美團選擇城市列表:
那麼逐一實現:
很簡單,根據前文最後的封裝( 第二篇戳我),若是隻有主體部分,咱們須要讓主體部分的JavaBean繼承自BaseIndexPinyinBean
,而後正常構建數據,最終設置給IndexBar和SuspensionDecoration便可。
public class MeiTuanBean extends BaseIndexPinyinBean {
private String city;//城市名字
...
@Override
public String getTarget() {
return city;
}
}複製代碼
這裏不論是經過HeaderView添加進來頭部佈局,仍是經過itemViewType本身去實現,核心都是經過itemViewType去作的。
也就是說頭部的HeaderView也是RecyclerView的Item。
既然是Item必定對應着相應的JavaBean。
咱們須要針對這些JavaBean讓其分別繼承BaseIndexPinyinBean
。
具體怎麼實現頭部佈局不是本文重點,再也不贅述,Demo裏有代碼可細看Demo及庫地址。
定、近、熱三個HeaderView有以下特色:
作法:
不過既然是RecyclerView裏的Item,又有 懸停分組、索引導航 特性。那麼就要繼承BaseIndexPinyinBean
。
isNeedToPinyin()
返回false,並調用setBaseIndexTag(indexBarTag)
給右側索引賦值。getSuspensionTag()
返回title。public class MeituanHeaderBean extends BaseIndexPinyinBean { private ListcityList; //懸停ItemDecoration顯示的Tag private String suspensionTag; public MeituanHeaderBean(List 複製代碼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
保存定、近、熱頭部數據源,最終須要將其設置給IndexBar
和SuspensionDecoration
。
mHeaderDatas = new ArrayList<>(); ListlocationCity = new ArrayList<>(); locationCity.add("定位中"); mHeaderDatas.add(new MeituanHeaderBean(locationCity, "定位城市", "定")); List 複製代碼recentCitys = new ArrayList<>(); mHeaderDatas.add(new MeituanHeaderBean(recentCitys, "最近訪問城市", "近")); List hotCitys = new ArrayList<>(); mHeaderDatas.add(new MeituanHeaderBean(hotCitys, "熱門城市", "熱"));
最頂部的HeaderView,因爲不須要右側索引,也沒有懸停分組。它只是一個普通的HeaderView便可。
對於這種需求的HeaderView,只須要將它們的數量傳給IndexBar
和SuspensionDecoration
便可。
在內部我已經作了處理,保證聯動座標和數據源下標的正確。
mDecoration.setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size()));
mIndexBar.setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size());複製代碼
這裏用headerView一共的count=4,減去上步中mHeaderDatas
的size =3,得出不須要右側索引,也沒有懸停分組 頭部的數量。
咱們前幾步中,設計到了三部分數據集,
一部分是主體數據集,
//主體部分數據源(城市數據) private ListmBodyDatas; 複製代碼
第二部分是須要特性的頭部數據集
//頭部數據源 private ListmHeaderDatas; 複製代碼
第三部分是不須要特性的數據集,這裏忽略。咱們只用到它的count。
咱們須要將第一和第二部分融合,而且設置給IndexBar
和SuspensionDecoration
。
則咱們利用它們共同的基類,BaseIndexPinyinBean
來存儲。
核心代碼以下:
//設置給InexBar、ItemDecoration的完整數據集 private ListmSourceDatas; 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()));複製代碼
效果圖如文首。
這裏再提一點,我已經將排序功能抽離至IndexBar
的IIndexBarDataHelper
類型變量中去作,
在mIndexBar.setmSourceDatas(mSourceDatas)
時會自動排序。
也能夠手動調用mIndexBar.getDataHelper().sortSourceDatas(mBodyDatas);
排序。
像本節的案例,能夠選擇先排序bodyDatas,而後再合併至sourceDatas,最終設置給IndexBar
和SuspensionDecoration
。
如:
//先排序
mIndexBar.getDataHelper().sortSourceDatas(mBodyDatas);
mSourceDatas.addAll(mBodyDatas);
mIndexBar.setmSourceDatas(mSourceDatas)//設置數據
.invalidate();
mDecoration.setmDatas(mSourceDatas);複製代碼
除了上節提到的那些數據結構的重構,
我還將之前在IndexBar裏完成的:
抽成一個接口表示,與IndexBar分離。
/** * 介紹:IndexBar 的 數據相關幫助類 * 1 將漢語轉成拼音 * 2 填充indexTag * 3 排序源數據源 * 4 根據排序後的源數據源->indexBar的數據源 * 做者:zhangxutong * 郵箱:mcxtzhang@163.com * 主頁:http://blog.csdn.net/zxt0601 * 時間: 2016/11/28. */ public interface IIndexBarDataHelper { //漢語-》拼音 IIndexBarDataHelper convert(List data); //拼音->tag IIndexBarDataHelper fillInexTag(List data); //對源數據進行排序(RecyclerView) IIndexBarDataHelper sortSourceDatas(List datas); //對IndexBar的數據源進行排序(右側欄),在 sortSourceDatas 方法後調用 IIndexBarDataHelper getSortedIndexDatas(List sourceDatas, Listdatas); } 複製代碼
IndexBar內部持有這個接口的變量,調用其中方法完成需求:
public IndexBar setmSourceDatas(List
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 datas) { if (null == datas || datas.isEmpty()) { return this; } convert(datas); fillInexTag(datas); //對數據源進行排序 Collections.sort(datas, new Comparator() { @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
接口中的方法,可控制:
靈活重寫BaseIndexPinyinBean
中的方法,可控制:
isNeedToPinyin()
返回false時,不要忘了手動setBaseIndexTag()
設置IndexBar的Tag值.IndexBar
的IIndexBarDataHelper
都提供了setHeaderViewCount(int headerViewCount)
方法,供設置 不須要右側索引,也沒有懸停分組的HeaderView數量。
轉載請標明出處: gold.xitu.io/post/583c13…
本文出自:【張旭童的稀土掘金】(gold.xitu.io/user/56de21…)
代碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/S…