1.定义个类继承view
主要使用三个方法:
一.onSizeChanged() {计算这个自定义控件的宽和每个字母所占的格子的高}
二.onDraw(){调用canvas.drawText}
三.onTouchEvent(){处理触摸逻辑}
public class QuickIndexBar extends View {
private Paint paint;
private float cellWidth;
private float cellHeight;
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);
init();
}
private void init() {
paint = new Paint();
paint.setColor(Color.GRAY);
//抗锯齿
paint.setAntiAlias(true);
paint.setTextSize(20);
//设置粗体和斜体
paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < Cheeses.LETTERS.length; i++) {
String letter = Cheeses.LETTERS[i];
//定义一个矩形
Rect rect = new Rect();
//把letter装进一个矩形区域内
//这个方法将letter进行测量,将letter的宽高设置到Rect中
//参数2:字母的开始
//参数3:字母的结束
paint.getTextBounds(letter, 0, 1, rect);
int textWidth = rect.width();
int textHeight = rect.height();
//等于一个单位宽度/2+文本占用的宽度/2
float x = cellWidth / 2 - textWidth / 2;
//等于一个单位高度/2+文本占用的高度/2+每个单位高度*i
float y = cellHeight / 2 + textHeight / 2 + i * cellHeight;
//判断绘制的文本是否是当前选择的索引,如果是,则高亮显示
paint.setColor(i == currentIndex ? Color.RED : Color.GRAY);
paint.setTextSize(i==currentIndex?30:20);
canvas.drawText(letter, x, y, paint);
}
//绘制一个文本在View上
// canvas.drawText("#",10f,20f,paint);
// canvas.drawText("#",10f,50f,paint);
// canvas.drawText("#",10f,80f,paint);
// canvas.drawText("#",10f,110f,paint);
// canvas.drawText("#",10f,140f,paint);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
cellWidth = this.getMeasuredWidth();
cellHeight = this.getMeasuredHeight() * 1.0f / Cheeses.LETTERS.length;
}
private int currentIndex = -1;
private int preIndex = -1;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
if (listener != null) {
preIndex = currentIndex;
float y = event.getY();
//计算触摸的点所在的位置
currentIndex = (int) (y / cellHeight);
//识别被触摸到的letter
String letter = Cheeses.LETTERS[currentIndex];
//检查触摸的位置与上一个位置是否一致,如果不一致,则打印
if (preIndex != currentIndex) {
Log.i("test", "letter:" + letter);
listener.onLetterChanged(letter);
}
invalidate();
}
break;
case MotionEvent.ACTION_UP:
if (listener != null) {
listener.onLetterDismiss();
}
currentIndex=-1;
invalidate();
break;
default:
break;
}
return true;
}
private OnLetterChangedListener listener;
public interface OnLetterChangedListener {
void onLetterChanged(String letter);
void onLetterDismiss();
}
public void setOnLetterChangedListener(OnLetterChangedListener listener) {
this.listener = listener;
}
}
2.布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
<com.itheima.quickindexbarhm91.QuickIndexBar
android:id="@+id/qib"
android:layout_width="20dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:background="#770095ff"/>
<TextView
android:id="@+id/tv_show_letter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@drawable/tv_bg"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="20sp"
android:visibility="gone"
/>
</RelativeLayout>
3.activity
public class MainActivity extends AppCompatActivity {
private TextView tv_show_letter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_show_letter = (TextView) findViewById(R.id.tv_show_letter);
QuickIndexBar qib = (QuickIndexBar) findViewById(R.id.qib);
qib.setOnLetterChangedListener(new QuickIndexBar.OnLetterChangedListener() {
@Override
public void onLetterChanged(String letter) {
tv_show_letter.setVisibility(View.VISIBLE);
tv_show_letter.setText(letter);
}
@Override
public void onLetterDismiss() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
tv_show_letter.setVisibility(View.GONE);
}
}, 200);
}
});
List<ContactInfo> contacts=new ArrayList<ContactInfo>();
for (int i = 0; i < Cheeses.NAMES.length; i++) {
contacts.add(new ContactInfo(Cheeses.NAMES[i]));
}
//让ContactInfo具有可排序性,让Collections对ContactInfo集合进行排序
Collections.sort(contacts);
ListView lv= (ListView) findViewById(R.id.lv);
lv.setAdapter(new ContactAdapter(contacts));
}
}
4.要显示的数据对象类
public class ContactInfo implements Comparable<ContactInfo>{
private String firstLetter;
private String name;
public ContactInfo(String name) {
this.firstLetter= PinyinUtil.chineseWordToPinyin(name).substring(0, 1);
this.name = name;
}
public String getFirstLetter() {
return firstLetter;
}
public void setFirstLetter(String firstLetter) {
this.firstLetter = firstLetter;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "ContactInfo{" +
"firstLetter='" + firstLetter + '\'' +
", name='" + name + '\'' +
'}';
}
@Override
public int compareTo(ContactInfo another) {
return this.firstLetter.compareTo(another.firstLetter);
}
}
5.适配器
public class ContactAdapter extends BaseAdapter {
private List<ContactInfo> contacts;
public ContactAdapter(List<ContactInfo> contacts) {
this.contacts = contacts;
}
@Override
public int getCount() {
return contacts == null ? 0 : contacts.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = View.inflate(parent.getContext(), R.layout.lv_contact_list_item, null);
holder = new ViewHolder();
holder.tv_first_letter = (TextView) convertView.findViewById(R.id.tv_first_letter);
holder.tv_name = (TextView) convertView.findViewById(R.id.tv_name);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
ContactInfo info = contacts.get(position);
holder.tv_first_letter.setText(info.getFirstLetter());
holder.tv_name.setText(info.getName());
if (position == 0) {
holder.tv_first_letter.setVisibility(View.VISIBLE);
} else {
//如果当前条目对应的首字母和上一个一致,则让首字母隐藏
ContactInfo lastInfo = contacts.get(position - 1);
if (lastInfo.getFirstLetter().equals(info.getFirstLetter())) {
holder.tv_first_letter.setVisibility(View.GONE);
} else {
holder.tv_first_letter.setVisibility(View.VISIBLE);
}
}
return convertView;
}
private class ViewHolder {
private TextView tv_first_letter;
private TextView tv_name;
}
}
6.由于显示数据时要显示每个名字的字母 所以要将汉子转为拼音 提取出首字母 这里用到了一个拼音jar包 pinyin4j-2.5.0.jar
public class PinyinUtil {
private static HanyuPinyinOutputFormat format;
public static String chineseWordToPinyin(String chineseWord) {
if (format == null) {
format = new HanyuPinyinOutputFormat();
}
//设置去除声调
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
format.setCaseType(HanyuPinyinCaseType.UPPERCASE);
//将汉语字符串转化为字符数据
char[] chars = chineseWord.toCharArray();
StringBuffer sb=new StringBuffer();
for (char c : chars) {
//跨过空格
if(Character.isWhitespace(c)){
continue;
}else{
//拼配汉字的正则表达式
if(Character.toString(c).matches("[\\u4E00-\\u9FA5]")) {
try {
String[] results = PinyinHelper.toHanyuPinyinStringArray(c, format);
// for (String result : results) {
// Log.i("test","result:"+result);
// }
String result = results[0];
sb.append(result);
} catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {
badHanyuPinyinOutputFormatCombination.printStackTrace();
}
}else{
//如果是字母
if(Character.isLetter(c)){
//直接变为字母
sb.append(c);
}else{
//如果是"火星文",以#代替
sb.append("#");
}
}
}
}
return sb.toString();
}
}