版权声明:您好,转载请留下原文地址http://blog.csdn.net/JayChan_95318/article/details/58595626,谢谢
Android 快速索引集成
最近把以前做过的快速索引整理一下,方便以后使用,现在分享给大家,初次发表,如果有什么错误,请多多包涵,多多指教!
下面是一个Demo项目的展示效果
其实实现快速索引并不难,现在跟着我一起做一个快速索引的项目吧
项目准备
1.自定义控件(快速索引栏控件) 新建一个包,在这里就命名为view吧,主要用来放置自定义控件的
QuickIndexBar.java的内容如下(看不懂先不理)
package com.jaychan.quickindex.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.jaychan.quickindex.R;
public class QuickIndexBar extends View {
private Paint mPaint;
private static 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 int cellWidth;
private float cellHeight;
//暴露一个字母的监听
public interface OnLetterUpdateListener{
void onLetterUpdate(String letter);
}
private OnLetterUpdateListener listener;
public OnLetterUpdateListener getListener() {
return listener;
}
public void setListener(OnLetterUpdateListener listener) {
this.listener = listener;
}
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(getResources().getColor(R.color.side_bar));
mPaint.setTextSize(25f);
//mPaint.setTypeface(Typeface.DEFAULT_BOLD);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < LETTERS.length; i++) {
String text = LETTERS[i];
// 计算坐标
// x坐标为单元格宽度的一半 减去 文字宽度的一半
int x = (int) (cellWidth / 2.0f - mPaint.measureText(text) / 2.0f);
Rect bounds = new Rect();
mPaint.getTextBounds(text, 0, text.length(), bounds);
// 文本的高度
int textHeight = bounds.height();
// y坐标为单元格高度的一半 + 文字高度的一半 + 上面有多少个单元格的高度(index*cellHeight)
int y = (int) (cellHeight / 2.0f + textHeight / 2.0f + i
* cellHeight);
//mPaint.setColor(touchIndex == i? Color.GRAY : Color.WHITE);//被选择到的字母变成灰色
// 绘制文本A-Z,此处的x,y坐标是字母左下方的坐标
canvas.drawText(text, x, y, mPaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 获取单元格的宽度和高度
cellWidth = getMeasuredWidth();
cellHeight = getMeasuredHeight() * 1.0f / LETTERS.length;
}
int touchIndex = -1;//用于记录当前触摸的索引值
@Override
public boolean onTouchEvent(MotionEvent event) {
int index = -1;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
index = (int) (event.getY() / cellHeight);// y值/每个单元格的高度 = 当前单元格的索引
if(index >= 0 && index < LETTERS.length){
if(index != touchIndex){
if(listener != null){
listener.onLetterUpdate(LETTERS[index]);
touchIndex = index;
}
}
}
setBackgroundColor(getResources().getColor(R.color.side_bar_pressed));
break;
case MotionEvent.ACTION_MOVE:
index = (int) (event.getY() / cellHeight);// y值/每个单元格的高度 = 当前单元格的索引
if(index >= 0 && index < LETTERS.length){
if(index != touchIndex){
if(listener != null){
listener.onLetterUpdate(LETTERS[index]);
touchIndex = index;
}
}
}
setBackgroundColor(getResources().getColor(R.color.side_bar_pressed));
break;
case MotionEvent.ACTION_UP:
touchIndex = -1;
setBackgroundColor(Color.TRANSPARENT);
break;
}
//invalidate();//重新调用onDraw方法实现选中的字母更改颜色
return true;
}
}
里面用到两个颜色,分别是索引栏被触碰到和为触碰时的颜色
所以要在res/values/colors.xml中声明
<color name="side_bar">#919091</color>
<color name="side_bar_pressed">#66BFBFBF</color>
2.下载 pinyin4j-2.5.0.jar 这个jar包主要是用来将文字转换成拼音的
下载链接 http://download.csdn.net/download/seamless_yang/6962659
下载后将jar包导入到项目中
在项目中我用一个工具类PinyinUtils来封装要用到的关于拼音转换的方法,放在utils包下
PinyinUtils.java的内容如下:
package com.jaychan.quickindex.utils;
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;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PinyinUtils {
public static String getPinyin(String str) {
// 设置拼音结果的格式
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
format.setCaseType(HanyuPinyinCaseType.UPPERCASE);// 设置为大写形式
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);// 不用加入声调
StringBuilder sb = new StringBuilder();
char[] charArray = str.toCharArray();
for (int i = 0; i < charArray.length; i++) {
char c = charArray[i];
if (Character.isWhitespace(c)) {// 如果是空格则跳过
continue;
}
if (isHanZi(c)) {// 如果是汉字
String s = "";
try {
// toHanyuPinyinStringArray 返回一个字符串数组是因为该汉字可能是多音字,此处只取第一个结果
s = PinyinHelper.toHanyuPinyinStringArray(c, format)[0];
sb.append(s);
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
sb.append(s);
}
} else {
// 不是汉字
if (i == 0) {
if (isEnglish(c)) {// 第一个属于字母,则返回该字母
return String.valueOf(c).toUpperCase(Locale.ENGLISH);
}
return "#"; // 不是的话返回#号
}
}
}
return sb.toString();
}
public static boolean isHanZi(char c) {
Pattern pattern = Pattern.compile("[\\u4e00-\\u9fa5]+");
Matcher matcher = pattern.matcher(String.valueOf(c));
return matcher.matches();
}
public static boolean isEnglish(char c) {
return String.valueOf(c).matches("^[a-zA-Z]*");
}
}
3.实体类Person.java 要实现Comparable接口 实现该接口是为了方便集合排序的时候按照拼音字母的数序排序,下面贴出Person类的代码
package com.jaychan.quickindex.bean;
import com.jaychan.quickindex.utils.PinyinUtils;
public class Person implements Comparable<Person>{
private String name;
private String pinyin;
public Person(String name) {
this.name = name;
this.pinyin = PinyinUtils.getPinyin(name);
}
public String getPinyin() {
return pinyin;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int compareTo(Person other) {
return this.pinyin.compareTo(other.getPinyin());
}
}
4.准备工作差不多了,开始写MainActivity的布局文件activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FEFEFE">
<ListView
android:id="@+id/lv_persons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@null"/>
<com.jaychan.quickindex.view.QuickIndexBar
android:id="@+id/bar"
android:layout_width="25dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:background="@android:color/transparent"/>
<TextView
android:id="@+id/tv_letter"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
android:background="@drawable/bg_index"
android:gravity="center"
android:text="A"
android:textColor="#fff"
android:textSize="32sp"
android:visibility="gone"/>
</RelativeLayout>
这是个非常简单的布局,一个ListView、一个索引栏、一个TextView,TextView的作用是当索引栏触碰到哪个索引的时候显示在屏幕中间的一个提示,默认情况下是隐藏的,所以visibility设置为gone
该TextView用到bg_index.xml作为背景
其实是一个圆角矩形的定义
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="#66000000"/>
<corners android:radius="10dp"/>
</shape>
5.ListView条目的布局 item_person_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:id="@+id/tv_index"
android:layout_width="match_parent"
android:layout_height="20dp"
android:background="#BFBFBF"
android:gravity="center_vertical"
android:paddingLeft="20dp"
android:text="A"
android:textColor="#989898"
android:textSize="14sp"/>
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:padding="5dp"
android:text="宋江"
android:textSize="18sp"/>
<View
android:id="@+id/line"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginRight="25dp"
android:background="#66BFBFBF"/>
</LinearLayout>
</LinearLayout>
6.创建一个ListView的适配器MyPeronAdapter
MyPeronAdapter.java的代码
package com.jaychan.quickindex.adapter;
import android.content.Context;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.jaychan.quickindex.bean.Person;
import com.jaychan.quickindex.R;
import java.util.ArrayList;
import java.util.List;
public class MyPersonAdapter extends BaseAdapter {
private List<Person> persons = new ArrayList<Person>();
private Context context;
public MyPersonAdapter(List<Person> persons, Context context) {
this.persons = persons;
this.context = context;
}
@Override
public int getCount() {
return persons.size();
}
@Override
public Person getItem(int position) {
return persons.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = View.inflate(context, R.layout.item_person_list, null);
holder.tvName = (TextView) convertView.findViewById(R.id.tv_name);
holder.tvIndex = (TextView) convertView.findViewById(R.id.tv_index);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Person person = getItem(position);
String str = "";
String currentLetter = person.getPinyin().charAt(0) + "";// 当前的首字母
// 如果是第一个条目则显示字母栏
if (position == 0) {
str = currentLetter;
} else {
String preLetter = persons.get(position - 1).getPinyin().charAt(0) + "";// 上一个的首字母
if (!preLetter.equalsIgnoreCase(currentLetter)) {// 如果和上一个的首字母不相同则显示字母栏
str = currentLetter;
}
}
if (TextUtils.isEmpty(str)) {// 根据str是否为空决定字母栏是否显示
holder.tvIndex.setVisibility(View.GONE);
} else {
holder.tvIndex.setVisibility(View.VISIBLE);
holder.tvIndex.setText(currentLetter);
}
holder.tvName.setText(person.getName());
return convertView;
}
static class ViewHolder {
TextView tvName;
TextView tvIndex;
}
}
6.开始着手MainActivity
package com.jaychan.quickindex;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
import com.jaychan.quickindex.adapter.MyPersonAdapter;
import com.jaychan.quickindex.bean.Person;
import com.jaychan.quickindex.view.QuickIndexBar;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private TextView tvLetter;
private QuickIndexBar mQuickIndexBar;
private ListView lvPersons;
public static final String[] NAMES = new String[] { "孙悟空", "贝吉塔", "孙悟饭","孙悟天",
"琪琪", "布玛", "特兰克斯", "克林", "弗利萨", "比克", "丹迪", "布欧", "乌龙",
"天津饭", "饺子", "兰琪", "加林仙人", "撒旦", " 比迪丽", "拉蒂兹", "巴达克", "那巴", "布罗利",
"多多利亚", "基纽", "利库姆", "达普拉", "巴比迪", " 龟仙人", "乐平", "界王", "桃白白", "皮拉夫",
"波波先生", "杰比特", "舞", "小芳", "阎魔王", "牛魔王", "排骨饭", "老界王神", "亚奇洛贝", "沙鲁",
"人造人17号", "人造人18号"};
private List<Person> persons;
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lvPersons = (ListView) findViewById(R.id.lv_persons);
tvLetter = (TextView) findViewById(R.id.tv_letter);
mQuickIndexBar = (QuickIndexBar) findViewById(R.id.bar);
initData();
initListener();
}
private void initListener() {
// 侧边栏设置点击监听
mQuickIndexBar.setListener(new QuickIndexBar.OnLetterUpdateListener() {
@Override
public void onLetterUpdate(String letter) {
showLetter(letter);// 在屏幕中间显示所按下的字母
if (letter.equals("↑")) {
lvPersons.setSelection(0);
return;
}
// 根据首字母定位listView
for (int i = 0; i < persons.size(); i++) {
Person person = persons.get(i);
String l = person.getPinyin().charAt(0) + "";
if (l.equalsIgnoreCase(letter)) {// 匹配成功
lvPersons.setSelection(i);
break;
}
}
}
});
}
private void initData() {
persons = new ArrayList<Person>();
for(int i = 0;i < NAMES.length; i++){
String name = NAMES[i];
persons.add(new Person(name));
}
//对集合进行排序
sortList();
lvPersons.setAdapter(new MyPersonAdapter(persons,this));
}
/**
*显示所触摸到的字母
*
*@param letter
*/
protected void showLetter(String letter) {
tvLetter.setVisibility(View.VISIBLE);// 设置为可见
tvLetter.setText(letter);
mHandler.removeCallbacksAndMessages(null);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
tvLetter.setVisibility(View.GONE);
}
}, 2000);// 两秒后隐藏
}
public void sortList() {
Collections.sort(persons);// 排序后由于#号排在上面,故得把#号的部分集合移到集合的最下面
List<Person> specialList = new ArrayList<Person>();
for (int i = 0; i < persons.size(); i++) {
// 将属于#号的集合分离开来
if (persons.get(i).getPinyin().equals("#")) {
specialList.add(persons.get(i));
}
}
if (specialList.size() != 0) {
persons.removeAll(specialList);// 先移出掉顶部的#号部分
persons.addAll(persons.size(), specialList);// 将#号的集合添加到集合底部
}
}
}
到这里快速索引就完成了,相信按照我的步骤一步一步做的小伙伴,最后应该都能完成。