安卓系统微信分组设置怎么设置

微信朋友圈分组教程 怎么用标签设置朋友圈分组可见?
我的图书馆
微信朋友圈分组教程 怎么用标签设置朋友圈分组可见?
 微信上加的人越来了越多,朋友、同事、家人、甚至陌生人等等,各种都有,有时候有些话并不适合所有人看到,屏蔽某个人的做法又太无情太不好意思了,那朋友圈怎么用不屏蔽朋友的方法而不让有些人看到我的朋友圈呢?小编终于研究出了一种好用并且温柔的方法,让你不屏蔽别人,不拉黑别人,也能给你的朋友圈加一道屏障,下面就来讲讲具体的操作方法。   其实这个运用到了微信的标签功能,首先在微信通讯录中找到标签一项
  点进去之后,选择新建标签
  这时会要你选择联系人,你要选择你朋友圈内容可以看得那些人,也就是你允许他看你朋友圈的那一部分人
  选好之后给这些人加上一个标签,随便你怎么加,朋友、亲人等等,按你自己的意思来,保存就可以了
  这样,在你下次发朋友圈的时候,你只要选择某个标签的人可见即可   还是来说一下具体做法吧,如下   当你发朋友圈的时候,下面有选择谁可以看(注意这个功能不支持纯文字)
  你点进去之后选择部分可见,这时会出现你编辑好的标签
  选择适当的标签,也就是你让他们看你这条朋友圈的那些人的那个标签
  这样,你刚刚发的朋友圈谁能看就在你自己的掌控之下了
  你发的朋友圈下面这个人人人的小图标就表示你的这条朋友圈是部分人可见的
  这种方法真的很实用很好玩啊,对方并不是完全不能看到你的消息,只是,你不想让他知道的东西,他就不会知道,为什么小编想到了一句古诗“犹抱琵琶半遮面”呢。
微信朋友圈怎么分组?设置技巧教程微信朋友圈怎么分组?设置技巧教程 我在微博上看到有朋友想屏蔽一些微信好友查看朋友圈的权限,又不好意思将他加到&朋友圈黑名单&,于是问到了我,我说难道你不知道微信朋友圈有分组功能吗?那么该如何设置呢?请看下面的教程文章。 我在微博上看到有朋友想屏蔽一些微信好友查看朋友圈的权限,又不好意思将他加到“朋友圈黑名单”,于是问到了我,我说难道你不知道微信朋友圈有分组
设置教程: 打开“微信”客户端,在顶部栏中点击“发现”。进入“发现”后选择“朋友圈”在朋友圈中发表自己的心情。 在朋友圈中发心情时,平时是否只编辑完内容就点击“发送”呢。但其实在编辑框下面有一个“可见范围”的设置,可以让你的朋友圈只分享指定的分组的。 点击“可见范围”打开,点击“分组”就能把朋友圈心情分享到指定分组中了。 在可见范围设置中,设置了只分享到指定分组,那么你所有分享的朋友圈都会有一
何为微信朋友圈分组?意思就是当你在朋友圈发送消息的时候可以选择指定的人群观看,比如选择家人组或者朋友组,又或者同事组。如何实现?让速营互动的小编告诉你吧。 首先,找到一个想要分享的东西,比如下面这篇谈论“约炮”的文章,分享给某些朋友看那是很不错的,但被爸妈看到了,肯定是不是很好的。 点击上图的“可见范围”,我们会进入到一个“选择可见范围”的页面,如下图: 然后点击上图的“编辑
微信朋友圈怎么用(图文) 微信里有一个功能,就是微信朋友圈,你可以在这里发布图片和文字,同时也可以看到好友发布的消息和图片,下面与大家分享下微信朋友圈怎么发布教程,不会的朋友可以了解下哈 微信现在成了越来越多用户的喜欢,使用微信的朋友也越来越多,在微信里有一个功能,就是微信朋友圈,你可以在这里发布图片和文字,同时也可以看到好友发布的消息和图片。在这里跟大家分享一下微信朋友圈怎
怎么建立微信朋友圈与电脑的通道: 打开电脑——上网——在地址栏输入wx.qq.com——见到二维码——打开手机——微信——点击右上侧+号——扫一扫——点击“登录网页版微信”——在电脑上出现对话框——点击电脑上的 “文件传输助手”——点击电脑上右侧左下方的小四方块——出现电脑中的文件——点击选好文件——点击电脑界面下方右侧“发送”,即把电脑中的文件传到手机——打开微信——点击 “文件传输
  微信朋友圈怎么发视频呢?这里所说的朋友圈发视频目前可以通过分享来完成,如果是给好友发视频的话可以直接点+号,然后选择拍摄即可发送视频。   微信朋友圈怎么发视频   一般有2种方法。1.扫描二维码 2. 发送链接分享。   下面以优酷网为例说说具体操作   一、扫描二维码分享。   1、首先打开优酷网页版,在视频下面找到微信图标,点击这个图标。      2、然后在网页上面会看到一个视频的二
在微信朋友圈中,如果发现有好友很久都没有发消息的话,原因无非是两个,第一是好友没有发消息,第二种就是你被好友屏蔽了,但是你还不知道。那么,怎么才能知道对方的微信朋友圈是否将你屏蔽了呢?我这里有一个非常简单的方法,只需要两步操作就能查看 微信朋友圈里的妹妹和很久没发自拍照了,为啥呢?原因无非是两个,一个是妹妹的右手食指和中指有些劳累,无法做劈叉状,所以没自拍;另一个是妹妹的微信朋友
微信朋友圈显示自定义地点位置怎么设置?发微信朋友圈时显示自定义的地点位置,是不是很酷呢?但你知道怎么弄吗?下面小编就给大家分享一下微信朋友圈显示自定义地点位置怎么设置。如果你的微信没有此功能,请升级到微信5.3 工具/原料 微信5.3 微信朋友圈显示自定义地点位置怎么设置 1 微信朋友圈发送前,点所在位置,如下图 2 微信会
微信中如何给朋友圈发送视频 微信不能直接发视频到朋友圈,只能先上传到别的网站,比如QQ空间、腾讯视频、优酷这种,然后分享到朋友圈 这里以 iPhone 版优酷为例,简单介绍下如何通过优酷客户端,把视频分享到微信朋友圈的操作方法。 工具/原料 微信 手机优酷 方法/步骤 1 如果手机上没有安装优酷应用程序,请先自行安装。另外如果优
有时候我们在微信朋友圈发状态之后想要删除怎么办呢?其实方法很简单。我们来看一下操作过程。 工具/原料 手机 微信软件 方法/步骤 1 首先,我们打开微信软件,点击右上角菜单“三个竖点”的菜单键,接着选择“我的相册”。 2 进入相册之后,找到我们需要删除的内容。如果是删除我们转发的内容,在选择进入我们转发的内容界面,点击内容下方“删除
有一朋友加我,我看她是美女(头像很漂亮),我就问她:“你是谁?为什么加我?”我还以为她对我有什么企图呢?结果人家说:“群里加的你,老板让一个月加好友加到一千人,我已经完成四百了多了……”   作为一个自我的90后,和一个资深微信玩家,我对这种玩法是嗤之以鼻的,当时我说了一句话:“肤浅的人类!”至于为什么这么说,其中有什么深刻的道理,前面文章《这个世界上,目前只有一个人可以点我的赞》中有详细叙述,复
3 选择拍照或者自己里面有的照片,编辑自己要发表的微信,然后点击发送 4 接着我们就可以看到朋友圈我们发表的微信了 5 光发布文字微信,是在上面第二步时,按住照相机图标几秒后就将弹出下图所示的窗口 6 输入自己要发表的文字,点击发送 7 接着我们就可以看到自己的成果了 END
怎样用微信拍视频,拍完视频之后如何将视频分享到朋友圈? 方法/步骤 1 首先登录微信,进入微信聊天列表页,然后将手放在聊天列表页的空白处,然后请按往下划拉,就进入了微信的小视频界面。(注:此功能要微信6.0或以上版本才能使用。) 2 进入微信的小视频界面,就会出现视频录制画面了,然后下方有一个按住拍提示。 3 接下来,你就只需要
视频/音乐(歌曲)怎么分享到微信朋友圈 本文章来给大家介绍关于视频/音乐(歌曲)怎么分享到微信朋友圈呢,这里我们准工作需要手机微信,与2个QQ号哦,下面我来给各位同学具体介绍。 一、扫描二维码分享。 1.首先打开优酷网页版,在视频下面找到微信图标,点击这个图标。 2.然后在网页上面会看到一个视频的二维码图片 3.打开手机,登陆微信,点击我——扫一扫。将摄像头对准网页上面的视频
优酷视频怎么发到微信分享到朋友圈 如果要分享的朋友很多一条一条发链接是不是太麻烦了,其实我们只要把视频分享到朋友圈,那么微信里的好友就都能看到啦,下面以图文的形式为大家介绍下:  经常在优酷上看到一些有意思的视频想跟朋友分享怎么办呢?如果要分享的朋友很多一条一条发链接是不是太麻烦了,其实我们只要把视频分享到朋友圈,那么微信里的好友就都能看到啦,优酷视频怎么发到微信? 软件名称:
玩转微信小技巧 :分享朋友圈展示所在位置名称 浏览:65 | 更新: 12:15 1 2 3 4 5 分步阅读 微信朋友圈展示我的所在位置,以及创建我的位置 工具/原料 朋友圈 微信 方法/步骤 1 第一步:分享喜欢的信息到朋友圈,点击所在位置。
如何将网页上的内容分享到微信朋友圈 在网上看到一些有意思的文章、漂亮的图片或有用的信息,但载有这些内容的网页却没有分享到微信的功能,这时应该怎么做,才能将这些内容分享到微信朋友圈呢?方法很简单,按以下步骤操作即可。 1. 复制要分享到微信朋友圈的网址。如果是在手机或平板上浏览到的网页,可点击浏览器的地址栏,将网址选定并复制下来。 2. 进入微信朋友圈,长按右上角的相机图标,将已复制的网
微信4.0 for iPhone 正式版支持把照片分享到朋友圈 前往微信电脑客户端专题 4月19日消息,微信iPhone新版本4.0版今天在App Store正式发布。新版本增加了类似Path的相册功能,支持把照片分享到朋友圈,并且开放了第三方应用接口。微信4.0版本此前已经在网上议论了许久,包括类似Path的照片分享功能以及开放第三方接口等重要更新被网友爆料出来。从现在发布的新版介
生活中最大的不幸是身边缺少积极进取的人,缺少远见卓识的人,这样你的生活会变得平庸失色。学最好的别人,做最好的自己,借人之智,成就自己。如果你想翱翔蓝天,那你就要和雄鹰一起飞翔;如果你想驰骋大地,那你就要和骏马一起奔跑。 长按微信号,可以复制哦! 秘自信女人帮 微信号: zixinnvrenbang 【←长按复制】 推荐理由:?中国女性第一提升自信微刊。自信是一个女人最重要的气质。如果
 第1页:微信6.0界面微调更扁平   国庆前一天,微信发布了全新6.0版,新版本最大的亮点就是增加了“小视频”功能,可以在聊天或朋友圈中即时拍摄一段小视频分享给好友。微信6.0赶在国庆之前更新,是让大家国庆出游时都来秀视频的节奏吗?我们的几位编辑试用后一致认为,这个功能还是挺有意思的,并且可以预想到的是,有了小视频,以后的微信朋友圈将不再宁静!下面我们就一起来看看,微信6.0小视频功能怎么用,微
  前几天跟大家说我的个人微信还能加 2000 人左右,然后收到一万多条留言,费了好大力气 - 真的是好大力气 - 看完全部留言之后,给一千多人发出了我的个人微信 ID,直到最后不能加人为止。  微信现在的规则是每个人的上限是 5000,超过限额就不允许继续新增联系人。这 5000 的限制包括你所订阅的公众帐号数量,在最开始的时候单向关注也被计算在内,所以实际上我的个人微信联系人还不到 4500
据爆料,微信将在下一个版本中带来一系列新功能,并且有望改变用户现有的部分使用习惯。 主要更新内容包括:1、通讯录新增自定义分组头像,删除好友改为双向删除;2、朋友圈新增倒喝彩功能,累计次数有惩罚;3、朋友圈新增搜索功能,查好友查信息,简单快捷;4、新增“热门话题”,朋友圈分享次数最高图文聚集地;5、公众号新增“星标公众账号”和批量取消关注功能;6、群功能增加群头像和群公告及群视频;7、小视频优化
很多人都感觉为什么我的朋友圈好友很少互动,每次发完微信,也没有几个人评论,连点赞者都寥寥无几,久而久之就没有了写微信的那种乐趣和激情。相信有这种情况的朋友比比皆是,这是为什么呢?如何才可以提高你朋友圈的互动频率,引爆朋友圈,让更多的好友参与进来,和你一起互动呢?相信关注我朋友圈的朋友都知道,我的朋友圈每条微信的互动参与人数都很高,基本上都是几百条评论(不含点赞),为什么我的朋友圈会有如此多的人参与
▲▲怎么让微信朋友圈所在地理位置作假显示在别的城市,▲▲需改的加微信Abbyad,我之前也是找她帮代发的 ,赞 第一步:在手机--设置 第二步:在手机--设置--位置和安全- 第三步 在手机--设置-开发--允许模拟地址 第四步:搜索想定位的地址城市 【需改的加微信 Abbyad】 基本全国详细城市都可以改发 。 如图: 中国 1. 北京市 东城区 西城区
【分享】微信改地理位置,不懂的朋友可加微信:Abbyad ,而且他还帮专门设计软件的图标和软件名称,想怎么改软件都行 改朋友圈地址和所在城市其实是利用模拟GPS坐标。 第一步:在手机--设置--位置和安全--开启GPS卫星定位功能第二步:在手机--设置--位置和安全--关闭使用无线网络 部分系统是停用AGPS第三步 在手机--设置--应用软件--开发--允许模拟地址第四步:搜索想定位的地址城市,
在微信朋友圈里分享信息和心情已成为人们一种日常的生活习惯了,然而一般发送朋友圈信息的时候是要点照相机的那个按钮连同发过文字和图片一起的,今天小编介绍一下怎么用微信发送纯文字信息; 工具/原料 装有微信的智能手机一部 方法/步骤 1 登录自己的微信账号,进入主界面,打开朋友圈; 2 点击进入朋友圈,进入信息分享发送界面,长按上
万万没想到朋友圈还能这么玩
13:06:49来源:中国商业新闻网作者:责任编辑:杨帆   360云盘因免费、无限空间终结了云存储的空间大战,并一举成为最受欢迎的网盘产品,全球最大的个人云储存空间。近日,360发布云盘6.0版,携好友系统,重磅上线,重新定义分享。新版本给大家带来了哪些惊喜的功能呢?   共享相册:秒传高清图片 聚会照片分享从此无忧   周末一起去郊游聚
正文:分析图在设计的前中后其分别起着不同的作用,前期可以理清自己的设计思路,中期有助于设计方案的推进,后期可以更充分的表达方案本身,所以好的设计分析图往往在一个方案中有画龙点睛的作用。不论是用手绘还是软件去做分析图,我们要了解分析图的形式,画分析图的目的,这些图拿去吧,相信会有助于你对分析图有更深的理解。 &img src=&a
什么男人手机不敢给女人看?为什么女人手机不敢给男人看?手机里的秘密手机里的隐私,这些帐号都有非常好的内容,绝对是你的隐私小场所哦!!2014年最火爆的订阅号,超过500万人关注 关注方法  长按蓝色框里的微信号,复制并在微信菜单“添加朋友”--&“查找公众号”里粘贴、搜索该微信号。 热门微信推荐 女人天生爱美丽微信号:AML106 推荐指数:★★★★★ 推荐理由:没有丑女人
戳右交个朋友吧→今日女报 新朋友点图片上方 今日女报 一键关注! 老朋友点右上角,转发或分享朋友圈! 你还相信那些七拼八凑的“十大”吗?在信息碎片的时代,我们关注这样一批微信公共账号——他们的内容总是能在朋友圈里掀起波澜、无限扩散……再不关注,你还好意思和朋友打招呼么?   果壳网   ID:guokr42   开放、多元的泛科技兴趣社区。每天提供一波负责
2014年,金马贺岁。   生活总是充满希望的,成功总是属于同朋友们一起分享的您。   新年新风新气象,送礼也有新变化。在这个万象更新的日子,特发送低炭环保的2014年电子挂历一份,祝您在新的一年里,365天天天开心,8760小时时时快乐,525600分分分健康,秒秒秒幸福!
2014年,金马贺岁。   生活总是充满希望的,成功总是属于同朋友们一起分享的您。   新年新风新气象,送礼也有新变化。在这个万象更新的日子,特发送低炭环保的2014年电子挂历一份,祝您在新的一年里,365天天天开心,8760小时时时快乐,525600分分分健康,秒秒秒幸福! 评论
  最近流行无边框手机,告诉你,弱爆了,要想提升逼格,还得是下面这款“全透明手机”。   近日,一张透明手机的图片在社交网站Instagram上引起热议,被称作“instagraminmyhand”, 意为Instagram在我的手中。   随后不少网友都晒出了自己的透明手机,照片上的手机仅有一个屏幕,没有边框、没有按键、没有摄像头...啥都没有,世上果真有如此神器? 当然不是,其实很简单,只要
2015年就要来了,大家除了倒数,也别忘了刷朋友圈啊!想要用独特的方式和新年打招呼吗?想要独一无二又有趣的配图吗?来阿联这里就对了!
朋友圈里整天都是文字照片小视频,你早都腻了吧,柠檬仔教你玩点新鲜的,发语音到朋友圈!别拘束,怒喊破音都秀起来吧!第一步,打开“西安快乐柠檬”聊天界面(你不关注我可是看不到的哦),点击下面自定义菜单中的“活动娱乐”进入“朋友圈发语音”栏目。第二步,进入录制界面即可录制个性语音。第三步,录制完成后,点击右上角就可以分享到朋友圈亮瞎众人了。就是这么简单啊!赶紧发一个试试吧!
  想知道微信怎么点歌吗?下文演示了微信在线点歌教程,通过一个微信公众号就能够轻松点歌哦~想知道方法吗?那么就与绿茶小编一起来学习一下如何点歌送给好友吧~   第一步、登陆微信后点击右上角“+”--“添加朋友”。微信下载: 微信安卓版 微信iPhone版 微信电脑版   第二步、输入“点歌台”后点击“搜索”。   第三步、添加“点歌台”成功之后,可以看到“点歌台”给出的
怎么分享电脑网页上的文章到微信圈 | 浏览:1439 | 更新: 16:46 | 标签:网页 1 2 3 分步阅读 电脑上看到了一篇很好的文章,可是突然发出了这样的疑问,文章怎么才能分享到自己的微信圈呢?想必大家也曾遇到过吧,我来告诉大家 工具/原料 微信扫一扫 方法/步骤
微信标签类似于QQ印象,可以给自己的微信好友贴上一些个性标签,方面我们查找类似特性的好友。以下小编主要为大家介绍一下,微信标签怎么用,主要涉及给微信好友添加和删除表现的方法,下面是具体的教程! 微信标签怎么用之怎么给好友添加标签 一、从手机中进入微信,然后进入“通讯录”,然后找到一个想要给其加标签的微信好友,点击进入,如下图所示。 二、之后在弹出的界面中选择“设置备注及标签”,然后就可以给微
第一步:注册微信公众平台登录微信公众平台网站https://mp.weixin.qq.com/ 点“立即注册” 填写邮箱帐号密码后上传身份证和照片。等待腾讯审核通过。一般需要3-5日。 第二步:设置微信公众平台的开发模式过几日到微信公众平台网站https://mp.weixin.qq.com/查看申请是否通过,在登录框中填入你申请的账户名和密码。登录成功后,点击左边的“功能“,高级功能”,然后选
  在最新发布的微信5.0.3版本中新增了清理微信存储空间功能,如果在使用微信之后都没有为微信清理缓存的话,那最好使用一下这个功能哦~接下来就告诉大家微信怎么清理缓存。 微信下载: 微信安卓版 微信iPhone版 微信电脑版   1、首先要下载微信5.0.3以上版本,然后点击进入,在主界面点击“我”后再点击“设置”。   2、进入微信“设置”后点击“通用”就,然后就能看到“清理微信
微信小视频怎么用? 首先确定你现在使用的是微信6.0版本,6.0以下的微信是没有小视频功能的。 打开手机微信6.0进行登录,然后先点击发现列表中的“朋友圈” 进入朋友圈页面后在右上角轻轻触摸一下“照相机”图标。 在弹出来的小版面上选择“小视频” 纪念日拍摄界面后用手指触摸长按“拍摄键”,手指往上移动可以取消拍摄。 注:目前微信6.0小视频只能拍摄6秒的短视频。 8秒短视频拍摄完成后
我相信很多朋友都是使用QQ来绑定微信了,在微信登录时也是使用QQ了,这样虽然方便但是有时候我们希望给微信设置独立的密码这样对于我们来讲要安全得多,那么要如何设置呢?下面我们一起来看具体的设置过程。 1)在手机中我们点击“微信”然后如图有一个“我”菜单我们点击进入。 2)然后我们点击“我”然后就会进入“个人信息”界面了,在这里我们找到“我的帐号”轻点击进入。 3)现在在我的报帐号界面
自今年微信5.2.1 for iPhone 发布后,新增了拍照与分享的功能,即可以把所拍照片自己收藏,还可以分别发送给多个朋友。下面为大家带来拍照分享如何分别发送多个好友的操作方法。1)首先打开“微信”,然后点击右上角的“+”。(如下图所示)2)在“+”中点击“拍照分享”,然后就可以拍摄照片。(如下图所示)3)拍摄完照片之后,点击右上角的“使用”,然后点击“分别发送给朋友”(如下图所示)4)选择需
深度玩法 一般人不告诉他 导言 微信社交,你会考虑陌生人吗? 你会进入陌生人的圈子吗? 改变你现有生活和际遇的往往是新朋友! 有时候,你发现新朋友,会比你的老友、同学更给力! 微信是巨大的时光社区,你需要掌握“星转腾挪”的功夫! 正文 一、微信是啥(巨大时光社区) 你的微信,是一个巨大的时光社区; 1,好友数是社区的居民数;大部分人不认识,属常态; 2,朋友圈是社区的公告拦,可以分享
微信可以发语音啦,你还不知道就OUT了,赶集收藏! 朋友圈发语音教程 ?根据提示开始录制语音 ? 录制完成,点击右上方按钮分享到朋友圈。 大功告成,新技能get√ 趁过节用语音表个白啊啥的 快转起告诉小伙伴吧!!!!!
微信5.2版本开始加入了共享实时位置的功能。下面小编为大家讲解下这个功能如何使用。 第一步: 首先到应用宝申请内测资格。打开“应用宝”软件平台,点击顶部的广告,进入“微信5.2”的下载页就能完成内测资格的申请。 第二步: 把微信下载到手机,打开微信,界面就完全不一样了。想要打开“共享实时位置功能”需要在“聊天”栏目选择聊天的朋友,点击进入“聊天界面”。 第三步: 在输入框旁边点击“+”
电费的查询和缴纳伴随信息化的覆盖也更加智能便捷,不仅能够支持在网上查询,通过微信也能够实现各项明细的查询以及进行缴费,更加的简单直观,下面就展示下具体的操作方法。 方法/步骤 1 通过微信查电费相比网页查询更加简单,真正做到随时随地查询电费明细,需要做的首先是关注自己缴费单位的公共号。 点击微信页面的“+”号按钮打开“添加好友”功能。 2 在搜索栏中输
喜欢该文的人也喜欢android手机上微信自定义表情如何批量添加? - 知乎有问题,上知乎。知乎作为中文互联网最大的知识分享平台,以「知识连接一切」为愿景,致力于构建一个人人都可以便捷接入的知识分享网络,让人们便捷地与世界分享知识、经验和见解,发现更大的世界。10被浏览<strong class="NumberBoard-itemValue" title="1分享邀请回答0添加评论分享收藏感谢收起写回答2 个回答被折叠()【Android 仿微信通讯录 导航分组列表-下】自定义View为RecyclerView打造... - 简书
【Android 仿微信通讯录 导航分组列表-下】自定义View为RecyclerView打造右侧索引导航栏IndexBar
**本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
转载请标明出处:
本文出自: ()
代码传送门:喜欢的话,随手点个star。多谢
在上篇文章() 里,我们用ItemDecoration为RecyclerView打造了带悬停头部的分组列表。
其实Android版微信的通讯录界面,它的分组title也不是悬停的,我们已经领先了微信一小步(认真脸)~
再看看市面上常见的分组列表(例如饿了么点餐商品列表),不仅有悬停头部,悬停头部在切换时,还会伴有切换动画。
关于ItemDecoration还有一个问题,简单布局还好,我们可以draw出来,如果是复杂的头部呢?能否写个xml,inflate进来,这样使用起来才简单,即另一种简单使用onDraw和onDrawOver的姿势。
so,本文开头我们就先用两节完善一下我们的ItemDecoration。
然后进入正题:自定义View实现右侧索引导航栏IndexBar,对数据源的排序字段按照拼音排序,最后将RecyclerView和IndexBar联动起来,触摸IndexBar上相应字母,RecyclerView滚动到相应位置。(在屏幕中间显示的其实就是一个TextView,我们set个体IndexBar即可)
由于大部分使用右侧索引导航栏的场景,都需要这几个固定步骤,对数据源排序,set给IndexBar,和RecyclerView联动等,所以最后再将其封装一把,成一个高度封装,因此扩展性不太高的控件,更方便使用,如果需要扩展的话,反正看完本文再其基础上修改应该很简单~。
最终版预览:
这里写图片描述
用ItemDecoration实现悬停头部切换动画
另一种简单使用onDraw()和onDrawOver()的姿势
自定义View实现右侧索引导航栏IndexBar
使用TinyPinyin对数据源排序
联动IndexBar和RecyclerView。
封装重复步骤,方便二次使用,并可定制导航数据源。
二 悬停头部的“切换动画”
实现了两种,
第一种就是仿饿了么点餐时,商品列表的悬停头部切换“动画效果”,如下:
这里写图片描述
第二种是一种头部折叠起来的视效,个人觉得也还不错~如下:(估计没人喜欢)
这里写图片描述
果然比上部残篇里的效果好看多了,那么代码多不多呢,看我的git show 记录:
这里写图片描述
就绿色部分的不到十行代码就搞定~先上这个图是为了让大家安心,代码不多,分分钟看完。
下面放上文字版代码,江湖人称 注释张 的我,已经写满了注释,
再简单说下吧,
滑动时,在判断头部即将切换(当前pos的tag和pos+1的tag不等)的时候,
1.计算出当前悬停头部应该上移的位移,
利用Canvas的画布移动方法Canvas.translate(),即可实现“饿了么”悬停头部切换效果。
2.计算出当前悬停头部应该在屏幕上还剩余的空间高度,作为头部绘制的高度
利用Canvas的Canvas.clipRect()方法,剪切画布,即可实现“折叠”的视效。
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {//最后调用 绘制在最上层
int pos = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
String tag = mDatas.get(pos).getTag();
//View child = parent.getChildAt(pos);
View child = parent.findViewHolderForLayoutPosition(pos).itemV//出现一个奇怪的bug,有时候child为空,所以将 child = parent.getChildAt(i)。-》 parent.findViewHolderForLayoutPosition(pos).itemView
boolean flag =//定义一个flag,Canvas是否位移过的标志
if ((pos + 1) & mDatas.size()) {//防止数组越界(一般情况不会出现)
if (null != tag && !tag.equals(mDatas.get(pos + 1).getTag())) {//当前第一个可见的Item的tag,不等于其后一个item的tag,说明悬浮的View要切换了
Log.d("zxt", "onDrawOver() called with: c = [" + child.getTop());//当getTop开始变负,它的绝对值,是第一个可见的Item移出屏幕的距离,
if (child.getHeight() + child.getTop() & mTitleHeight) {//当第一个可见的item在屏幕中还剩的高度小于title区域的高度时,我们也该开始做悬浮Title的“交换动画”
c.save();//每次绘制前 保存当前Canvas状态,
//一种头部折叠起来的视效,个人觉得也还不错~
//可与123行 c.drawRect 比较,只有bottom参数不一样,由于 child.getHeight() + child.getTop() & mTitleHeight,所以绘制区域是在不断的减小,有种折叠起来的感觉
//c.clipRect(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + child.getHeight() + child.getTop());
//类似饿了么点餐时,商品列表的悬停头部切换“动画效果”
//上滑时,将canvas上移 (y为负数) ,所以后面canvas 画出来的Rect和Text都上移了,有种切换的“动画”感觉
c.translate(0, child.getHeight() + child.getTop() - mTitleHeight);
mPaint.setColor(COLOR_TITLE_BG);
c.drawRect(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + mTitleHeight, mPaint);
mPaint.setColor(COLOR_TITLE_FONT);
mPaint.getTextBounds(tag, 0, tag.length(), mBounds);
c.drawText(tag, child.getPaddingLeft(),
parent.getPaddingTop() + mTitleHeight - (mTitleHeight / 2 - mBounds.height() / 2),
c.restore();//恢复画布到之前保存的状态
这份代码核心处c.translate(0, child.getHeight() + child.getTop() - mTitleHeight);实现的是饿了么效果,被注释掉的
//c.clipRect(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + child.getHeight() + child.getTop());
实现的是效果二。
三 另一种使用onDraw()和onDrawOver()的姿势
之前我们使用onDraw(),onDrawOver(),都是用canvas的方法活生生的绘制一个出View,这对于很多人(包括我)来说都不容易,xy坐标的确认,尺寸都较难把握,基本上调UI效果时间都很长。尤其是canvas.drawText()方法的y坐标,其实是baseLine的位置,不了解的童鞋肯定要踩很多坑。
当我们想要绘制的分类title、悬停头部复杂一点时,我都不敢想象要调试多久了,这个时候我们还敢用ItemDecoration吗。
有没有一种方法,就像我们平时使用的那样,在Layout布局xml里画好View,然后inflate出来就可以了呢。
这个问题开始确实也把我难住了,难道又要从入门到放弃了吗?
于是我又搜寻资料,功夫不负有心人。
解决问题的办法就是,View类的:public void draw(Canvas canvas) {方法
下面我们就看一个用法Demo吧:
布局layout:header_complex.xml(注意有个ProgressBar哦)
&?xml version="1.0" encoding="utf-8"?&
&LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark"
android:orientation="vertical"&
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"
android:text="复杂头部" /&
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="复杂头部"
android:textColor="@color/colorAccent" /&
&ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content" /&
&/LinearLayout&
onDrawOver代码如下:简单讲解下,先inflate这个复杂的Layout,然后拿到它的LayoutParams,利用这个lp拿到宽和高的MeasureSpec,然后依次调用 measure,layout,draw方法,将复杂头部显示在屏幕上。
(小安利一下,MeasureSpec不太了解的可以看看我的这篇)
View toDrawView = mInflater.inflate(R.layout.header_complex, parent, false);
int toDrawWidthS//用于测量的widthMeasureSpec
int toDrawHeightS//用于测量的heightMeasureSpec
//拿到复杂布局的LayoutParams,如果为空,就new一个。
// 后面需要根据这个lp 构建toDrawWidthSpec,toDrawHeightSpec
ViewGroup.LayoutParams lp = toDrawView.getLayoutParams();
if (lp == null) {
lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);//这里是根据复杂布局layout的width height,new一个Lp
toDrawView.setLayoutParams(lp);
if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
//如果是MATCH_PARENT,则用父控件能分配的最大宽度和EXACTLY构建MeasureSpec。
toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.EXACTLY);
} else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
//如果是WRAP_CONTENT,则用父控件能分配的最大宽度和AT_MOST构建MeasureSpec。
toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.AT_MOST);
//否则则是具体的宽度数值,则用这个宽度和EXACTLY构建MeasureSpec。
toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(lp.width, View.MeasureSpec.EXACTLY);
//高度同理
if (lp.height == ViewGroup.LayoutParams.MATCH_PARENT) {
toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.EXACTLY);
} else if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.AT_MOST);
toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(lp.width, View.MeasureSpec.EXACTLY);
//依次调用 measure,layout,draw方法,将复杂头部显示在屏幕上。
toDrawView.measure(toDrawWidthSpec, toDrawHeightSpec);
toDrawView.layout(parent.getPaddingLeft(), parent.getPaddingTop(),
parent.getPaddingLeft() + toDrawView.getMeasuredWidth(), parent.getPaddingTop() + toDrawView.getMeasuredHeight());
toDrawView.draw(c);
这里还有个有趣的地方,某些需要不断调用onDraw()更新绘制自己最新状态的View,例如ProgressBar,由于在屏幕上显示的并不是真正的View,只是我们手动的调用了一次draw方法,进而调用View的onDraw()显示的一次“残影”,所以ProgressBar只会显示onDraw()当时的样子,并不会主动刷新了。
看图说话,还是很容易理解的:
这里写图片描述
滑动时,由于会回调onDrawOver() 方法,所以ProgressBar又被手动调用了draw(),开始变化,滑动的快的话,progressBar会有动画效果。
停止不动时,ProgressBar也是静止的,保持draw()时绘制的状态。
四 自定义View实现右侧索引导航栏IndexBar
不管是自定义ItemDecoration还是实现右侧索引导航栏,都有大量的自定义View知识在里面 ,这里简单复习一下。
(步骤1-4是自定义View的必须套路,步骤5+是IndexBar特殊定制)
1 自定义View首先要确定这个View需要在xml里接受哪些属性?
在IndexBar里,我们先需要两个属性,每个索引的文字大小和手指按下时整个View的背景,
即在attrs.xml如下定义:
&attr name="textSize" format="dimension" /&
&declare-styleable name="IndexBar"&
&attr name="textSize" /&
&attr name="pressBackground" format="color" /&
&/declare-styleable&
2 在View的构造方法中获得我们自定义的属性
套路代码如下,都是套路,记得使用完最后要将typeArray对象 recycle()。
int textSize = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics());//默认的TextSize
mPressedBackground = Color.BLACK;//默认按下是纯黑色
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.IndexBar, defStyleAttr, 0);
int n = typedArray.getIndexCount();
for (int i = 0; i & i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.IndexBar_textSize:
textSize = typedArray.getDimensionPixelSize(attr, textSize);
case R.styleable.IndexBar_pressBackground:
mPressedBackground = typedArray.getColor(attr, mPressedBackground);
typedArray.recycle();
3 重写onMesure()方法(可选)
onMeasure()方法里,主要就是遍历一遍indexDatas,得到index最大宽度和高度。然后根据三种测量模式,分配不同的值给View,
EXACLTY就分配具体的测量值(match_parent,确定数值),
AT_MOST就分配父控件能给的最大值和自己需要的值之间的最小值。(保证不超过父控件限定的值)
UNSPECIFIED则分配自己需要的值。(随心所欲)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//取出宽高的MeasureSpec
Mode 和Size
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int measureWidth = 0, measureHeight = 0;//最终测量出来的宽高
//得到合适宽度:
Rect indexBounds = new Rect();//存放每个绘制的index的Rect区域
S//每个要绘制的index内容
for (int i = 0; i & mIndexDatas.size(); i++) {
index = mIndexDatas.get(i);
mPaint.getTextBounds(index, 0, index.length(), indexBounds);//测量计算文字所在矩形,可以得到宽高
measureWidth = Math.max(indexBounds.width(), measureWidth);//循环结束后,得到index的最大宽度
measureHeight = Math.max(indexBounds.width(), measureHeight);//循环结束后,得到index的最大高度,然后*size
measureHeight *= mIndexDatas.size();
switch (wMode) {
case MeasureSpec.EXACTLY:
measureWidth = wS
case MeasureSpec.AT_MOST:
measureWidth = Math.min(measureWidth, wSize);//wSize此时是父控件能给子View分配的最大空间
case MeasureSpec.UNSPECIFIED:
//得到合适的高度:
switch (hMode) {
case MeasureSpec.EXACTLY:
measureHeight = hS
case MeasureSpec.AT_MOST:
measureHeight = Math.min(measureHeight, hSize);//wSize此时是父控件能给子View分配的最大空间
case MeasureSpec.UNSPECIFIED:
setMeasuredDimension(measureWidth, measureHeight);
4 重写onDraw()方法
整理一下需求和思路:
利用index数据源的size,和控件可绘制的高度(高度-paddingTop-paddingBottom),求出每个index区域的高度mGapHeight。
每个index在绘制时,都是处于水平居中,竖直方向上在mGapHeight区域高度内居中。
思路整理清楚,代码很简单如下:
public static String[] INDEX_STRING = {"A", "B", "C", "D", "E", "F", "G", "H", "I","J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V","W", "X", "Y", "Z", "#"};//#在最后面(默认的数据源)
private List&String& mIndexD//索引数据源
private int mGapH//每个index区域的高度
mIndexDatas = Arrays.asList(INDEX_STRING);//数据源
在onSizeChanged方法里,获取控件的宽高,并计算出mGapHeight:
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mGapHeight = (mHeight - getPaddingTop() - getPaddingBottom()) / mIndexDatas.size();
最后在onDraw()方法里绘制,
如果对于竖直居中baseLine的计算不太理解可以先放置,这块的确挺绕人,后面应该会写一篇 canvas.drawText()x y坐标计算的小短文.
可记住重点就是 Paint默认的TextAlign是Left,即x方向,左对齐,所以x坐标决定绘制文字的左边界。
y坐标是绘制文字的baseLine位置。
protected void onDraw(Canvas canvas) {
int t = getPaddingTop();//top的基准点(支持padding)
Rect indexBounds = new Rect();//存放每个绘制的index的Rect区域
S//每个要绘制的index内容
for (int i = 0; i & mIndexDatas.size(); i++) {
index = mIndexDatas.get(i);
mPaint.getTextBounds(index, 0, index.length(), indexBounds);//测量计算文字所在矩形,可以得到宽高
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();//获得画笔的FontMetrics,用来计算baseLine。因为drawText的y坐标,代表的是绘制的文字的baseLine的位置
int baseline = (int) ((mGapHeight - fontMetrics.bottom - fontMetrics.top) / 2);//计算出在每格index区域,竖直居中的baseLine值
canvas.drawText(index, mWidth / 2 - indexBounds.width() / 2, t + mGapHeight * i + baseline, mPaint);//调用drawText,居中显示绘制index
以上四步基本完成了IndexBar的绘制工作,下面我们为它添加一些行为的响应。
5 重写onTouchEvent()方法
我们需要重写onTouchEvent()方法,
以便处理手指按下时的View背景变色,抬起时恢复原来颜色
并根据手指触摸的落点坐标,判断当前处于哪个index区域,回调给相应的监听器处理(显示当前index的值,滑动RecyclerView至相应区域等。。)
代码如下:
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setBackgroundColor(mPressedBackground);//手指按下时背景变色
//注意这里没有break,因为down时,也要计算落点 回调监听器
case MotionEvent.ACTION_MOVE:
float y = event.getY();
//通过计算判断落点在哪个区域:
int pressI = (int) ((y - getPaddingTop()) / mGapHeight);
//边界处理(在手指move时,有可能已经移出边界,防止越界)
if (pressI & 0) {
pressI = 0;
} else if (pressI &= mIndexDatas.size()) {
pressI = mIndexDatas.size() - 1;
//回调监听器
if (null != mOnIndexPressedListener) {
mOnIndexPressedListener.onIndexPressed(pressI, mIndexDatas.get(pressI));
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
setBackgroundResource(android.R.color.transparent);//手指抬起时背景恢复透明
//回调监听器
if (null != mOnIndexPressedListener) {
mOnIndexPressedListener.onMotionEventEnd();
6 联动IndexBar和RecyclerView
具体的操作交由监听器处理,定义和实现如下:
值得一提的就是,滑动RecyclerView到指定postion,我们使用的是LinearLayoutManager的scrollToPositionWithOffset(int position, int offset)方法,offset传入0,postion即目标postion即可。如果使用RecyclerView.scrollToPosition();等方法,滑动会很飘~定位不准。
mPressedShowTextView 就是在屏幕中间显示的当前处于哪个index的TextView。
* 当前被按下的index的监听器
public interface onIndexPressedListener {
void onIndexPressed(int index, String text);//当某个Index被按下
void onMotionEventEnd();//当触摸事件结束(UP CANCEL)
private onIndexPressedListener mOnIndexPressedL
public void setmOnIndexPressedListener(onIndexPressedListener mOnIndexPressedListener) {
this.mOnIndexPressedListener = mOnIndexPressedL
//设置index触摸监听器
setmOnIndexPressedListener(new onIndexPressedListener() {
public void onIndexPressed(int index, String text) {
if (mPressedShowTextView != null) { //显示hintTexView
mPressedShowTextView.setVisibility(View.VISIBLE);
mPressedShowTextView.setText(text);
if (mLayoutManager != null) {
int position = getPosByTag(text);
if (position != -1) {
mLayoutManager.scrollToPositionWithOffset(position, 0);
public void onMotionEventEnd() {
//隐藏hintTextView
if (mPressedShowTextView != null) {
mPressedShowTextView.setVisibility(View.GONE);
五 封装重复步骤,方便二次使用。
在我个人的理解里,程序过多的封装是会导致扩展性的降低(也是因为我水平有限),然而我们今天要封装的这个IndexBar,由于使用场景和套路还是挺固定的(城市分组列表,商品分类列表)所以值得将相关的操作都聚合起来,二次使用更方便。毕竟,一个项目里同样的代码写第二遍的程序员都不是好的圣斗士。(其实是我的leader不想写第二遍,让我封装一下给他秒用)
梳理一下固定的操作:
1 都是先对原始数据sourceDatas源按照排序字段拼音排序。
2 然后将屏幕中hint的TextView ,以及索引数据源indexDatas(通过sourceDatas获得),通过set方法传给IndexBar。
3 联动IndexBar和RecyclerView,使得触摸IndexBar相应区域RecyclerView会滚动(借助sourceDatas获得对应postion)。
根据上述,我的设想在使用时,只需要给IndexBar设置 原始数据sourceDatas,HintTextView,和RecyclerView的LinearLayoutManager,在IndexBar内部对sourceDatas排序,并获得索引数据源indexDatas,然后设置一个默认的index触摸监听器,在手指按下滑动时,由于IndexBar持有HintTextView和LayoutManager,则HintTextView的show hide,以及LayoutManager的滚动 都在IndexBar内部完成。
最终使用预览:
//使用indexBar
mTvSideBarHint = (TextView) findViewById(R.id.tvSideBarHint);//HintTextView
mIndexBar = (IndexBar) findViewById(R.id.indexBar);//IndexBar
mIndexBar.setmPressedShowTextView(mTvSideBarHint)//设置HintTextView
.setNeedRealIndex(true)//设置需要真实的索引
.setmLayoutManager(mManager)//设置RecyclerView的LayoutManager
.setmSourceDatas(mDatas);//设置数据源
&?xml version="1.0" encoding="utf-8"?&
&FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"&
&android.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"&
&/android.support.v7.widget.RecyclerView&
&mcxtzhang.itemdecorationdemo.IndexBar.widget.IndexBar
android:id="@+id/indexBar"
android:layout_width="24dp"
android:layout_height="match_parent"
android:layout_gravity="right"
app:pressBackground="@color/partTranslucent"
app:textSize="16sp" /&
android:id="@+id/tvSideBarHint"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center"
android:background="@drawable/shape_side_bar_bg"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="48sp"
android:visibility="gone"
tools:text="A"
tools:visibility="visible" /&
&/FrameLayout&
其中,setNeedRealIndex(true)//设置需要真实的索引,是指索引栏的数据不是固定的A-Z,#。而是根据真实的sourceDatas生成。
因为链式调用用起来很爽,所以在这些set方法里都return 了 this。
1 抽象两个实体类和一个接口。
先把tag抽象出来,放在顶层,这里存放的就是IndexBar显示的每个index值(A-Z,#)(本例是城市的汉语拼音首字母),而且在联动滑动时,根据tag获取postion时,也需要用到tag。它是导航分组列表的基础。
public class BaseIndexTagBean {
private S//所属的分类(城市的汉语拼音首字母)
public String getTag() {
public void setTag(String tag) {
this.tag =
然后抽象一个接口和一个实体类,
接口定义一个方法getTarget(),它返回 需要被转化成拼音,并取出首字母 索引排序的 字段。(本例就是城市的名字)
实体类继承BaseIndexTagBean,并实现以上接口,且额外存放 需要排序的字段的拼音值,(本例是城市的拼音)。它根据getTarget()返回的值利用TinyPinyin库得到拼音。
public interface IIndexTargetInterface {
String getTarget();//需要被转化成拼音,并取出首字母 索引排序的 字段
public abstract class BaseIndexPinyinBean extends BaseIndexTagBean implements IIndexTargetInterface {
private String pyC//城市的拼音
public String getPyCity() {
return pyC
public void setPyCity(String pyCity) {
this.pyCity = pyC
有了以上两个类一个接口,我们就可以将 对原始数据源sourceDatas按照拼音排序,并取出索引数据源indexDatas的操作封装起来。
2 封装原始数据源初始化(利用TinyPinyin获取全拼音),取出索引数据源indexDatas的操作。
使用时,我们先让具体的实体bean,继承自BaseIndexPinyinBean ,在getTarget()方法返回排序目标字段。本例如下:
public class CityBean extends BaseIndexPinyinBean {
private S//城市名字
public CityBean() {
public CityBean(String city) {
this.city =
public String getCity() {
public void setCity(String city) {
this.city =
public String getTarget() {
IndexBar类内代码:
使用时会调用IndexBar.setmSourceDatas()方法传入原始数据源,在方法内对数据源初始化,并取出索引数据源。
private List&? extends BaseIndexPinyinBean& mSourceD//Adapter的数据源
public IndexBar setmSourceDatas(List&? extends BaseIndexPinyinBean& mSourceDatas) {
this.mSourceDatas = mSourceD
initSourceDatas();//对数据源进行初始化
* 初始化原始数据源,并取出索引数据源
private void initSourceDatas() {
int size = mSourceDatas.size();
for (int i = 0; i & i++) {
BaseIndexPinyinBean indexPinyinBean = mSourceDatas.get(i);
StringBuilder pySb = new StringBuilder();
String target = indexPinyinBean.getTarget();//取出需要被拼音化的字段
//遍历target的每个char得到它的全拼音
for (int i1 = 0; i1 & target.length(); i1++) {
//利用TinyPinyin将char转成拼音
//查看源码,方法内 如果char为汉字,则返回大写拼音
//如果c不是汉字,则返回String.valueOf(c)
pySb.append(Pinyin.toPinyin(target.charAt(i1)));
indexPinyinBean.setPyCity(pySb.toString());//设置城市名全拼音
//以下代码设置城市拼音首字母
String tagString = pySb.toString().substring(0, 1);
if (tagString.matches("[A-Z]")) {//如果是A-Z字母开头
indexPinyinBean.setTag(tagString);
if (isNeedRealIndex) {//如果需要真实的索引数据源
if (!mIndexDatas.contains(tagString)) {//则判断是否已经将这个索引添加进去,若没有则添加
mIndexDatas.add(tagString);
} else {//特殊字母这里统一用#处理
indexPinyinBean.setTag("#");
if (isNeedRealIndex) {//如果需要真实的索引数据源
if (!mIndexDatas.contains("#")) {
mIndexDatas.add("#");
sortData();
3 封装对原始数据源sourceDatas,索引数据源indexDatas的排序操作。
* 对数据源排序
private void sortData() {
//对右侧栏进行排序 将 # 丢在最后
Collections.sort(mIndexDatas, new Comparator&String&() {
public int compare(String lhs, String rhs) {
if (lhs.equals("#")) {
} else if (rhs.equals("#")) {
return -1;
return lhs.compareTo(rhs);
//对数据源进行排序
Collections.sort(mSourceDatas, new Comparator&BaseIndexPinyinBean&() {
public int compare(BaseIndexPinyinBean lhs, BaseIndexPinyinBean rhs) {
if (lhs.getTag().equals("#")) {
} else if (rhs.getTag().equals("#")) {
return -1;
return lhs.getPyCity().compareTo(rhs.getPyCity());
4 是否需要真实的索引数据源。
相关变量定义:
public static String[] INDEX_STRING = {"A", "B", "C", "D", "E", "F", "G", "H", "I","J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"};//#在最后面(默认的数据源)
private List&String& mIndexD//索引数据源
private boolean isNeedRealI//是否需要根据实际的数据来生成索引数据源(例如 只有 A B C 三种tag,那么索引栏就 A B C 三项)
初始化init时,判断不需要真实的索引数据源,就用默认值(A-Z,#)
if (!isNeedRealIndex) {//不需要真实的索引数据源
mIndexDatas = Arrays.asList(INDEX_STRING);
使用时,如果如果真实索引数据源,调用这个方法,传入true,一定要在设置数据源setmSourceDatas(List)之前调用。
* 一定要在设置数据源{@link #setmSourceDatas(List)}之前调用
* @param needRealIndex
public IndexBar setNeedRealIndex(boolean needRealIndex) {
isNeedRealIndex = needRealI
if (isNeedRealIndex){
if (mIndexDatas != null) {
mIndexDatas = new ArrayList&&();
在initSourceDatas() 里,会根据这个变量往mIndexDatas里增加index。
5 IndexBar和外部联动的相关(HintTextView,和RecyclerView的LinearLayoutManager)
set方法很简单:
public IndexBar setmPressedShowTextView(TextView mPressedShowTextView) {
this.mPressedShowTextView = mPressedShowTextV
public IndexBar setmLayoutManager(LinearLayoutManager mLayoutManager) {
this.mLayoutManager = mLayoutM
它们两最终都是在index触摸监听器里用到,代码上文已提及,只不过这次挪到IndexBar内部init里。
init函数如下:
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
if (!isNeedRealIndex) {//不需要真实的索引数据源
mIndexDatas = Arrays.asList(INDEX_STRING);
//设置index触摸监听器
setmOnIndexPressedListener(new onIndexPressedListener() {
public void onIndexPressed(int index, String text) {
if (mPressedShowTextView != null) { //显示hintTexView
mPressedShowTextView.setVisibility(View.VISIBLE);
mPressedShowTextView.setText(text);
if (mLayoutManager != null) {
int position = getPosByTag(text);
if (position != -1) {
mLayoutManager.scrollToPositionWithOffset(position, 0);
public void onMotionEventEnd() {
//隐藏hintTextView
if (mPressedShowTextView != null) {
mPressedShowTextView.setVisibility(View.GONE);
* 根据传入的pos返回tag
* @param tag
private int getPosByTag(String tag) {
if (TextUtils.isEmpty(tag)) {
return -1;
for (int i = 0; i & mSourceDatas.size(); i++) {
if (tag.equals(mSourceDatas.get(i).getTag())) {
return -1;
六 完整代码
思前想后还是放出来吧,三百多行有点长
* 介绍:索引右侧边栏
* 作者:zhangxutong
* CSDN:http://blog.csdn.net/zxt0601
* 时间: 16/09/04.
public class IndexBar extends View {
private static final String TAG = "zxt/IndexBar";
public static String[] INDEX_STRING = {"A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z", "#"};//#在最后面(默认的数据源)
private List&String& mIndexD//索引数据源
private boolean isNeedRealI//是否需要根据实际的数据来生成索引数据源(例如 只有 A B C 三种tag,那么索引栏就 A B C 三项)
private int mWidth, mH//View的宽高
private int mGapH//每个index区域的高度
private Paint mP
private int mPressedB//手指按下时的背景色
//以下边变量是外部set进来的
private TextView mPressedShowTextV//用于特写显示正在被触摸的index值
private List&? extends BaseIndexPinyinBean& mSourceD//Adapter的数据源
private LinearLayoutManager mLayoutM
public IndexBar(Context context) {
this(context, null);
public IndexBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
public IndexBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
int textSize = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics());//默认的TextSize
mPressedBackground = Color.BLACK;//默认按下是纯黑色
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.IndexBar, defStyleAttr, 0);
int n = typedArray.getIndexCount();
for (int i = 0; i & i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.IndexBar_textSize:
textSize = typedArray.getDimensionPixelSize(attr, textSize);
case R.styleable.IndexBar_pressBackground:
mPressedBackground = typedArray.getColor(attr, mPressedBackground);
typedArray.recycle();
if (!isNeedRealIndex) {//不需要真实的索引数据源
mIndexDatas = Arrays.asList(INDEX_STRING);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(textSize);
mPaint.setColor(Color.BLACK);
//设置index触摸监听器
setmOnIndexPressedListener(new onIndexPressedListener() {
public void onIndexPressed(int index, String text) {
if (mPressedShowTextView != null) { //显示hintTexView
mPressedShowTextView.setVisibility(View.VISIBLE);
mPressedShowTextView.setText(text);
if (mLayoutManager != null) {
int position = getPosByTag(text);
if (position != -1) {
mLayoutManager.scrollToPositionWithOffset(position, 0);
public void onMotionEventEnd() {
//隐藏hintTextView
if (mPressedShowTextView != null) {
mPressedShowTextView.setVisibility(View.GONE);
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
protected void onDraw(Canvas canvas) {
int t = getPaddingTop();//top的基准点(支持padding)
Rect indexBounds = new Rect();//存放每个绘制的index的Rect区域
S//每个要绘制的index内容
for (int i = 0; i & mIndexDatas.size(); i++) {
index = mIndexDatas.get(i);
mPaint.getTextBounds(index, 0, index.length(), indexBounds);//测量计算文字所在矩形,可以得到宽高
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();//获得画笔的FontMetrics,用来计算baseLine。因为drawText的y坐标,代表的是绘制的文字的baseLine的位置
int baseline = (int) ((mGapHeight - fontMetrics.bottom - fontMetrics.top) / 2);//计算出在每格index区域,竖直居中的baseLine值
canvas.drawText(index, mWidth / 2 - indexBounds.width() / 2, t + mGapHeight * i + baseline, mPaint);//调用drawText,居中显示绘制index
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setBackgroundColor(mPressedBackground);//手指按下时背景变色
//注意这里没有break,因为down时,也要计算落点 回调监听器
case MotionEvent.ACTION_MOVE:
float y = event.getY();
//通过计算判断落点在哪个区域:
int pressI = (int) ((y - getPaddingTop()) / mGapHeight);
//边界处理(在手指move时,有可能已经移出边界,防止越界)
if (pressI & 0) {
pressI = 0;
} else if (pressI &= mIndexDatas.size()) {
pressI = mIndexDatas.size() - 1;
//回调监听器
if (null != mOnIndexPressedListener) {
mOnIndexPressedListener.onIndexPressed(pressI, mIndexDatas.get(pressI));
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
setBackgroundResource(android.R.color.transparent);//手指抬起时背景恢复透明
//回调监听器
if (null != mOnIndexPressedListener) {
mOnIndexPressedListener.onMotionEventEnd();
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mGapHeight = (mHeight - getPaddingTop() - getPaddingBottom()) / mIndexDatas.size();
* 当前被按下的index的监听器
public interface onIndexPressedListener {
void onIndexPressed(int index, String text);//当某个Index被按下
void onMotionEventEnd();//当触摸事件结束(UP CANCEL)
private onIndexPressedListener mOnIndexPressedL
public onIndexPressedListener getmOnIndexPressedListener() {
return mOnIndexPressedL
public void setmOnIndexPressedListener(onIndexPressedListener mOnIndexPressedListener) {
this.mOnIndexPressedListener = mOnIndexPressedL
* 显示当前被按下的index的TextView
public IndexBar setmPressedShowTextView(TextView mPressedShowTextView) {
this.mPressedShowTextView = mPressedShowTextV
public IndexBar setmLayoutManager(LinearLayoutManager mLayoutManager) {
this.mLayoutManager = mLayoutM
* 一定要在设置数据源{@link #setmSourceDatas(List)}之前调用
* @param needRealIndex
public IndexBar setNeedRealIndex(boolean needRealIndex) {
isNeedRealIndex = needRealI
if (mIndexDatas != null) {
mIndexDatas = new ArrayList&&();
public IndexBar setmSourceDatas(List&? extends BaseIndexPinyinBean& mSourceDatas) {
this.mSourceDatas = mSourceD
initSourceDatas();//对数据源进行初始化
* 初始化原始数据源,并取出索引数据源
private void initSourceDatas() {
int size = mSourceDatas.size();
for (int i = 0; i & i++) {
BaseIndexPinyinBean indexPinyinBean = mSourceDatas.get(i);
StringBuilder pySb = new StringBuilder();
String target = indexPinyinBean.getTarget();//取出需要被拼音化的字段
//遍历target的每个char得到它的全拼音
for (int i1 = 0; i1 & target.length(); i1++) {
//利用TinyPinyin将char转成拼音
//查看源码,方法内 如果char为汉字,则返回大写拼音
//如果c不是汉字,则返回String.valueOf(c)
pySb.append(Pinyin.toPinyin(target.charAt(i1)));
indexPinyinBean.setPyCity(pySb.toString());//设置城市名全拼音
//以下代码设置城市拼音首字母
String tagString = pySb.toString().substring(0, 1);
if (tagString.matches("[A-Z]")) {//如果是A-Z字母开头
indexPinyinBean.setTag(tagString);
if (isNeedRealIndex) {//如果需要真实的索引数据源
if (!mIndexDatas.contains(tagString)) {//则判断是否已经将这个索引添加进去,若没有则添加
mIndexDatas.add(tagString);
} else {//特殊字母这里统一用#处理
indexPinyinBean.setTag("#");
if (isNeedRealIndex) {//如果需要真实的索引数据源
if (!mIndexDatas.contains("#")) {
mIndexDatas.add("#");
sortData();
* 对数据源排序
private void sortData() {
//对右侧栏进行排序 将 # 丢在最后
Collections.sort(mIndexDatas, new Comparator&String&() {
public int compare(String lhs, String rhs) {
if (lhs.equals("#")) {
} else if (rhs.equals("#")) {
return -1;
return lhs.compareTo(rhs);
//对数据源进行排序
Collections.sort(mSourceDatas, new Comparator&BaseIndexPinyinBean&() {
public int compare(BaseIndexPinyinBean lhs, BaseIndexPinyinBean rhs) {
if (lhs.getTag().equals("#")) {
} else if (rhs.getTag().equals("#")) {
return -1;
return lhs.getPyCity().compareTo(rhs.getPyCity());
* 根据传入的pos返回tag
* @param tag
private int getPosByTag(String tag) {
if (TextUtils.isEmpty(tag)) {
return -1;
for (int i = 0; i & mSourceDatas.size(); i++) {
if (tag.equals(mSourceDatas.get(i).getTag())) {
return -1;
不管是自定义ItemDecoration还是实现右侧索引导航栏,其实大量的自定义View知识在里面 ,
so 要想自定义ItemDecoration玩得好,自定义View少不了。
对数据源的排序字段按照拼音排序,我们使用TinyPinyin()帮助我们排序。
它的特性很适合Android平台。
生成的拼音不包含声调,也不处理多音字,默认一个汉字对应一个拼音;
拼音均为大写;
无需初始化,执行效率很高(Pinyin4J的4倍);
很低的内存占用(小于30KB)。
(介绍来源于其项目github)
其实不仅仅是IndexBar以及它和RecyclerView,HintTextView的联动可以封装在一起。
悬停头部ItemDecoration也可以利用 BaseIndexTagBean 类来抽象一下,不与具体的实体类耦合,
private List&CityBean& mD
private List&?extends BaseIndexPinyinBean& mD
本文起笔于9.5晚八点,项目上线打包期间,每逢打包是非多~你们懂得,结果打包期间出现各种问题,各种bug紧急修复通宵到凌晨,9.6日,两点起床,又续写后面三节。本文篇幅也略长,写到后面自己也有点懵逼(也可能是通宵还没醒导致),总耗时6小时+,希望大家看后觉得有用可以给我刷波66666.
八 代码地址
我不想吐槽CSDN了,上传代码真心慢,
稍后补上CSDN地址。
csdn传送门:
github地址:欢迎star
宇宙最大oto公司里最小的Android工程师
https://github.com/mcxtzhang
本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布转载请标明出处: http://www.jianshu.com/p/0d49d9f51d2c本文出自:【张旭童的博客】 (http://www.jianshu.com/users/8e91ff99b072/l...
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金相信有很多朋友...
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金 相信有很多...
解密RecyclerView自定义分割线 RecyclerView的分割线ItemDecoration是可以自定制的,但是很多情况下我们并不懂怎么去定制它,这需要我们去了解其原理,安卓内部是怎样去实现它的,然后才能定制出各种花样各异的不同分割线,那么接下来我们先看看Recy...
我们要实现的效果主要涉及三个部分: 分组 GroupHeader 分割线 SideBar 前两个部分涉及到一个ItemDecoration类,也是我们接下来的重点,该类是RecyclerView的一个抽象静态内部类,主要作用就是给RecyclerView的ItemView绘...
为巩固中国市场,前几天Chanel第二次来到中国举办大秀,在北京发布了高级手工坊系列,虽然老佛爷Karl Lagerfeld没有出现在北京秀场,但依然吸引了超过1000位嘉宾出席活动,秀后Chanel新系列也已正式在中国大陆地区开始出售。此次办秀表明,Chanel十分重视中...
书中一共精选了四十多个故事,这些故事发生的地点很多,在南京、广州、北京、曼谷,人物也很多广。故事的写法很独特,真真假假,有的故事主人公又串到另一个故事里当起了配角,故事中有喜有泪,悲喜交加。就如封面上说的一样,每一分钟,都有人在故事里看到了自己。 1、很多人都喜欢这样...
5月23日,江苏省内唯一一家真正意义的文旅“一站式”专业服务智力集团,在江苏南京成立。全国休闲标准化技术委员会、中国质量万里行促进会、江苏省经信委、江苏省旅游局、江苏省信息中心、江苏省旅游协会、江苏省自驾游协会、江苏省企业发展战略研究会、部分市县(区)旅委(旅游局)及部门等...
本系列文章将分N篇介绍Android中的消息机制。 概述和设计架构 Message和MessageQueue Looper Handler Handler使用实战 Handler引起的内存溢出 ThreadLocal Android的应用程序和Windows应用程序一样,都...
俺们东北馄饨都是煮着吃的,汤里放点虾皮紫菜葱末香菜末,很香……
在上海一次吃馄饨是过油的,增加了一种油香……
昨晚散步走的稍微远了些,附近的农村在种地,儿媳妇在前面刨个坑,老婆婆在后面扔个种子,老公公随后用脚踩踩……我看着有趣就多站了一会儿,那儿媳妇抬头看到我,喊:...

我要回帖

更多关于 微信如何设置分组 的文章

 

随机推荐