效果图如下:
首先看看布局文件,自定义的控件中包含一个 ListView,用于显示具体的数据内容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginTop="20dp"
android:background="#fffafafa"
android:orientation="vertical" >
<com.common.myletterview.LetterFilterListView
android:id="@+id/letterView"
android:layout_width="wrap_content"
android:layout_height="fill_parent" >
<ListView
android:id="@+id/listView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000"
android:divider="@null"/>
</com.common.myletterview.LetterFilterListView>
</LinearLayout>
加载完 xml 中的 View 后,在自定义控件中添加靠右显示的一列字母控件、点击那列字母后的显示字母控件,这两个子控件均继承自 View,均采用自绘。
右边一列字母显示控件的绘制如下:
/**
* 初始化.
*/
private void init() {
mLetters = new char[] { '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', '#' };
mPaint = new Paint();
mPaint.setColor(Color.parseColor("#949494"));
mPaint.setTypeface(Typeface.DEFAULT_BOLD);
mPaint.setTextSize(22);
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float height = getHeight();
mSingleHeight = height / mLetters.length;
mWidthCenter = getMeasuredWidth() / (float) 2;
for (int i = 0; i < mLetters.length; i++) {
canvas.drawText(String.valueOf(mLetters[i]), mWidthCenter, mSingleHeight + (i * mSingleHeight), mPaint);
}
}
自定义实体实现每项内容与内容首字母的捆绑:
public class Letter implements Comparable<Letter> {
private int id;
private String name;//名字
private String firstLetter;//名字首字母
public Letter() {
super();
}
public Letter(int id, String name, String firstLetter) {
super();
this.id = id;
this.name = name;
this.firstLetter = firstLetter;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFirstLetter() {
return firstLetter;
}
public void setFirstLetter(String firstLetter) {
this.firstLetter = firstLetter;
}
@Override
public int compareTo(Letter another) {
if (this.getFirstLetter().equals("@")
|| another.getFirstLetter().equals("#")) {
return -1;
} else if (this.getFirstLetter().equals("#")
|| another.getFirstLetter().equals("@")) {
return 1;
} else {
return this.getFirstLetter().compareTo(another.getFirstLetter());
}
}
}
适配器实现 SectionIndexer 接口,接口中的两个方法实现是关键,具体如下:
/**
* 根据ListView的当前位置获取分类的首字母的Char ascii值
*/
public int getSectionForPosition(int position) {
return mList.get(position).getFirstLetter().charAt(0);
}
/**
* 根据分类的首字母的Char ascii值获取其第一次出现该首字母的位置
*/
public int getPositionForSection(int section) {
for (int i = 0; i < getCount(); i++) {
String sortStr = mList.get(i).getFirstLetter();
char firstChar = sortStr.toUpperCase().charAt(0);
if (firstChar == section) {
return i;
}
}
return -1;
}
适配器每个 Item 包括字母行跟内容行,如果当前位置等于该分类首字母的Char的位置 ,则显示字母行跟内容行,否则只显示出内容行,具体代码如下:
//根据position获取分类的首字母的Char ascii值
int section = getSectionForPosition(position);
//如果当前位置等于该分类首字母的Char的位置 ,则认为是第一次出现
if(position == getPositionForSection(section)){
viewHolder.tvLetter.setVisibility(View.VISIBLE);
viewHolder.tvLetter.setText(letter.getFirstLetter());
}else{
viewHolder.tvLetter.setVisibility(View.GONE);
}
在点击右边字母列表的时候,根据点击的位置重新计算点击了那个字母,从而作相应的定位,并显示选中的字母:
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
int index = 0;//点击的位置在 mLetters 中的索引
int i = (int) event.getY();
int div = (int) mSingleHeight;
/** 重新计算出索引 */
if (div != 0) {
index = i / div;
}
if (index >= mLetters.length) {
index = mLetters.length - 1;
} else if (index < 0) {
index = 0;
}
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
mLetterSelectedView.setViewY(mSingleHeight + (index * mSingleHeight));//设置选中字母显示的y轴位置
mLetterSelectedView.setSelectedLetter(mLetters[index]);//设置选中的字母
if (mLetterSelectedView.getVisibility() == View.GONE)//若控件为隐藏状态,则显示
mLetterSelectedView.setVisibility(View.VISIBLE);
/** 显示1s后消失*/
mLetterSelectedView.postDelayed(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
mLetterSelectedView.setVisibility(View.GONE);
}
}, 1000);
if (mListView.getAdapter() != null) {
ListAdapter listAdapter = (ListAdapter) mListView.getAdapter();
if (mSectionIndexter == null) {
mSectionIndexter = (SectionIndexer) listAdapter;
}
int position = mSectionIndexter.getPositionForSection(mLetters[index]);
if (position == -1) {//列表中没有首字母为选中字母的的项
return true;
}
mListView.setSelection(position);
}
}
return true;
}
仅仅看此博客可能很难理解具体的实现,大家可以结合完整的代码查看,完整代码:自定义快速查找字母控件
最近在运营一个有关反脆弱成长的个人公众号,欢迎关注