概述

本文是这个系列的第三篇,不出意外也是终结篇。因为使用经过重构后的控件已经可以快速实现市面上带 索引导航、悬停分组的列表界面了。
在前两篇里,我们从0开始,一步一步实现了仿微信通讯录、饿了么选餐界面。
(第一篇戳我 第二篇戳我)
这篇文章作为终结篇,和前文相比,主要涉及以下内容:

  • 重构悬停分组,将TitleItemDecoration更名为SuspensionDecoration,数据源依赖ISuspensionInterface接口。
  • 重构索引导航,将IndexBar对数据源的操作,如排序,转拼音等分离出去,以接口IIndexBarDataHelper通信。
  • 有N多兄弟给我留言、加QQ问的:如何实现美团选择城市列表页面,
  • 添加一个不带悬停分组的HeaderView(微信通讯录界面)

代码传送门:喜欢的话,随手点个star。多谢
https://github.com/mcxtzhang/SuspensionIndexBar

老规矩,先上图:


(SwipeDelMenuLayout : https://github.com/mcxtzhang/SwipeDelMenuLayout)

本文将先举例子如何写,并对其中涉及到的重构部分进行讲解。
如有不明者,建议先观看(第一篇戳我 第二篇戳我),
以及下载Demo,边看代码边阅读,效果更佳。


转载请标明出处:
http://blog.csdn.net/zxt0601/article/details/53389835
本文出自:【张旭童的博客】(http://blog.csdn.net/zxt0601)
代码传送门:喜欢的话,随手点个star。多谢
https://github.com/mcxtzhang/SuspensionIndexBar


微信通讯录界面写法

先从简单的用法看起,微信通讯录界面和普通的 分组悬停&索引导航 的列表相比:
* 多了四个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;//是否是最上面的 不需要被转化成拼音的...@Overridepublic String getTarget() {return city;}@Overridepublic boolean isNeedToPinyin() {return !isTop;}@Overridepublic 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设一个标题titilemDatas.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 {//是否需要显示悬停titleboolean isShowSuspension();//悬停的titleString getSuspensionTag();
}

BaseIndexBean里实现,默认显示悬停,分组title和IndexBar的Tag是一样的。

public abstract class BaseIndexBean implements ISuspensionInterface {private String baseIndexTag;//所属的分类(城市的汉语拼音首字母)@Overridepublic String getSuspensionTag() {return baseIndexTag;}@Overridepublic 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;//城市名字...@Overridepublic 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显示的Tagprivate String suspensionTag;public MeituanHeaderBean(List<String> cityList, String suspensionTag, String indexBarTag) {this.cityList = cityList;this.suspensionTag = suspensionTag;this.setBaseIndexTag(indexBarTag);}@Overridepublic String getTarget() {return null;}@Overridepublic boolean isNeedToPinyin() {return false;}@Overridepublic 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* 邮箱:mcxtzhang@163.com* 主页:http://blog.csdn.net/zxt0601* 时间: 2016/11/28.*/public interface IIndexBarDataHelper {//汉语-》拼音IIndexBarDataHelper convert(List<? extends BaseIndexPinyinBean> data);//拼音->tagIIndexBarDataHelper 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) {//排序sourceDatasmDataHelper.sortSourceDatas(mSourceDatas);} else {//汉语->拼音mDataHelper.convert(mSourceDatas);//拼音->tagmDataHelper.fillInexTag(mSourceDatas);}if (isNeedRealIndex) {mDataHelper.getSortedIndexDatas(mSourceDatas, mIndexDatas);computeGapHeight();}}

我在sortSourceDatas()实现里,已经调用了convert(datas);fillInexTag(datas);

    @Overridepublic IIndexBarDataHelper sortSourceDatas(List<? extends BaseIndexPinyinBean> datas) {if (null == datas || datas.isEmpty()) {return this;}convert(datas);fillInexTag(datas);//对数据源进行排序Collections.sort(datas, new Comparator<BaseIndexPinyinBean>() {@Overridepublic 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数量。


转载请标明出处:
http://blog.csdn.net/zxt0601/article/details/53389835
本文出自:【张旭童的博客】(http://blog.csdn.net/zxt0601)
代码传送门:喜欢的话,随手点个star。多谢
https://github.com/mcxtzhang/SuspensionIndexBar


【Android】快速实现仿美团选择城市界面,微信通讯录界面相关推荐

  1. Android 仿美团选择城市、微信通讯录、饿了么点餐列表的导航悬停分组索引列表

    SuspensionIndexBar 项目地址:mcxtzhang/SuspensionIndexBar 简介:快速实现分组悬停.右侧索引导航联动 列表. 如 美团选择城市界面,微信通讯录界面.饿了么 ...

  2. Android仿美团选择城市

    需求:需要有当前定位城市,热门城市,下面按照城市首拼音排序,滑动的过程中字母A,B,C-会置顶互相切换.右侧有快速切换字母城市的选择 效果图: 思路:因为上部分要划走,RecyclerView滑动过程 ...

  3. android 仿微信demo————微信通讯录界面功能实现(移动端,服务端)

    android 仿微信demo----微信启动界面实现 android 仿微信demo----注册功能实现(移动端) android 仿微信demo----注册功能实现(服务端) android 仿微 ...

  4. Android动画之仿美团加载数据等待时,小人奔跑进度动画对话框(附顺丰快递员奔跑效果)...

    Android动画之仿美团加载数据等待时,小人奔跑进度动画对话框(附顺丰快递员奔跑效果) 首句依然是那句老话,你懂得! finddreams :(http://blog.csdn.net/finddr ...

  5. php 仿美团切换城市,微信小程序仿美团城市选择的实现

    这篇文章主要为大家详细介绍了微信小程序仿美团城市选择,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 本文实例为大家分享了微信小程序城市选择器,供大家参考,具体内容如下 代码很简单. var cit ...

  6. Android中使用ExpandableListView实现微信通讯录界面(完善仿微信APP)

    之前的博文<Android中使用ExpandableListView实现好友分组>我简单介绍了使用ExpandableListView实现简单的好友分组功能,今天我们针对之前的所做的仿微信 ...

  7. Android微信通讯录界面代码,Android中使用Expandablelistview实现微信通讯录界面

    之前的博文<Android 中使用ExpandableListView 实现分组的实例>我简单介绍了使用ExpandableListView实现简单的好友分组功能,今天我们针对之前的所做的 ...

  8. Android移动应用开发之Viewpage2+fragment实现微信滑动界面的效果

    文章目录 布局 viewpager2 fragment adapter 实现的效果如下: 滑动界面能够实现界面的跳转. 点击下面按钮同样实现界面的跳转. 布局 最下面的导航栏,单独写了个布局文件: & ...

  9. Android仿美团切换城市

    转载请注明出处:http://blog.csdn.net/dmk877/article/details/49757731 最近一直关注一些比较有名的app,像美团.58.赶集网.淘宝等等.主要目的就是 ...

最新文章

  1. JAVA 设计模式 享元模式
  2. MATLAB之GUI学习经典举例
  3. oracle登陆认证方式
  4. python脚本根据cookies自动登录网站_python模拟登录并且保持cookie的方法详解
  5. const 和 #define 区别总结
  6. 一篇教你xftp连接阿里云轻量级应用服务器。超级详细,避免踩坑
  7. [MicroPython]stm32f407控制DS18B20检测温度
  8. Spring经典面试题
  9. ECSHOP首页调用指定分类下的商品
  10. Java逐帧动画播放器V0.1.0
  11. c语言常量指针和指针常量
  12. ReactNative视频播放器
  13. Nexus3搭建jenkins报错 下载 依赖jar包 报错Not authorized
  14. Android对未签名的apk进行签名
  15. 文本框获取焦点后出现的边框,怎么去掉
  16. Unity 像机抖动效果
  17. 职业规划以兴趣为导向,而不是盲目跟风
  18. 全能成熟稳定开源分布式存储Ceph破冰之旅-上
  19. lego-loam代码分析(3)-激光里程计
  20. 什么是OJ _OJ是什么?

热门文章

  1. XUPT_STA2018(部分题解)
  2. 单用户MIMO系统(二):信道信息在发端未知
  3. 项管行知02--工作环境
  4. webstorm 一次Git使用很卡的处理记录
  5. java中冒号的用法
  6. 数学模型天气预测方法_预测即将到来的天气的新方法
  7. Python代码中的三大常见“愚形”,你中招了吗?
  8. tplink查看上网记录_tp-link路由器如何查看连接人数 tp-link路由器查看连接人数方法【步骤】...
  9. 美国的ACH支付体系
  10. discuz mysql 配置_Discuz如何修改数据库配置?