总结是一种习惯,不能停,一停人就懒了,都快一个月没有写了!该提提神了!
进入正题: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 ,以方便后期的扩展,虽然数据进行多次分组会很麻烦!
3、字符串转换成拼音工具类:
这边需要用到 pinyin4j-2.5.0.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;
}
}
好了,差不多就这样,大家如果在参考我的方法出错了,可以留言一下,方便大家一起进步!
如果你希望一级列表展开且不能关闭,可以加入一下代码:
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 > </span> //group 点击事件进行拦截,不进行任何处理,就不会关闭了
<pre style="font-family: 宋体; font-size: 10.5pt; background-color: rgb(255, 255, 255);"><span > </span> v.setClickable(<span style="color:#000080;"><strong>false</strong></span>);
return true; } });