一、效果圖展示
無(wú)圖不BB仙畦,先上圖
image
二、功能與準(zhǔn)備
2.1 功能
- 按照拼音順序?qū)糜堰M(jìn)行排序音婶,英文數(shù)字符號(hào)歸為#
- 右側(cè)字母導(dǎo)航條慨畸,既可拖動(dòng)也可點(diǎn)擊
- 粘性頭布局
- 搜索(全拼+簡(jiǎn)拼)
2.2 準(zhǔn)備
需要導(dǎo)入文字轉(zhuǎn)拼音的庫(kù)
com.belerweb:pinyin4j:2.5.1'
三、開(kāi)工
3.1 右側(cè)字母的索引
- 字母的繪畫
private static final String[] DEFAULT_INDEX_ITEMS = {"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", "#"};
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String index;
//y的位置是baseline線的位置 加上mTopMargin
//循環(huán)畫上所有的字母
for (int i = 0; i < mIndexItems.size(); i++) {
index = mIndexItems.get(i);
Paint.FontMetrics fm = mPaint.getFontMetrics();
canvas.drawText(index,
(mWidth - mPaint.measureText(index)) / 2,
mItemHeight / 2 + (fm.bottom - fm.top) / 2 - fm.bottom + mItemHeight * i + mTopMargin,
i == mCurrentIndex ? mTouchedPaint : mPaint);
}
}
- 字母列表的觸摸效果
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
float y = event.getY();
//得到字母?jìng)€(gè)數(shù)
int indexSize = mIndexItems.size();
//計(jì)算按壓的位置
int touchIndex = (int) (y / mItemHeight);
//小于0的話那就默認(rèn)第一個(gè)衣式,大于他的個(gè)數(shù)就是最后一個(gè)
if (touchIndex < 0) {
touchIndex = 0;
} else if (touchIndex >= indexSize) {
touchIndex = indexSize - 1;
}
if (mOnIndexChangedListener != null && touchIndex >= 0 && touchIndex < indexSize) {
if (touchIndex != mCurrentIndex) {
mCurrentIndex = touchIndex;
if (mCenterTextView!=null){
mCenterTextView.setText(mIndexItems.get(touchIndex));
mCenterTextView.setVisibility(VISIBLE);
}
mOnIndexChangedListener.onIndexChanged(mIndexItems.get(touchIndex), touchIndex);
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mCenterTextView!=null){
mCenterTextView.setVisibility(VISIBLE);
}
mCurrentIndex=-1;
invalidate();
break;
}
return true;
}
//顯示居中的字母View
public SideIndexBar setCenterTextView(TextView view){
this.mCenterTextView = view;
return this;
}
public SideIndexBar setOnIndexChangedListener(OnIndexTouchedChangedListener listener) {
this.mOnIndexChangedListener = listener;
return this;
}
//改變位置的接口
public interface OnIndexTouchedChangedListener {
void onIndexChanged(String index, int position);
}
3.2寸士、通訊錄分組
- 首先先判斷是否是同組的第一個(gè)
//判斷該是否是同組的第一個(gè)
private boolean isFirst(int position) {
if (mStrings == null) {
return false;
}
if (mStrings.isEmpty()) {
return false;
}
if (position <= 0) {
return true;
} else {
return !mStrings.get(position).getSection().equals(mStrings.get(position - 1).getSection());
}
}
- 再繪制子Item之間的距離
//getItemOffsets 可以實(shí)現(xiàn)類似于padding的效果
//
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
//mSectionHeight是粘性頭部的高度,是同組的第一個(gè)就設(shè)置top
if (isFirst(position)) {
outRect.top = mSectionHeight;
} else {
outRect.top = 0;
}
}
- 繪制每組頭部的背景和文字
//實(shí)現(xiàn)類似繪制背景的效果碴卧,內(nèi)容在上面
//繪制背景和文字
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
int position = params.getViewLayoutPosition();
//是第一個(gè)就繪制背景以及文字
if (isFirst(position)) {
String name = mStrings.get(position).getSection();
c.drawRect(left, child.getTop() - params.topMargin - mSectionHeight, right, child.getTop() - params.topMargin, mBgPaint);
Paint.FontMetrics fm = mTextPaint.getFontMetrics();
c.drawText(name, child.getPaddingLeft(), (child.getTop() - (mSectionHeight / 2 - (fm.descent - fm.ascent) / 2 + fm.descent) - params.topMargin), mTextPaint);
}
}
}
- 繪制粘性頭部弱卡,粘性頭部就是滑動(dòng)范圍還在該組時(shí),在最上方顯示該組頭部住册,其實(shí)就是頭部覆蓋在內(nèi)容上婶博。
//onDrawOver 繪制在內(nèi)容的上面,覆蓋內(nèi)容
//這個(gè)是實(shí)現(xiàn)粘性頭部的關(guān)鍵
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
//拿到屏幕上顯示的第一個(gè)item的位置
int pos = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
if (pos < 0) return;
if (mStrings == null || mStrings.isEmpty()) return;
String section =mStrings.get(pos).getSection();
View child = parent.findViewHolderForLayoutPosition(pos).itemView;
boolean flag = false;
//添加一個(gè)平移替換效果
if ((pos + 1) < mStrings.size()) {
if (null != section && !section.equals(mStrings.get(pos + 1).getSection())) {
//如果子item的高度+加距離頂部的距離 小于 section的高度荧飞,則進(jìn)行平移
if (child.getHeight() + child.getTop() < mSectionHeight) {
c.save();
flag = true;
c.translate(0, child.getHeight() + child.getTop() - mSectionHeight);
}
}
}
c.drawRect(parent.getPaddingLeft(),
parent.getPaddingTop(),
parent.getRight() - parent.getPaddingRight(),
parent.getPaddingTop() + mSectionHeight, mBgPaint);
Paint.FontMetrics fm = mTextPaint.getFontMetrics();
c.drawText(section,
child.getPaddingLeft(),
parent.getPaddingTop() + mSectionHeight -(mSectionHeight / 2 - (fm.descent - fm.ascent) / 2 + fm.descent),
mTextPaint);
if (flag)
c.restore();
}
3.3 數(shù)據(jù)整理排序
- 先看Bean類
public class Star implements Comparable<Star> {
private String name;
private String pinyin; //拼音
private String jianpin;//簡(jiǎn)拼
/***
* 獲取懸浮欄文本凡人,(#、定位垢箕、熱門 需要特殊處理)
* @return
*/
public String getSection() {
String s= pinyin;
if (TextUtils.isEmpty(s)) {
return "#";
} else {
String c = s.substring(0, 1);
Pattern p = Pattern.compile("[a-zA-Z]");
Matcher m = p.matcher(c);
if (m.matches()) {
return c.toUpperCase();
} else {
return "#";
}
}
}
//排序 #都往后放
@Override
public int compareTo(@NonNull Star o) {
if (getSection().equals("#")&&!o.getSection().equals("#")){
return 1;
}else if (!getSection().equals("#")&&o.getSection().equals("#")){
return -1;
}else {
return getSection().compareToIgnoreCase(o.getSection());
}
}
//省略get···set方法
}
- 簡(jiǎn)拼和全拼的獲取時(shí)通過(guò)
//獲取全拼
public String getPinYi(String chines) {
sb.setLength(0);
char[] nameChar = chines.toCharArray();
HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (int i = 0; i < nameChar.length; i++) {
if (nameChar[i] > 128) {
try {
sb.append(PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0]);
} catch (Exception e) {
e.printStackTrace();
}
} else {
sb.append(nameChar[I]);
}
}
return sb.toString();
}
//獲取簡(jiǎn)拼
public String getPinYinHeadChar(String chines) {
sb.setLength(0);
char[] chars = chines.toCharArray();
HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (int i = 0; i < chars.length; i++) {
if (chars[i] > 128) {
try {
sb.append(PinyinHelper.toHanyuPinyinStringArray(chars[i], defaultFormat)[0].charAt(0));
} catch (Exception e) {
e.printStackTrace();
}
} else {
sb.append(chars[I]);
}
}
return sb.toString();
}
3.4 搜索結(jié)果處理
@Override
public void afterTextChanged(Editable s) {
String keyword = s.toString();
if (TextUtils.isEmpty(keyword)) {
mClearAllBtn.setVisibility(View.GONE);
mEmptyView.setVisibility(View.GONE);
mResults = mAllCities;
((SectionDividerDecoration) (mRecyclerView.getItemDecorationAt(0))).setData(mResults);
mAdapter.updateData(mResults);
} else {
mClearAllBtn.setVisibility(View.VISIBLE);
//search是匹配結(jié)果拧粪,下面顯示
mResults = search(keyword, mAllCities);
((SectionDividerDecoration) (mRecyclerView.getItemDecorationAt(0))).setData(mResults);
if (mResults == null || mResults.isEmpty()) {
mEmptyView.setVisibility(View.VISIBLE);
} else {
mEmptyView.setVisibility(View.GONE);
mAdapter.updateData(mResults);
}
}
mRecyclerView.scrollToPosition(0);
}
public List search(String name, List<Star> list) {
List results = new ArrayList();
String patten = Pattern.quote(name);
Pattern pattern = Pattern.compile(patten, Pattern.CASE_INSENSITIVE);
for (int i = 0; i < list.size(); i++) {
//根據(jù)拼音
Matcher matcherPin = pattern.matcher((list.get(i)).getPinyin());
//根據(jù)簡(jiǎn)拼
Matcher jianPin = pattern.matcher((list.get(i)).getJianpin());
//根據(jù)名字
Matcher matcherName = pattern.matcher((list.get(i)).getName());
if (matcherPin.find() || matcherName.find() || jianPin.find()) {
results.add(list.get(i));
}
}
return results;
}
3.5 列表跟隨索引移動(dòng)
在Fragment中實(shí)現(xiàn)索引的接口
在實(shí)現(xiàn)里寫上
/**
* 滾動(dòng)RecyclerView到索引位置
*
* @param index
*/
public void scrollToSection(String index) {
if (mData == null || mData.isEmpty()) return;
if (TextUtils.isEmpty(index)) return;
int size = mData.size();
for (int i = 0; i < size; i++) {
if (TextUtils.equals(index.substring(0, 1), mData.get(i).getSection().substring(0, 1))) {
if (mLayoutManager != null) {
mLayoutManager.scrollToPositionWithOffset(i, 0);
return;
}
}
}
}