Android自定义View实现快速索引(如微信好友列表,通讯录)
①最右侧的索引是用自定义View来实现的,通过onDraw方法将其描绘;②用pinyin4j-2.5.0.jar第三方架包取到每个名字的首字母,将汉字转化成拼音再取第一个字符;③ListView的adapte适配器。如下图所示:
1 布局实现
单个记录的实现,代码如下:
item_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#44000000"
android:text="A"
android:textColor="#000000"
android:textSize="25sp" />
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="阿三"
android:textColor="#000000"
android:textSize="25sp" />
</LinearLayout>
主页面布局的实现,代码如下
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.atguigu.quickindex.MainActivity">
<!--左边内容-->
<ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!--中间内容-->
<TextView
android:id="@+id/tv_word"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
android:background="#44000000"
android:gravity="center"
android:text="A"
android:textColor="#000000"
android:textSize="30sp"
android:visibility="gone" />
<!--右边内容-->
<com.wang.quickindex.IndexView
android:id="@+id/iv_words"
android:layout_width="30dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:background="#ff0000" />
</RelativeLayout>
2 代码功能实现
自定义的View来实现快速索引,代码如下:
/**
* 作用:快速索引,绘制26个字母
* 1.把26个字母放入数组
* 2.在onMeasure计算每条的高itemHeight和宽itemWidth,
* 3.在onDraw和wordWidth,wordHeight,wordX,wordY
*
* 手指按下文字变色
* 1.重写onTouchEvent(),返回true,在down/move的过程中计算
* int touchIndex = Y / itemHeight; 强制绘制
*
* 2.在onDraw()方法对于的下标设置画笔变色
*
* 3.在up的时候
* touchIndex = -1; //还原默认
* 强制绘制
*/
public class IndexView extends View {
/**
* 每条的宽和高
*/
private int itemWidth;
private int itemHeight;
private Paint paint; //画笔
private String[] words = {"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"};
/**
* 如果我们在布局文件使用该类,将会用这个构造方法实例该类,如果没有就会崩溃
*
* @param context
* @param attrs
*/
public IndexView(Context context, @Nullable AttributeSet attrs) { //要实现带有两个参数值的构造方法
super(context, attrs);
paint = new Paint();
paint.setTextSize(40); //字母字体大小
paint.setColor(Color.WHITE); //默认设置画笔是白色
paint.setAntiAlias(true); //设置抗锯齿
paint.setTypeface(Typeface.DEFAULT_BOLD);//设置粗体字
}
/**
* 测量方法
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
itemWidth = getMeasuredWidth();
itemHeight = getMeasuredHeight() / words.length; //words.length是26。除以26
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i<words.length;i++){
if (touchIndex == i){
//设置为灰色(按下去的时候)
paint.setColor(Color.GRAY);
}else { //没有-1走下面代码
//设置为白色(松开)
paint.setColor(Color.WHITE);
}
String word = words[i];//若取值是A
Rect rect = new Rect(); //矩形
//画笔
paint.getTextBounds(word, 0, 1, rect); // 0, 1指的是取一个字母
//字母的高和宽
int wordWidth = rect.width();
int wordHeight = rect.height();
//计算每个字母在视图上的坐标位置
float wordX = itemWidth / 2 - wordWidth / 2;
float wordY = itemHeight / 2 + wordHeight / 2 + i * itemHeight; ///i * itemHeight是指往下绘制字母
canvas.drawText(word, wordX, wordY, paint);
}
}
private int touchIndex = -1; //默认字母的下标位置
/**
* 手指按下文字变色
* 1.重写onTouchEvent(),返回true,在down/move的过程中计算
* int touchIndex = Y / itemHeight; 强制绘制
*
* 2.在onDraw()方法对于的下标设置画笔变色
*
* 3.在up的时候
* touchIndex = -1; //还原默认
* 强制绘制
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
float Y = event.getY();
int index = (int) (Y/itemHeight); //字母索引
if(index != touchIndex){
touchIndex = index; //当前的索引位置
invalidate(); //强制绘制会导致OnDraw()方法执行
if (onIndexChangeListener != null && touchIndex < words.length){
onIndexChangeListener.onIndexChange(words[touchIndex]);
}
}
break;
case MotionEvent.ACTION_UP:
touchIndex = -1; //还原
invalidate();
break;
}
return true;
}
/**
* 字母下标索引变化的监听器(做接口)
*/
public interface OnIndexChangeListener{
/**
* 当字母下标位置发生变化的时候回调
* @param word 字母(A~Z)
*/
void onIndexChange(String word); //没有返回值
}
private OnIndexChangeListener onIndexChangeListener;
/** Setter方法
* 设置字母下标索引变化的监听
* @param onIndexChangeListener
*/
public void setOnIndexChangeListener(OnIndexChangeListener onIndexChangeListener) {
this.onIndexChangeListener = onIndexChangeListener;
}
}
实体类的实现,代码如下
/**
* 作用: 姓名 :阿三
* 拼音:ASAN
*/
public class Person {
private String name;
private String pinyin;
public Person(String name) { //生成带name参数的构造方法
this.name = name;
this.pinyin = PinYinUtils.getPinYin(name);
}
//Getter和Setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPinyin() {
return pinyin;
}
public void setPinyin(String pinyin) {
this.pinyin = pinyin;
}
//toString方法 (Alt+Insert)
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", pinyin='" + pinyin + '\'' +
'}';
}
}
汉字转成拼音的实现,代码如下:
/**
* 作用:把汉字转换成拼音
* 阿三
* ASAN
*/
public class PinYinUtils {
/**
* 得到指定汉字的拼音
* 注意:不应该被频繁调用,它消耗一定内存
* @param hanzi
* @return
*/
public static String getPinYin(String hanzi){
String pinyin = "";
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();//控制转换是否大小写,是否带音标
format.setCaseType(HanyuPinyinCaseType.UPPERCASE);//大写是UPPERCASE;小写是LOWERCASE
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
//由于不能直接对多个汉字转换,只能对单个汉字转换
char[] arr = hanzi.toCharArray();
for (int i = 0; i < arr.length; i++) {
if(Character.isWhitespace(arr[i]))continue;//如果是空格,则不处理,进行下次遍历
//汉字是2个字节存储,肯定大于127,所以大于127就可以当为汉字转换
if(arr[i]>127){
try {
//由于多音字的存在,单 dan shan
String[] pinyinArr = PinyinHelper.toHanyuPinyinStringArray(arr[i], format);
if(pinyinArr!=null){
pinyin += pinyinArr[0];
}else {
pinyin += arr[i];
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
//不是正确的汉字
pinyin += arr[i];
}
}else {
//不是汉字,
pinyin += arr[i];
}
}
return pinyin;
}
}
主代码的实现,代码如下:
public class MainActivity extends Activity {
//实例化
private ListView lv_main;
private TextView tv_word;
private IndexView iv_words;
private Handler handler = new Handler(); //用于隐藏切换后的字母,在主线程中运行
/**
* 联系人集合
*/
private ArrayList<Person> persons;
private IndexAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//实例化
lv_main= (ListView)findViewById(R.id.lv_main);
tv_word= (TextView)findViewById(R.id.tv_word);
iv_words= (IndexView) findViewById(R.id.iv_words);
// //方法一:设置监听字母下标索引的变化
// iv_words.setOnIndexChangeListener(new IndexView.OnIndexChangeListener() { //写的内部类(OnIndexChangeListener)
// /**
// *
// * @param word 字母(A~Z)
// */
// @Override
// public void onIndexChange(String word) {
// updateWord(word);
//
// }
// });
//设置监听字母下标索引的变化
iv_words.setOnIndexChangeListener(new MyOnIndexChangeListener());
//准备数据
initData();
//设置适配器
adapter = new IndexAdapter(); //最好适配器new成变量,以后就不用改了
lv_main.setAdapter(adapter);
}
class IndexAdapter extends BaseAdapter{
@Override
public int getCount() {
return persons.size();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null){
convertView = View.inflate(MainActivity.this,R.layout.item_main,null);
viewHolder = new ViewHolder();
viewHolder.tv_word = (TextView)convertView.findViewById(R.id.tv_word);
viewHolder.tv_name = (TextView)convertView.findViewById(R.id.tv_name);
convertView.setTag(viewHolder); //有set就有get
}else {
viewHolder = (ViewHolder) convertView.getTag();
}
//得到姓名和拼音
String name = persons.get(position).getName(); //阿福
String word = persons.get(position).getPinyin().substring(0,1); //AFU变成A(substring(0,1),截取只剩下一个A
viewHolder.tv_word.setText(word);
viewHolder.tv_name.setText(name);
if (position == 0){ //若每种信息字母的第一行显示
viewHolder.tv_word.setVisibility(View.VISIBLE); //显示
}else {
//得到前一个位置对应的字母,如果当前的字母和上一个相同,隐藏TextView;否则就显示
String preWord = persons.get(position - 1).getPinyin().substring(0, 1); //得到上一个字母A~Z
if (word.equals(preWord)){ //若word和preWord相同
viewHolder.tv_word.setVisibility(View.GONE); //隐藏
}else {
viewHolder.tv_word.setVisibility(View.VISIBLE); //显示
}
}
return convertView;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
}
//优化
static class ViewHolder{
TextView tv_word;
TextView tv_name;
}
//方法二:设置监听字母下标索引的变化
class MyOnIndexChangeListener implements IndexView.OnIndexChangeListener {
/**
* @param word 字母(A~Z)
*/
@Override
public void onIndexChange(String word) {
updateWord(word);
updateListView(word);//A~Z字母
}
}
private void updateListView(String word) {
for (int i =0;i<persons.size();i++){
String listWord = persons.get(i).getPinyin().substring(0,1); //YANGGUANGFU-(转成)->Y
if (word.equals(listWord)){
//i是ListView中的位置
lv_main.setSelection(i); //定位到ListVeiw中的某个位置
return;
}
}
}
private void updateWord(String word) {
//显示
tv_word.setVisibility(View.VISIBLE);
tv_word.setText(word);
handler.removeCallbacksAndMessages(null); //先把每次的消息移除
handler.postDelayed(new Runnable() {
@Override
public void run() {
//因为handler在主线程中运行,Runnable方法也是运行在主线程
//(打日志System.out.println是判断Runnable在哪个线程中运行,出现main证明就是在主线程中运行)
System.out.println(Thread.currentThread().getName() +"-------------------");
tv_word.setVisibility(View.GONE); ////3秒后隐藏
}
},3000); //3秒后隐藏
}
/**
* 初始化数据
*/
private void initData() {
persons = new ArrayList<>();
persons.add(new Person("张小光")); //将人名添加到集合中
persons.add(new Person("杨大雷"));
persons.add(new Person("胡继开"));
persons.add(new Person("刘三"));
persons.add(new Person("钟兴"));
persons.add(new Person("尹顺"));
persons.add(new Person("安杰"));
persons.add(new Person("张骞"));
persons.add(new Person("温小松"));
persons.add(new Person("李凤"));
persons.add(new Person("杜甫"));
persons.add(new Person("娄志超"));
persons.add(new Person("张飞"));
persons.add(new Person("王杰"));
persons.add(new Person("李三"));
persons.add(new Person("孙二娘"));
persons.add(new Person("唐小雷"));
persons.add(new Person("牛二"));
persons.add(new Person("姜光刃"));
persons.add(new Person("刘能"));
persons.add(new Person("张四"));
persons.add(new Person("张五"));
persons.add(new Person("侯大帅"));
persons.add(new Person("刘洪"));
persons.add(new Person("乔三"));
persons.add(new Person("徐达健"));
persons.add(new Person("吴洪亮"));
persons.add(new Person("王兆雷"));
persons.add(new Person("阿四"));
persons.add(new Person("李洪磊"));
//排序
Collections.sort(persons, new Comparator<Person>() {
@Override
public int compare(Person lhs, Person rhs) {
return lhs.getPinyin().compareTo(rhs.getPinyin()); //根据拼音排序
}
});
}
}