总结是一种习惯,不能停,一停人就懒了,都快一个月没有写了!该提提神了!
进入正题:android 仿微信联系人 首字母快速索引,先用下美团的索引效果图:
1、自定义View字母索引栏(右边那一列):
public class QuickIndexBar extends View { private final String[] LETTERS = new 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 Paint mPaint; //单元格高 private float cellHeight; //单元格宽 private float cellWidth; private OnLetterUpdateListener mOnLetterUpdateListener; /** * 设置字母监听 * * @param onLetterUpdateListener */ public void setOnLetterUpdateListener(OnLetterUpdateListener onLetterUpdateListener) { mOnLetterUpdateListener = onLetterUpdateListener; } public QuickIndexBar(Context context) { this(context, null); } public QuickIndexBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public QuickIndexBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //去锯齿 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.DKGRAY); //粗体 mPaint.setTypeface(Typeface.DEFAULT_BOLD); } @Override protected void onDraw(Canvas canvas) { for (int i = 0; i < LETTERS.length; i++) { String text = LETTERS[i]; //计算xy坐标 //获取文本的宽度mPaint.measureText(text) int x = (int) (cellWidth / 2.0f - mPaint.measureText(text) / 2.0f); //获取文本的高度 Rect rect = new Rect();//文本所在的矩形,创建空矩形 mPaint.getTextBounds(text, 0, text.length(), rect);//填充文本 int textHeight = rect.height();//获取文本高度 int y = (int) (cellHeight / 2.0f + textHeight / 2.0f + cellHeight * i); <span style="color:#ff0000;"> //当字母被选中,使用不同颜色画出来,根据需求改变画笔的属性就可以了 if (i == touchIndex){ mPaint.setColor(Color.GREEN); mPaint.setFakeBoldText(true); }</span> canvas.drawText(LETTERS[i], x, y, mPaint); <span style="color:#ff0000;"> mPaint.reset();</span> } } /** * This is called during layout when the size of this view has changed. If * you were just added to the view hierarchy, you're called with the old * values of 0. * * @param w Current width of this view. * @param h Current height of this view. * @param oldw Old width of this view. * @param oldh Old height of this view. */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); //测量出view的宽 cellWidth = getMeasuredWidth(); //测量view高度除以格子总数 cellHeight = getMeasuredHeight() / LETTERS.length; } <span style="color:#ff0000;"> /**</span>
<span style="color:#ff0000;"> * 这段代码实现外部列表滑动时,监听可视首项改变,用来改变画笔颜色进行突出</span>
<span style="color:#ff0000;"> */</span>
这边注释得很详细,需要注意的地方,我都进行标红了!<span style="color:#ff0000;">//被选中项 int touchIndex = -1; public int getTouchIndex() { return touchIndex; } //设置被选中项 public void setTouchIndex(int touchIndex) { this.touchIndex = touchIndex; invalidate(); }</span> @Override public boolean onTouchEvent(MotionEvent event) { int index; switch (MotionEventCompat.getActionMasked(event)) { case MotionEvent.ACTION_DOWN: //所在view中点击的Y坐标(屏幕中的绝对坐标),换算成位置 index = (int) (event.getY() / cellHeight); if (index >= 0 && index < LETTERS.length) { if (index != touchIndex) { if (mOnLetterUpdateListener != null) { mOnLetterUpdateListener.onLetterUpdateListener(LETTERS[index]); touchIndex = index; } } } break; case MotionEvent.ACTION_MOVE: index = (int) (event.getY() / cellHeight); if (index >= 0 && index < LETTERS.length) { <span style="color:#ff0000;">if (index != touchIndex) { if (mOnLetterUpdateListener != null) { mOnLetterUpdateListener.onLetterUpdateListener(LETTERS[index]); touchIndex = index; } }</span> } break; case MotionEvent.ACTION_UP: break; default: break; } //重绘,被选中字母颜色变化 invalidate(); //拦截事件并处理 return true; } <span style="color:#cc0000;">/** * 对外暴露字母监听 */ public interface OnLetterUpdateListener { void onLetterUpdateListener(String letter); } }</span>
2、布局:<RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" > <android.support.v7.widget.RecyclerView android:id="@+id/rv_persons" android:layout_width="match_parent" android:layout_height="match_parent"/> <com.example.views.QuickIndexBar android:id="@+id/qib_bar" android:layout_width="40dp" android:layout_height="match_parent" android:layout_alignParentRight="true" android:background="@color/gray_half" /> </RelativeLayout>
这边本人使用了 RecyclerView 替换 listview不过并不建议这么做,因为在点击索引栏定位时:rv_persons.getLayoutManager().scrollToPosition(i);
recycleView 索引时是从下往上滚,只要符合要求的项为可视项就不在进行滚动,所以当你点击的字母栏字母,满足item 在 目前可视项下方,recycleView 从下往上滚就会出现在当前界面最后一项,也就是底部项!所以还是使用listview,这边最好的选择是使用 ExpandableListView ,以方便后期的扩展,虽然数据进行多次分组会很麻烦!
这边需要用到 pinyin4j-2.5.0.jar:下载地址:jar包下载地址
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
/**
* Created by f on 2015-12-01.
* 根据汉子文本获取拼音首字母
*/
public class PinYinUtil {
/**
* 将汉语拼音转化成字母
*
* @param str 字符串
* @return 字母串
*/
public static String getPinyin(String str) {
//输出格式
HanyuPinyinOutputFormat mFormat = new HanyuPinyinOutputFormat();
//输出为大写字母
mFormat.setCaseType(HanyuPinyinCaseType.UPPERCASE);
//去掉音标
mFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
StringBuilder sb = new StringBuilder();
char[] chars = str.toCharArray();
for (char c : chars) {
//如果是空格跳过
if (Character.isWhitespace(c)) {
continue;
}
//判断是否为汉字
if (Character.toString(c).matches("[\\u4E00-\\u9FA5]+")) {
String s = "";
try {
//通过char得到拼音集合,单->dan或者是shan
s = PinyinHelper.toHanyuPinyinStringArray(c, mFormat)[0];
sb.append(s);
} catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {
badHanyuPinyinOutputFormatCombination.printStackTrace();
sb.append(s);
}
} else if (Character.toString(c).matches("^[a-zA-Z]*")) {//判断是否是英语,是保留
sb.append(c);
} else { //不是汉字不是英语的,保留下来的,使用“#”代替,归为一类
sb.append("#");
}
}
return sb.toString();
}
}
这边根据自己分组需求,进行整改!
4、Java bean类:
import com.example.utils.PinYinUtil;
import java.io.Serializable;
/**
*
* 联系人界面bean,实现Comparable<Persons>接口对每个类的对象进行整体排序
*/
public class Persons <span style="color:#cc0000;">implements Comparable<Persons>{</span>
private String name;
private String pinyin;
private int imageId;
public Persons() {
}
public Persons(int imageId, String name, String status) {
this.imageId = imageId;
this.name = name;
this.status = status;
this.pinyin = PinYinUtil.getPinyin(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getImageId() {
return imageId;
}
public void setImageId(int imageId) {
this.imageId = imageId;
}
public String getPinyin() {
return pinyin;
}
public void setPinyin(String pinyin) {
this.pinyin = pinyin;
}
/**
* 重写父类方法,对比pinyin进行自然排序
*
* @param another Persons
* @return 该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
*/
@Override
public int compareTo(Persons another) {
return this.pinyin.compareTo(another.getPinyin());
}
@Override
public String toString() {
return "Persons{" +
"name='" + name + '\'' +
", pinyin='" + pinyin + '\'' +
", imageId=" + imageId +
'}';
}
}
5、例子名单:
public static final String[] NAMES = new String[]{"宋江", "卢俊义", "吴用",
"公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进", "李应", "朱仝", "鲁智深",
"武松", "董平", "张清", "杨志", "徐宁", "索超", "戴宗", "刘唐", "李逵", "史进", "穆弘",
"雷横", "李俊", "阮小二", "张横", "阮小五", " 张顺", "阮小七", "杨雄", "石秀", "解珍",
" 解宝", "燕青", "朱武", "黄信", "孙立", "宣赞", "郝思文", "韩滔", "彭玘", "单廷珪",
"魏定国", "萧让", "裴宣", "欧鹏", "邓飞", " 燕顺", "杨林", "凌振", "蒋敬", "吕方",
"郭 盛", "安道全", "皇甫端", "王英", "扈三娘", "鲍旭", "樊瑞", "孔明", "孔亮", "项充",
"李衮", "金大坚", "马麟", "童威", "童猛", "孟康", "侯健", "陈达", "杨春", "郑天寿",
"陶宗旺", "宋清", "乐和", "龚旺", "丁得孙", "穆春", "曹正", "宋万", "杜迁", "薛永", "施恩",
"周通", "李忠", "杜兴", "汤隆", "邹渊", "邹润", "朱富", "朱贵", "蔡福", "蔡庆", "李立",
"李云", "焦挺", "石勇", "孙新", "顾大嫂", "张青", "孙二娘", " 王定六", "郁保四", "白胜",
"时迁", "段景柱", "&张三", "11级李四", "12级小明"};
6、适配器:
/**
*
* 联系人列表适配器
*/
public class PersonRVAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Persons> mPersonsList;
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private static final int TYPE_FOOTER = 2;
public PersonRVAdapter(List<Persons> personsList) {
if (personsList == null) {
throw new IllegalArgumentException(
"mRecentList must not be null");
}
mPersonsList = personsList;
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return TYPE_HEADER;
} else if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
} else {
return TYPE_ITEM;
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {//头部项
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.contacts_rv_header, parent, false);
HeaderViewHolder itemViewHolder = new HeaderViewHolder(itemView);
return itemViewHolder;
} else if (viewType == TYPE_ITEM) {//正常项
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.contacts_rv_item, parent, false);
ItemViewHolder itemViewHolder = new ItemViewHolder(itemView);
itemViewHolder.setIsRecyclable(true);
return itemViewHolder;
} else if (viewType == TYPE_FOOTER) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.contacts_rv_footer, parent, false);
FooterViewHolder itemViewHolder = new FooterViewHolder(itemView);
return itemViewHolder;
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder != null) {
if (holder instanceof HeaderViewHolder) {
((HeaderViewHolder) holder).iv_header_image.setBackgroundResource(R.drawable.person);
((HeaderViewHolder) holder).tv_header_what.setText("0.0");
} else if (holder instanceof ItemViewHolder) {
Persons persons = mPersonsList.get(position);
String str = null;
//当前联系人姓名首字母
char c = persons.getPinyin().charAt(0);
String currentLetter = c + "";
if (position == 2) {
str =currentLetter;
} else {
String lastLetter = mPersonsList.get(position - 1).getPinyin().charAt(0) + "";
if (!TextUtils.equals(currentLetter, lastLetter)) {
str = currentLetter;
}
}
//根据str是否为空与是否为字母决定是否显示字母栏
if ((c >= 'A' && str != null && c <= 'Z') || position == 2) {
((ItemViewHolder) holder).tv_letter.setVisibility(View.VISIBLE);
((ItemViewHolder) holder).tv_letter.setText(str);
} else {
((ItemViewHolder) holder).tv_letter.setVisibility(View.GONE);
}
((ItemViewHolder) holder).iv_person_header.setBackgroundResource(R.mipmap.ic_launcher);
((ItemViewHolder) holder).tv_person_name.setText(persons.getName());
} else if (holder instanceof FooterViewHolder) {
}
}
}
@Override
public int getItemCount() {
return mPersonsList.size();
}
/**
* ItemViewHolder
*/
public static final class ItemViewHolder extends RecyclerView.ViewHolder {
//首字母
public TextView tv_letter;
//头像
public ImageView iv_person_header;
//from who
public TextView tv_person_name;
public ItemViewHolder(View itemView) {
super(itemView);
iv_person_header = (ImageView) itemView.findViewById(R.id.iv_person_header);
tv_person_name = (TextView) itemView.findViewById(R.id.tv_person_name);
tv_letter = (TextView) itemView.findViewById(R.id.tv_letter);
}
}
/**
* HeaderViewHolder
*/
public static final class HeaderViewHolder extends RecyclerView.ViewHolder {
//功能logo
public ImageView iv_header_image;
//功能名
public TextView tv_header_what;
public HeaderViewHolder(View itemView) {
super(itemView);
iv_header_image = (ImageView) itemView.findViewById(R.id.iv_header_image);
tv_header_what = (TextView) itemView.findViewById(R.id.tv_header_what);
}
}
/**
* FooterViewHolder
*/
public static final class FooterViewHolder extends RecyclerView.ViewHolder {
//功能logo
public ImageView iv_footer_image;
//功能名
public TextView tv_footer_what;
//字母栏
public TextView tv_letter_footer;
public FooterViewHolder(View itemView) {
super(itemView);
iv_footer_image = (ImageView) itemView.findViewById(R.id.iv_footer_image);
tv_footer_what = (TextView) itemView.findViewById(R.id.tv_footer_what);
tv_letter_footer= (TextView) itemView.findViewById(R.id.tv_letter_footer);
}
}
}
7、联系人界面:
/**
* 联系人界面
*/
public class ContactsFragment extends Fragment {
private OnFragmentInteractionListener mListener;
//索引view
private QuickIndexBar qib_bar;
//联系人列表
private List<Persons> mPersonsList;
//索引弹窗
private PopupWindow mPopupWindow;
//handler 处理者
private Handler mHandler = new Handler();
//弹窗异步关闭
private Thread mThread = new Thread() {
@Override
public void run() {
<span style="color:#ff0000;"> if (mPopupWindow != null)
mPopupWindow.dismiss();
mPopupWindow = null;</span>
}
};
//联系人列表
private RecyclerView rv_persons;
//recycleView布局类型
private LinearLayoutManager mLayoutManager;
//recycleView适配器
private PersonRVAdapter mRVAdapter;
//当前可见首项
private int currentFirstIndex = -1;
//下拉刷新
private SwipeRefreshLayout srl_contacts;
public static ContactsFragment newInstance(String param1, String param2) {
ContactsFragment fragment = new ContactsFragment();
// Bundle args = new Bundle();
// args.putString(ARG_PARAM1, param1);
// args.putString(ARG_PARAM2, param2);
// fragment.setArguments(args);
return fragment;
}
public ContactsFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// findAllPersons();
if (getArguments() != null) {
// mParam1 = getArguments().getString(ARG_PARAM1);
// mParam2 = getArguments().getString(ARG_PARAM2);
}
}
/**
* 真机测试,有联系人的情况下:
* 填充数据
*/
// private void findAllPersons() {
// Cursor cursor = getContext().getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
// mPersonsList = new ArrayList<>();
// if (cursor.moveToFirst()) {
// do {
// //获取联系人name
// String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
// Persons person = new Persons(name, R.mipmap.ic_launcher,"",0);
// mPersonsList.add(person);
// }while (cursor.moveToNext());
// } else {
// getDatas();
// }
// //根据name的首字母排序
// Collections.sort(mPersonsList);
// }
/**
* 非真机测试或者无数据情况下,读取备用数据
*/
// private void getDatas() {
// if (ContactNames.NAMES.length != 0) {
// for (String name : ContactNames.NAMES) {
// Persons person = new Persons(name, R.mipmap.ic_launcher,"","0");
// mPersonsList.add(person);
// }
// }
// }
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.main_fragment_contacts, container, false);
srl_contacts= (SwipeRefreshLayout) view.findViewById(R.id.srl_contacts);
srl_contacts.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
srl_contacts.setRefreshing(false);
}
});
//联系人列表
rv_persons = (RecyclerView) view.findViewById(R.id.rv_persons);
mLayoutManager = new LinearLayoutManager(getContext());
// mLayoutManager.setStackFromEnd(true);
// mLayoutManager.setReverseLayout(true);
rv_persons.setLayoutManager(mLayoutManager);
rv_persons.setItemAnimator(new DefaultItemAnimator());
// rv_persons.setHasFixedSize(false);
mRVAdapter = new PersonRVAdapter(mPersonsList);
qib_bar = (QuickIndexBar) view.findViewById(R.id.qib_bar);
// 字母按键回调
qib_bar.setOnLetterUpdateListener(new QuickIndexBar.OnLetterUpdateListener() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.pop_contacts_layout, null);
TextView tv_content = (TextView) view.findViewById(R.id.tv_content);
@Override
public void onLetterUpdateListener(String letter) {
<span style="color:#ff0000;">if (mPopupWindow != null) {
tv_content.setText(letter);
} else {
mPopupWindow = new PopupWindow(view, 80, 80, false);
mPopupWindow.showAtLocation(getActivity().getWindow().getDecorView(), Gravity.CENTER, 0, 0);
}
tv_content.setText(letter);</span>
mHandler.removeCallbacks(mThread);
mHandler.postDelayed(mThread, 1000);
if (TextUtils.equals(letter, "↑") || TextUtils.equals(letter, "#")) {
rv_persons.getLayoutManager().scrollToPosition(0);
} else if (TextUtils.equals(letter, "☆")) {
rv_persons.getLayoutManager().scrollToPosition(mPersonsList.size()-1);
} else {
//根据字母地位recycleView
for (int i = 0; i < mPersonsList.size(); i++) {
Persons person = mPersonsList.get(i);
String where = person.getPinyin().charAt(0) + "";
if (TextUtils.equals(letter, where)) {
//判断是向上滑动还是向下滑动
int currentLastPosition = mLayoutManager.findLastVisibleItemPosition();
int currentFirstPosition = mLayoutManager.findFirstVisibleItemPosition();
if (i > currentLastPosition) {//向下滑动
int total = currentLastPosition - currentFirstPosition;
rv_persons.getLayoutManager().scrollToPosition(i + total - 1);
} else {//向上滑动
rv_persons.getLayoutManager().scrollToPosition(i);
}
break;
}
}
}
}
});
rv_persons.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
<span style="color:#cc0000;">if (mPersonsList != null) {
char c = mPersonsList.get(mLayoutManager.findFirstVisibleItemPosition()).getPinyin().charAt(0);
currentFirstIndex = switchPinyin(c);
// if (newState == RecyclerView.SCROLL_STATE_IDLE) {
qib_bar.setTouchIndex(currentFirstIndex);
// }</span>
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
rv_persons.setAdapter(mRVAdapter);
return view;
}
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mListener = (OnFragmentInteractionListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/**
* 首字母转化成对应position
*
* @param c 字母
* @return
*/
private int switchPinyin(char c) {
int switchNew = -1;
if (c >= 'A' && c <= 'Z') {
switchNew = 2;
for (char cc = 'A'; cc <= 'Z'; cc++) {
if (c == cc) {
break;
}
switchNew++;
}
} else {
switchNew = 1;
}
return switchNew;
}
}
好了,差不多就这样,大家如果在参考我的方法出错了,可以留言一下,方便大家一起进步!
最后在贴个他人使用expandableListView实现的连接:点击打开链接,
如果你希望一级列表展开且不能关闭,可以加入一下代码:
for (int i = 0; i < mGroupList.size(); i++) {
mExpandableListView.expandGroup(i);
}
mExpandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
<span style="white-space:pre"> </span> //group 点击事件进行拦截,不进行任何处理,就不会关闭了
<pre style="font-family: 宋体; font-size: 10.5pt; background-color: rgb(255, 255, 255);"><span style="white-space:pre"> </span> v.setClickable(<span style="color:#000080;"><strong>false</strong></span>);
return true; } });