是不是iphone 通讯录 标签能看到朋友把你标签成什么

微信朋友圈分组教程 怎么用标签设置朋友圈分组可见?
时间: 15:58:39来源:作者:holic(0)
  上加的人越来了越多,朋友、同事、家人、甚至陌生人等等,各种都有,有时候有些话并不适合所有人看到,屏蔽某个人的做法又太无情太不好意思了,那朋友圈怎么用不屏蔽朋友的方法而不让有些人看到我的朋友圈呢?小编终于研究出了一种好用并且温柔的方法,让你不屏蔽别人,不拉黑别人,也能给你的朋友圈加一道屏障,下面就来讲讲具体的操作方法。  其实这个运用到了微信的标签功能,首先在微信通讯录中找到标签一项  点进去之后,选择新建标签  这时会要你选择联系人,你要选择你朋友圈内容可以看得那些人,也就是你允许他看你朋友圈的那一部分人  选好之后给这些人加上一个标签,随便你怎么加,朋友、亲人等等,按你自己的意思来,保存就可以了  这样,在你下次发朋友圈的时候,你只要选择某个标签的人可见即可  还是来说一下具体做法吧,如下  当你发朋友圈的时候,下面有选择谁可以看(注意这个功能不支持纯文字)  你点进去之后选择部分可见,这时会出现你编辑好的标签  选择适当的标签,也就是你让他们看你这条朋友圈的那些人的那个标签  这样,你刚刚发的朋友圈谁能看就在你自己的掌控之下了  你发的朋友圈下面这个人人人的小图标就表示你的这条朋友圈是部分人可见的  这种方法真的很实用很好玩啊,对方并不是完全不能看到你的消息,只是,你不想让他知道的东西,他就不会知道,为什么小编想到了一句古诗“犹抱琵琶半遮面”呢。
名称:腾讯微信.2.112最新版大小:28.4M下载:这功能比较适合有强迫症,爱折腾的人哈!!
规范了通讯录标签,以后可以轻松的知道别人是用短号还是亲情网给你打电话。
如果是长号还可以显示归属地。
也许从IOS8(不太清楚)开始自带了号码归属地显示功能,但是却被隐藏起来了。
先看我保存的一个通讯录,电话是隐藏标签,短号和亲情网是IOS自带的自定义标签。
打这个号码的3个电话,效果:
【电话】这个特殊标签在此界面是被隐藏的。
再看来电效果:
【电话】的来电归属地功能已经有了。
再到最近通话里看,是同样的效果。
接下来就是如何实现批量修改电话标签以及新增号码的问题:
由于【电话】是隐藏标签,而本人试了好几款通讯录管理软件,
都没有发现直接修改标签为“电话”的功能,如果有请告知,谢谢。
目前新增号码,我都是在拨号界面输入号码,
再点击+号新增通讯录好友实现的,见下图:
而当前已经存在的号码需要批处理,下面是我的方法
一、导出Excel文件
我是用合合通讯录工具(和扫描全能王同一家公司的)
批量导出通讯录到Excel,然后点击“打开”,
选择QQ,就可以在电脑上接收并编辑Excel了。
二、修改Excel文件
修改Excel第一行Phone 1 Label对应的内容,
如果多个号码,可以继续修改Phone 2 Label等的内容
把相应的内容(即标签名)清空,
这样导入手机就会批量把号码标签变成“电话”。
三、上传到iPhone并导入
(建议用其他方式先备份下通讯录)清空iPhone通讯录,
方法可以参考:http://jingyan.baidu.com/article/eb4ee5e2e7118d5d.html
清空了通讯录后,我个人是把通讯录文件上传到微云,手机微云里下载,
选择打开方式→合合通讯录,然后导入到手机。
看看通讯录里的标签是否都批量变成了“电话”,赶紧试试吧!
阅读(...) 评论()10364人阅读
编程(29)
微信开发(13)
企业号(9)
还没吃饭呢,刚写了下企业号通讯录接口,企业号通讯录具备完全开放的接口,你的应用可以调用这些接口管理部门、成员和标签。
你的应用也可以使用部门、成员、标签发消息,或更改应用的可见范围
在通讯录管理下,有3个接口,部门、成员、标签管理三个接口,我分别写了3个对应类,管理通讯录API地址:
通讯录部门管理类&MGroup:
package jsp.weixin.contacts.
import jsp.weixin.ParamesAPI.util.ParamesAPI;
import jsp.weixin.ParamesAPI.util.WeixinU
* 通讯录部门管理类
* @author Engineer.Jsp
* @date */
public class MGroup {
// 创建部门地址
public static String CREATE_URL = &https://qyapi.weixin.qq.com/cgi-bin/department/create?access_token=ACCESS_TOKEN&;
// 更新部门地址
public static String UPDATE_URL = &https://qyapi.weixin.qq.com/cgi-bin/department/update?access_token=ACCESS_TOKEN&;
// 删除部门地址
public static String DELETE_URL = &https://qyapi.weixin.qq.com/cgi-bin/department/delete?access_token=ACCESS_TOKEN&id=ID&;
// 获取部门列表地址
public static String GETLIST_URL = &https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=ACCESS_TOKEN&;
* 创建部门
* @param name 部门名称。长度限制为1~64个字符
* @param parentid 父亲部门id。根部门id为1
public static String Create(String name , String parentid){
String Postjson = &{\&name\&: %s,\&parentid\&: %s}&;
return String.format(Postjson, name,parentid);
* 更新部门
* @param name 更新的部门名称。长度限制为0~64个字符。修改部门名称时指定该参数
* @param id 部门id
public static String Update(String name , String id){
String Postjson = &{\&id\&: %s,\&name\&: %s}&;
return String.format(Postjson, name,id);
* 删除部门
* @param id 部门id
public static String Delete(String id){
String delete_url = DELETE_URL.replace(&ID&, id);
return delete_
public static void main(String[] args) {
* 创建部门示例
// 调取凭证
String access_token = WeixinUtil.getAccessToken(ParamesAPI.corpId, ParamesAPI.secret).getToken();
// 拼装数据
String PostData = Create(&新建部门&, &2&);
// 提交数据,获取结果
int result = WeixinUtil.PostMessage(access_token, &POST&, CREATE_URL, PostData);
// 打印结果
if(0==result){
System.out.println(&操作成功&);
System.out.println(&操作失败&);
通讯录成员管理类&MPerson:
package jsp.weixin.contacts.
import jsp.weixin.ParamesAPI.util.ParamesAPI;
import jsp.weixin.ParamesAPI.util.WeixinU
* 通讯录成员管理类
* @author Engineer.Jsp
* @date */
public class MPerson {
//创建成员地址
public static String CREATE_URL = &https://qyapi.weixin.qq.com/cgi-bin/user/create?access_token=ACCESS_TOKEN&;
//更新成员地址
public static String UPDATA_URL = &https://qyapi.weixin.qq.com/cgi-bin/user/update?access_token=ACCESS_TOKEN&;
//删滁成员地址
public static String DELETE_URL = &https://qyapi.weixin.qq.com/cgi-bin/user/delete?access_token=ACCESS_TOKEN&userid=ID&;
//获取成员地址
public static String GET_PERSON_URL = &https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&userid=ID&;
//获取部门成员地址
public static String GET_GROUP_URL = &https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=ACCESS_TOKEN&department_id=ID&fetch_child=0&status=0&;
* 创建成员
* @param userid 员工UserID。对应管理端的帐号,企业内必须唯一。长度为1~64个字符
* @param name 成员名称。长度为1~64个字符
* @param department 成员所属部门id列表 格式: &department&: [x, y]
* @param position 职位信息
* @param mobile 手机号码。企业内必须唯一,mobile/weixinid/email三者不能同时为空
* @param gender 性别。gender=0表示男,=1表示女。默认gender=0
* @param tel 办公电话。长度为0~64个字符
* @param email 邮箱。长度为0~64个字符。企业内必须唯一
* @param weixinid 微信号。企业内必须唯一
public static String Create(String
userid,String name ,String position ,String mobile ,String gender,String tel ,String email,String weixinid){
String PostData = &{\&userid\&: %s,\&name\&: %s,\&department\&: [1, 2],\&position\&: %s,\&mobile\&: %s,\&gender\&: %s,\&tel\&: %s,\&email\&: %s,\&weixinid\&: %s}&;
return String.format(PostData, userid,name,position,mobile,gender,tel,email,weixinid);
* 更新成员
* @param userid 员工UserID。对应管理端的帐号,企业内必须唯一。长度为1~64个字符
* @param name 成员名称。长度为1~64个字符
* @param department 成员所属部门id列表 格式: &department&: [x]
* @param position 职位信息
* @param mobile 手机号码。企业内必须唯一,mobile/weixinid/email三者不能同时为空
* @param gender 性别。gender=0表示男,=1表示女。默认gender=0
* @param tel 办公电话。长度为0~64个字符
* @param email 邮箱。长度为0~64个字符。企业内必须唯一
* @param weixinid 微信号。企业内必须唯一
* @param enable 启用/禁用成员。1表示启用成员,0表示禁用成员
public static String Updata(String
userid,String name ,String position ,String mobile ,String gender,String tel ,String email,String weixinid,String enable){
String PostData = &{\&userid\&: %s,\&name\&: %s,\&department\&: [1],\&position\&: %s,\&mobile\&: %s,\&gender\&: %s,\&tel\&: %s,\&email\&: %s,\&weixinid\&: %s,\&enable\&: %s}&;
return String.format(PostData, userid,name,position,mobile,gender,tel,email,weixinid,enable);
* 删除成员
* @param userid 员工UserID。对应管理端的帐号
public static String Delete(String userid){
String delete_url = DELETE_URL.replace(&ID&, userid);
return delete_
* 获取成员
* @param userid 员工UserID。对应管理端的帐号
public static String GPerson(String userid){
String getperson_url = GET_PERSON_URL.replace(&ID&, userid);
return getperson_
* 获取部门成员
* @param department_id 获取的部门id
* @param fetch_child 1/0:是否递归获取子部门下面的成员 (可选)
* @param status 0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 (可选)
public static String GGroup(String department_id){
String getgroup_url = GET_GROUP_URL.replace(&ID&, department_id);
return getgroup_
public static void main(String[] args) {
* 创建成员示例
// 调取凭证
String access_token = WeixinUtil.getAccessToken(ParamesAPI.corpId, ParamesAPI.secret).getToken();
// 拼装数据
String PostData = Create(&员工UserID&, &Engineer-JSP&, &架构师&, &150xxxx8524&, &0&, &0731-80xxx89&, &&, &oYxxxxxxxxxxxxxxx26336o3&);
// 提交数据,获取结果
int result = WeixinUtil.PostMessage(access_token, &POST&, CREATE_URL, PostData);
// 打印结果
if(0==result){
System.out.println(&操作成功&);
System.out.println(&操作失败&);
通讯录标签管理类&MTag:
package jsp.weixin.contacts.
import jsp.weixin.ParamesAPI.util.ParamesAPI;
import jsp.weixin.ParamesAPI.util.WeixinU
* 通讯录标签管理类
* @author Engineer.Jsp
* @date */
public class MTag {
//创建标签地址
public static String CREATE_TAG_URL = &https://qyapi.weixin.qq.com/cgi-bin/tag/create?access_token=ACCESS_TOKEN&;
//更新标签地址
public static String UPDATA_TAG_URL = &https://qyapi.weixin.qq.com/cgi-bin/tag/update?access_token=ACCESS_TOKEN&;
//删除标签地址
public static String DELETE_TAG_URL = &https://qyapi.weixin.qq.com/cgi-bin/tag/delete?access_token=ACCESS_TOKEN&tagid=ID&;
//获取标签成员地址
public static String GET_TAG_PERSON = &https://qyapi.weixin.qq.com/cgi-bin/tag/get?access_token=ACCESS_TOKEN&tagid=ID&;
//增加标签成员地址
public static String ADD_TAG_PERSON = &https://qyapi.weixin.qq.com/cgi-bin/tag/addtagusers?access_token=ACCESS_TOKEN&;
//删除标签成员地址
public static String DELETE_TAG_PERSON = &https://qyapi.weixin.qq.com/cgi-bin/tag/deltagusers?access_token=ACCESS_TOKEN&;
* 创建标签
* @param tagname 标签名称。长度为1~64个字符,标签不可与其他同组的标签重名,也不可与全局标签重名
public static String Create_Tag(String tagname){
String PostData = &{\&tagname\&: %s}&;
return String.format(PostData, tagname);
* 更新标签名字
* @param tagid 标签ID
* @param tagname 标签名称。最长64个字符
public static String Updata_Tag(String tagid , String tagname){
String PostData = &{\&tagid\&: %s,\&tagname\&: %s}&;
return String.format(PostData, tagid,tagname);
* 删除标签
* @param tagid 标签ID
public static String Delete_Tag(String tagid){
String delete_url = DELETE_TAG_URL.replace(&ID&, tagid);
return delete_
* 获取标签成员
* @param tagid 标签ID
public static String Get_Tag_Person(String tagid){
String get_tagperson_url = GET_TAG_PERSON.replace(&ID&, tagid);
return get_tagperson_
* 增加标签成员
* @param tagid 标签ID
* @param userlist 企业员工ID列表 格式:&userlist&:[ &user1&,&user2&]
public static String Add_Tag_Person(String tagid,String userlist){
String PostData = &{\&tagid\&: %s,\&userlist\&:%s}&;
return String.format(PostData, tagid,userlist);
* 删除标签成员
* @param tagid 标签ID
* @param userlist 企业员工ID列表 格式:&userlist&:[ &user1&,&user2&]
public static String Delete_Tag_Person(String tagid,String userlist){
String PostData = &{\&tagid\&: %s,\&userlist\&:%s}&;
return String.format(PostData, tagid,userlist);
public static void main(String[] args) {
* 创建标签示例
// 调取凭证
String access_token = WeixinUtil.getAccessToken(ParamesAPI.corpId, ParamesAPI.secret).getToken();
// 拼装数据
String PostData = Create_Tag(&新建标签&);
// 提交数据,获取结果
int result = WeixinUtil.PostMessage(access_token, &POST&, CREATE_TAG_URL, PostData);
// 打印结果
if(0==result){
System.out.println(&操作成功&);
System.out.println(&操作失败&);
}其中WeixinUtil的getAccessToken()和PostMessage()是通用方法,主要是方便调用,减少代码量,下面看看这2个方法的代码&span style=&font-family: Arial, Helvetica, sans-&&getAccessToken()&/span&
* 获取access_token
* @param CorpID 企业Id
* @param SECRET 管理组的凭证密钥,每个secret代表了对应用、通讯录、接口的不同权限;不同的管理组拥有不同的secret
public static AccessToken getAccessToken(String corpID, String secret) {
AccessToken accessToken =
String requestUrl = access_token_url.replace(&CorpID&, corpID).replace(&SECRET&, secret);
JSONObject jsonObject = HttpRequest(requestUrl, &GET&, null);
// 如果请求成功
if (null != jsonObject) {
accessToken = new AccessToken();
accessToken.setToken(jsonObject.getString(&access_token&));
accessToken.setExpiresIn(jsonObject.getInt(&expires_in&));
System.out.println(&获取token成功:&+jsonObject.getString(&access_token&)+&————&+jsonObject.getInt(&expires_in&));
} catch (Exception e) {
accessToken =
// 获取token失败
String error = String.format(&获取token失败 errcode:{} errmsg:{}&, jsonObject.getInt(&errcode&), jsonObject.getString(&errmsg&));
System.out.println(error);
return accessT
&strong&PostMessage():&/strong&
* 数据提交与请求通用方法
* @param access_token 凭证
* @param RequestMt 请求方式
* @param RequestURL 请求地址
* @param outstr 提交json数据
public static int PostMessage(String access_token ,String RequestMt , String RequestURL , String outstr){
int result = 0;
RequestURL = RequestURL.replace(&ACCESS_TOKEN&, access_token);
JSONObject jsonobject = WeixinUtil.HttpRequest(RequestURL, RequestMt, outstr);
if (null != jsonobject) {
if (0 != jsonobject.getInt(&errcode&)) {
result = jsonobject.getInt(&errcode&);
String error = String.format(&操作失败 errcode:{} errmsg:{}&, jsonobject.getInt(&errcode&), jsonobject.getString(&errmsg&));
System.out.println(error);
HttpRequest():
* 发起https请求并获取结果
* @param requestUrl 请求地址
* @param requestMethod 请求方式(GET、POST)
* @param outputStr 提交的数据
* @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
public static JSONObject HttpRequest(String request , String RequestMethod , String output ){
@SuppressWarnings(&unused&)
JSONObject jsonObject =
StringBuffer buffer = new StringBuffer();
//建立连接
URL url = new URL(request);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setRequestMethod(RequestMethod);
if(output!=null){
OutputStream out = connection.getOutputStream();
out.write(output.getBytes(&UTF-8&));
out.close();
InputStream input = connection.getInputStream();
InputStreamReader inputReader = new InputStreamReader(input,&UTF-8&);
BufferedReader reader = new BufferedReader(inputReader);
while((line=reader.readLine())!=null){
buffer.append(line);
//关闭连接、释放资源
reader.close();
inputReader.close();
input.close();
connection.disconnect();
jsonObject = JSONObject.fromObject(buffer.toString());
} catch (Exception e) {
return jsonO
以上就是管理通讯录接口的主要接口
新建部门:
新建成员:
新建标签:
有疑问请再次留言,看到第一时间回复,谢谢大家观看本博!
文章:12篇
阅读:93754
文章:48篇
阅读:93530
文章:14篇
阅读:42421今天看啥 热点:
类似通讯录分组的Android PinnedSectionListView,且分组标签悬停滑入滑出,android通讯录

《类似通讯录分组的Android PinnedSectionListView,且分组标签悬停滑入滑出》
常用的联系人、通讯录,会按照联系人的姓氏从A,B,C,,,X,Y,Z,这样归类排列下去,方便用户快速查找和定位。PinnedSectionListView是一个第三方的开源框架,在github上的链接地址是:https://github.com/beworker/pinned-section-listview 。Android PinnedSectionListView不仅是一个实现上述功能且有“pinded”的ListView,而且PinnedSectionListView在向上滚动或者向下滚动时候会有一些特殊效果:分组的标签会悬停在ListView的顶部,直到该分组被滑出/滑入整个ListView的可视界面而呈现出弹入弹出效果。
实现上述功能,直接将PinnedSectionListView作为一个普通的ListView即可,但重点是在构造PinnedSectionListView适配器Adapter时,每一个Item需要小心设置View Type。
现给出一个例子加以说明。
测试用的主MainActivity.java
package zhangphil.
import java.util.ArrayL
import com.hb.views.PinnedSectionListV
import com.hb.views.PinnedSectionListView.PinnedSectionListA
import android.app.ListA
import android.content.C
import android.graphics.C
import android.os.B
import android.view.LayoutI
import android.view.V
import android.view.ViewG
import android.widget.ArrayA
import android.widget.ListV
import android.widget.TextV
public class MainActivity extends ListActivity {
//数据集。
private ArrayList&Item& items =
// ListView之Item的View Type数量。
private final int VIEW_TYPE_COUNT = 2;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
items = new ArrayList&Item&();
// 假设我们演示以A,B,C,,,F这样的字符串作为分组的标签。
// 每一组装载5个子数据。
String[] groups = { &A&, &B&, &C&, &D&, &E&, &F& };
for (int i = 0; i & groups. i++) {
String s = groups[i];
Item group = new Item();
group.type = Item.GROUP;
group.text =
items.add(group);
for (int j = 0; j & 5; j++) {
Item child = new Item();
child.type = Item.CHILD;
child.text = s + & 组数据:& +
items.add(child);
PinnedSectionListView listView = (PinnedSectionListView) this
.getListView();
listView.setAdapter(new MyAdapter(this, -1));
// 可选项。非核心部分。
// 为列表增加header和footer, 根据需要加或者不加。
addHeaderAndFooter(listView);
// 用于承载数据块的类。
// 字段分为类型(type)和值(text)。
private class Item {
public static final int GROUP = 0;
public static final int CHILD = 1;
private class MyAdapter extends ArrayAdapter&Item& implements
PinnedSectionListAdapter {
private LayoutInflater inflater =
public MyAdapter(Context context, int resource) {
super(context, resource);
inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
public View getView(int position, View convertView, ViewGroup parent) {
int type = this.getItemViewType(position);
if (type == Item.GROUP) {
convertView = inflater.inflate(
android.R.layout.simple_list_item_1, null);
TextView tv = (TextView) convertView
.findViewById(android.R.id.text1);
tv.setText(getItem(position).text);
tv.setBackgroundColor(Color.RED);
// 将背景设置为半透明,更好的观察效果。
tv.getBackground().setAlpha(128);
if (type == Item.CHILD) {
convertView = inflater.inflate(
android.R.layout.simple_list_item_1, null);
TextView tv = (TextView) convertView
.findViewById(android.R.id.text1);
tv.setText(getItem(position).text);
return convertV
public int getItemViewType(int position) {
return getItem(position).
// 2个type:GROUP或者CHILD。
public int getViewTypeCount() {
return VIEW_TYPE_COUNT;
public Item getItem(int position) {
Item item = items.get(position);
public int getCount() {
return items.size();
// 假设此方法返回皆为false。那么PinnedSectionListView将退化成为一个基础的ListView.
// 只不过退化后的ListView只是一个拥有两个View Type的ListView。
// 从某种角度上讲,此方法对于PinnedSectionListView至关重要,因为返回值true或false,将直接导致PinnedSectionListView是一个PinnedSectionListView,还是一个普通的ListView。
public boolean isItemViewTypePinned(int viewType) {
boolean type =
switch (viewType) {
case Item.GROUP:
case Item.CHILD:
// 次要的代码,非关键部分。
// 此处和一个普通ListView一样,添加ListView的底部和尾部。
private void addHeaderAndFooter(ListView list) {
LayoutInflater inflater = LayoutInflater.from(this);
TextView header1 = (TextView) inflater.inflate(
android.R.layout.simple_list_item_1, list, false);
header1.setText(&列表头-1&);
list.addHeaderView(header1);
TextView header2 = (TextView) inflater.inflate(
android.R.layout.simple_list_item_1, list, false);
header2.setText(&列表头-2&);
list.addHeaderView(header2);
TextView footer = (TextView) inflater.inflate(
android.R.layout.simple_list_item_1, list, false);
footer.setText(&列表底部&);
list.addFooterView(footer);
MainActivity.java需要的布局文件:activity_main.xml文件,把PinnedSectionListView直接作为ListView使用:
&com.hb.views.PinnedSectionListView
xmlns:android=&http://schemas.android.com/apk/res/android&
android:id=&@android:id/list&
android:layout_width=&match_parent&
android:layout_height=&wrap_content&
android:divider=&@null&
android:footerDividersEnabled=&false&
android:headerDividersEnabled=&false& /&
第三方开源的PinnedSectionListView全部源代码:
* Copyright (C) 2013 Sergej Shafarenka, halfbit.de
* Licensed under the Apache License, Version 2.0 (the &License&);
* you may not use this file kt in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an &AS IS& BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
package com.hb.
import zhangphil.listview.BuildC
import android.content.C
import android.database.DataSetO
import android.graphics.C
import android.graphics.C
import android.graphics.PointF;
import android.graphics.R
import android.graphics.drawable.GradientD
import android.graphics.drawable.GradientDrawable.O
import android.os.P
import android.util.AttributeS
import android.view.MotionE
import android.view.SoundEffectC
import android.view.V
import android.view.ViewC
import android.view.accessibility.AccessibilityE
import android.widget.AbsListV
import android.widget.HeaderViewListA
import android.widget.ListA
import android.widget.ListV
import android.widget.SectionI
* ListView, which is capable to pin section views at its top while the rest is still scrolled.
public class PinnedSectionListView extends ListView {
//-- inner classes
/** List adapter to be implemented for being used with PinnedSectionListView adapter. */
public static interface PinnedSectionListAdapter extends ListAdapter {
/** This method shall return 'true' if views of given type has to be pinned. */
boolean isItemViewTypePinned(int viewType);
/** Wrapper class for pinned section view and its position in the list. */
static class PinnedSection {
//-- class fields
// fields used for handling touch events
private final Rect mTouchRect = new Rect();
private final PointF mTouchPoint = new PointF();
private int mTouchS
private View mTouchT
private MotionEvent mDownE
// fields used for drawing shadow under a pinned section
private GradientDrawable mShadowD
private int mSectionsDistanceY;
private int mShadowH
/** Delegating listener, can be null. */
OnScrollListener mDelegateOnScrollL
/** Shadow for being recycled, can be null. */
PinnedSection mRecycleS
/** shadow instance with a pinned view, can be null. */
PinnedSection mPinnedS
/** Pinned view Y-translation. We use it to stick pinned view to the next section. */
int mTranslateY;
/** Scroll listener which does the magic */
private final OnScrollListener mOnScrollListener = new OnScrollListener() {
@Override public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mDelegateOnScrollListener != null) { // delegate
mDelegateOnScrollListener.onScrollStateChanged(view, scrollState);
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mDelegateOnScrollListener != null) { // delegate
mDelegateOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
// get expected adapter or fail fast
ListAdapter adapter = getAdapter();
if (adapter == null || visibleItemCount == 0) // nothing to do
final boolean isFirstVisibleItemSection =
isItemViewTypePinned(adapter, adapter.getItemViewType(firstVisibleItem));
if (isFirstVisibleItemSection) {
View sectionView = getChildAt(0);
if (sectionView.getTop() == getPaddingTop()) { // view sticks to the top, no need for pinned shadow
destroyPinnedShadow();
} else { // section doesn't stick to the top, make sure we have a pinned shadow
ensureShadowForPosition(firstVisibleItem, firstVisibleItem, visibleItemCount);
} else { // section is not at the first visible position
int sectionPosition = findCurrentSectionPosition(firstVisibleItem);
if (sectionPosition & -1) { // we have section position
ensureShadowForPosition(sectionPosition, firstVisibleItem, visibleItemCount);
} else { // there is no section for the first visible item, destroy shadow
destroyPinnedShadow();
/** Default change observer. */
private final DataSetObserver mDataSetObserver = new DataSetObserver() {
@Override public void onChanged() {
recreatePinnedShadow();
@Override public void onInvalidated() {
recreatePinnedShadow();
//-- constructors
public PinnedSectionListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
public PinnedSectionListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
private void initView() {
setOnScrollListener(mOnScrollListener);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
initShadow(true);
//-- public API methods
public void setShadowVisible(boolean visible) {
initShadow(visible);
if (mPinnedSection != null) {
View v = mPinnedSection.
invalidate(v.getLeft(), v.getTop(), v.getRight(), v.getBottom() + mShadowHeight);
//-- pinned section drawing methods
public void initShadow(boolean visible) {
if (visible) {
if (mShadowDrawable == null) {
mShadowDrawable = new GradientDrawable(Orientation.TOP_BOTTOM,
new int[] { Color.parseColor(&#ffa0a0a0&), Color.parseColor(&#50a0a0a0&), Color.parseColor(&#00a0a0a0&)});
mShadowHeight = (int) (8 * getResources().getDisplayMetrics().density);
if (mShadowDrawable != null) {
mShadowDrawable =
mShadowHeight = 0;
/** Create shadow wrapper with a pinned view for a view at given position */
void createPinnedShadow(int position) {
// try to recycle shadow
PinnedSection pinnedShadow = mRecycleS
mRecycleSection =
// create new shadow, if needed
if (pinnedShadow == null) pinnedShadow = new PinnedSection();
// request new view using recycled view, if such
View pinnedView = getAdapter().getView(position, pinnedShadow.view, PinnedSectionListView.this);
// read layout parameters
LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();
if (layoutParams == null) {
layoutParams = (LayoutParams) generateDefaultLayoutParams();
pinnedView.setLayoutParams(layoutParams);
int heightMode = MeasureSpec.getMode(layoutParams.height);
int heightSize = MeasureSpec.getSize(layoutParams.height);
if (heightMode == MeasureSpec.UNSPECIFIED) heightMode = MeasureSpec.EXACTLY;
int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
if (heightSize & maxHeight) heightSize = maxH
// measure & layout
int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
pinnedView.measure(ws, hs);
pinnedView.layout(0, 0, pinnedView.getMeasuredWidth(), pinnedView.getMeasuredHeight());
mTranslateY = 0;
// initialize pinned shadow
pinnedShadow.view = pinnedV
pinnedShadow.position =
pinnedShadow.id = getAdapter().getItemId(position);
// store pinned shadow
mPinnedSection = pinnedS
/** Destroy shadow wrapper for currently pinned view */
void destroyPinnedShadow() {
if (mPinnedSection != null) {
// keep shadow for being recycled later
mRecycleSection = mPinnedS
mPinnedSection =
/** Makes sure we have an actual pinned shadow for given position. */
void ensureShadowForPosition(int sectionPosition, int firstVisibleItem, int visibleItemCount) {
if (visibleItemCount & 2) { // no need for creating shadow at all, we have a single visible item
destroyPinnedShadow();
if (mPinnedSection != null
&& mPinnedSection.position != sectionPosition) { // invalidate shadow, if required
destroyPinnedShadow();
if (mPinnedSection == null) { // create shadow, if empty
createPinnedShadow(sectionPosition);
// align shadow according to next section position, if needed
int nextPosition = sectionPosition + 1;
if (nextPosition & getCount()) {
int nextSectionPosition = findFirstVisibleSectionPosition(nextPosition,
visibleItemCount - (nextPosition - firstVisibleItem));
if (nextSectionPosition & -1) {
View nextSectionView = getChildAt(nextSectionPosition - firstVisibleItem);
final int bottom = mPinnedSection.view.getBottom() + getPaddingTop();
mSectionsDistanceY = nextSectionView.getTop() -
if (mSectionsDistanceY & 0) {
// next section overlaps pinned shadow, move it up
mTranslateY = mSectionsDistanceY;
// next section does not overlap with pinned, stick to top
mTranslateY = 0;
// no other sections are visible, stick to top
mTranslateY = 0;
mSectionsDistanceY = Integer.MAX_VALUE;
int findFirstVisibleSectionPosition(int firstVisibleItem, int visibleItemCount) {
ListAdapter adapter = getAdapter();
int adapterDataCount = adapter.getCount();
if (getLastVisiblePosition() &= adapterDataCount) return -1; // dataset has changed, no candidate
if (firstVisibleItem+visibleItemCount &= adapterDataCount){//added to prevent index Outofbound (in case)
visibleItemCount = adapterDataCount-firstVisibleI
for (int childIndex = 0; childIndex & visibleItemC childIndex++) {
int position = firstVisibleItem + childI
int viewType = adapter.getItemViewType(position);
if (isItemViewTypePinned(adapter, viewType))
return -1;
int findCurrentSectionPosition(int fromPosition) {
ListAdapter adapter = getAdapter();
if (fromPosition &= adapter.getCount()) return -1; // dataset has changed, no candidate
if (adapter instanceof SectionIndexer) {
// try fast way by asking section indexer
SectionIndexer indexer = (SectionIndexer)
int sectionPosition = indexer.getSectionForPosition(fromPosition);
int itemPosition = indexer.getPositionForSection(sectionPosition);
int typeView = adapter.getItemViewType(itemPosition);
if (isItemViewTypePinned(adapter, typeView)) {
return itemP
} // else, no luck
// try slow way by looking through to the next section item above
for (int position=fromP position&=0; position--) {
int viewType = adapter.getItemViewType(position);
if (isItemViewTypePinned(adapter, viewType))
return -1; // no candidate found
void recreatePinnedShadow() {
destroyPinnedShadow();
ListAdapter adapter = getAdapter();
if (adapter != null && adapter.getCount() & 0) {
int firstVisiblePosition = getFirstVisiblePosition();
int sectionPosition = findCurrentSectionPosition(firstVisiblePosition);
if (sectionPosition == -1) // no views to pin, exit
ensureShadowForPosition(sectionPosition,
firstVisiblePosition, getLastVisiblePosition() - firstVisiblePosition);
public void setOnScrollListener(OnScrollListener listener) {
if (listener == mOnScrollListener) {
super.setOnScrollListener(listener);
mDelegateOnScrollListener =
public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
post(new Runnable() {
@Override public void run() { // restore pinned view after configuration change
recreatePinnedShadow();
public void setAdapter(ListAdapter adapter) {
// assert adapter in debug mode
if (BuildConfig.DEBUG && adapter != null) {
if (!(adapter instanceof PinnedSectionListAdapter))
throw new IllegalArgumentException(&Does your adapter implement PinnedSectionListAdapter?&);
if (adapter.getViewTypeCount() & 2)
throw new IllegalArgumentException(&Does your adapter handle at least two types& +
& of views in getViewTypeCount() method: items and sections?&);
// unregister observer at old adapter and register on new one
ListAdapter oldAdapter = getAdapter();
if (oldAdapter != null) oldAdapter.unregisterDataSetObserver(mDataSetObserver);
if (adapter != null) adapter.registerDataSetObserver(mDataSetObserver);
// destroy pinned shadow, if new adapter is not same as old one
if (oldAdapter != adapter) destroyPinnedShadow();
super.setAdapter(adapter);
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (mPinnedSection != null) {
int parentWidth = r - l - getPaddingLeft() - getPaddingRight();
int shadowWidth = mPinnedSection.view.getWidth();
if (parentWidth != shadowWidth) {
recreatePinnedShadow();
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mPinnedSection != null) {
// prepare variables
int pLeft = getListPaddingLeft();
int pTop = getListPaddingTop();
View view = mPinnedSection.
// draw child
canvas.save();
int clipHeight = view.getHeight() +
(mShadowDrawable == null ? 0 : Math.min(mShadowHeight, mSectionsDistanceY));
canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop + clipHeight);
canvas.translate(pLeft, pTop + mTranslateY);
drawChild(canvas, mPinnedSection.view, getDrawingTime());
if (mShadowDrawable != null && mSectionsDistanceY & 0) {
mShadowDrawable.setBounds(mPinnedSection.view.getLeft(),
mPinnedSection.view.getBottom(),
mPinnedSection.view.getRight(),
mPinnedSection.view.getBottom() + mShadowHeight);
mShadowDrawable.draw(canvas);
canvas.restore();
//-- touch handling methods
public boolean dispatchTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
final int action = ev.getAction();
if (action == MotionEvent.ACTION_DOWN
&& mTouchTarget == null
&& mPinnedSection != null
&& isPinnedViewTouched(mPinnedSection.view, x, y)) { // create touch target
// user touched pinned view
mTouchTarget = mPinnedSection.
mTouchPoint.x =
mTouchPoint.y =
// copy down event for eventually be used later
mDownEvent = MotionEvent.obtain(ev);
if (mTouchTarget != null) {
if (isPinnedViewTouched(mTouchTarget, x, y)) { // forward event to pinned view
mTouchTarget.dispatchTouchEvent(ev);
if (action == MotionEvent.ACTION_UP) { // perform onClick on pinned view
super.dispatchTouchEvent(ev);
performPinnedItemClick();
clearTouchTarget();
} else if (action == MotionEvent.ACTION_CANCEL) { // cancel
clearTouchTarget();
} else if (action == MotionEvent.ACTION_MOVE) {
if (Math.abs(y - mTouchPoint.y) & mTouchSlop) {
// cancel sequence on touch target
MotionEvent event = MotionEvent.obtain(ev);
event.setAction(MotionEvent.ACTION_CANCEL);
mTouchTarget.dispatchTouchEvent(event);
event.recycle();
// provide correct sequence to super class for further handling
super.dispatchTouchEvent(mDownEvent);
super.dispatchTouchEvent(ev);
clearTouchTarget();
// call super if this was not our pinned view
return super.dispatchTouchEvent(ev);
private boolean isPinnedViewTouched(View view, float x, float y) {
view.getHitRect(mTouchRect);
// by taping top or bottom padding, the list performs on click on a border item.
// we don't add top padding here to keep behavior consistent.
mTouchRect.top += mTranslateY;
mTouchRect.bottom += mTranslateY + getPaddingTop();
mTouchRect.left += getPaddingLeft();
mTouchRect.right -= getPaddingRight();
return mTouchRect.contains((int)x, (int)y);
private void clearTouchTarget() {
mTouchTarget =
if (mDownEvent != null) {
mDownEvent.recycle();
mDownEvent =
private boolean performPinnedItemClick() {
if (mPinnedSection == null)
OnItemClickListener listener = getOnItemClickListener();
if (listener != null && getAdapter().isEnabled(mPinnedSection.position)) {
View view =
mPinnedSection.
playSoundEffect(SoundEffectConstants.CLICK);
if (view != null) {
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
listener.onItemClick(this, view, mPinnedSection.position, mPinnedSection.id);
public static boolean isItemViewTypePinned(ListAdapter adapter, int viewType) {
if (adapter instanceof HeaderViewListAdapter) {
adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
return ((PinnedSectionListAdapter) adapter).isItemViewTypePinned(viewType);
备注:关于ListView实现多种样式布局的子Item,请参考我的另外一篇文章:
《Android ListView Adapter的getItemViewType和getViewTypeCount多种布局》
链接地址:http://blog.csdn.net/zhangphil/article/details/
版权声明:本文为博主原创文章,未经博主允许不得转载。
相关搜索:
相关阅读:
相关频道:
Android教程最近更新}

我要回帖

更多关于 iphone 通讯录 标签 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信