仿微信通讯录右侧快速定位字母表控件
先看效果图:
界面比较单调,凑合看,主要看功能。这种控件在很多应用的通讯录的界面,MIUI里面的通讯录都有这个功能,其实这是一个自定义View,相对来说,这个并不是一个多么复杂的自定义View。
下面介绍一下这种控件的实现方法:
首先,自定义View,一般是对View的增强,因为系统提供的控件不能满足需求,
一般情况下,都是继承View,然后重写里面的onDraw等方法。
来看一下这个控件,A~#这27个符号需要绘制到view上去,这个当然需要在onDraw方法中实现。
另外,这个控件能够响应用户的Touch事件,在用户触碰到相应的字母的时候,左边需要展示相应字母的对话框,
其实这里可以用一个TextView来实现,在Touch事件里面设置TextView的状态是否可见就可以了,
处理需要展示对话框,还需要控制左边ListView的位置,当手指碰到相应的字母时,ListView需要跳到相应的字母位置处。
下面我们来看下这个自定义View的源码:
package com.hgao.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import com.hgao.main.R;
import com.hgao.utils.PixelUtil;
public class MyLetterView extends View {
public static String[] letters = { "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 Paint paint = new Paint();
/**
* 用于标记哪个位置被选中
*/
private int choose = -1;
//该TextView是左边显示的对话框
private TextView mTextDialog;
public void setTextDialog(TextView mTextDialog) {
this.mTextDialog = mTextDialog;
}
private OnTouchingLetterChangedListener listener;
public void setOnTouchingLetterChangedListener(OnTouchingLetterChangedListener listener) {
this.listener = listener;
}
public MyLetterView(Context context) {
super(context);
}
public MyLetterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLetterView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 获取该自定义View的宽度和高度
int width = getWidth();
int height = getHeight();
// 单个字母的高度
int singleHeight = height / letters.length;
for (int i = 0; i < letters.length; i++) {
paint.setColor(getResources().getColor(
R.color.color_bottom_text_normal));
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setAntiAlias(true);
paint.setTextSize(PixelUtil.sp2px(12, getContext()));
// 如果选中的话,改变样式和颜色
if (i == choose) {
paint.setColor(Color.parseColor("#3399ff"));
paint.setFakeBoldText(true);
}
// 首先确定每个字母的横坐标的位置,横坐标:该自定义View的一半 -(减去) 单个字母宽度的一半
float xPos = width / 2 - paint.measureText(letters[i]) / 2;
float yPos = singleHeight * (i + 1);
canvas.drawText(letters[i], xPos, yPos, paint);
// 重置画笔
paint.reset();
}
}
@SuppressWarnings("deprecation")
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int action = event.getAction();
float y = event.getY();
int oldChoose = choose;
// 根据y坐标确定当前哪个字母被选中
int pos = (int) (y / getHeight() * letters.length);
switch (action) {
case MotionEvent.ACTION_UP:
// 当手指抬起时,设置View的背景为白色
setBackgroundDrawable(new ColorDrawable(0x00000000));
// 重置为初始状态
choose = -1;
// 让View重绘
invalidate();
// 将对话框设置为不可见
if (mTextDialog != null) {
mTextDialog.setVisibility(View.INVISIBLE);
}
break;
default:
// 设置右边字母View的背景色
setBackgroundResource(R.drawable.v2_sortlistview_sidebar_background);
if (pos != oldChoose) {
// 如果之前选中的和当前的不一样,需要重绘
if (pos >= 0 && pos < letters.length) {
if(listener != null) {
//当前字母被选中,需要让ListView去更新显示的位置
listener.onTouchingLetterChanged(letters[pos]);
}
//在左边显示选中的字母,该字母放在TextView上,相当于一个dialog
if (mTextDialog != null) {
mTextDialog.setText(letters[pos]); //让对话框显示响应的字母
mTextDialog.setVisibility(View.VISIBLE);
}
choose = pos; //当前位置为选中位置
}
}
break;
}
return true;
}
/**
* 该回调接口用于通知ListView更新状态
*/
public interface OnTouchingLetterChangedListener {
public void onTouchingLetterChanged(String s);
}
}
在onDraw方法中,将A~#这些字母绘制成功,这里面的逻辑相对简单,计算下高度初始化画笔,设置画笔相关的属性和style。
该控件对Touch事件进行响应,显然需要重写Touch事件相关的方法,其实这里重写onTouchEvent事件和dispatchTouchEvent都是可以的。
我们这里重写的是dispatchTouchEvent这个方法,这个涉及到事件的分发机制,有兴趣的同学可以去研究下。
其实View的事件遵循这样的流程:dispatchTouchEvent------>onTouch------>onTouchEvent------->onClick。
在dispatchTouchEvent中重写,返回true,这样不会往下分发,其实重写onTouchEvent是一样的,都是响应用户的Touch事件,
然后让View作出相应的重绘。
/**
* 该回调接口用于通知ListView更新状态
*/
public interface OnTouchingLetterChangedListener {
public void onTouchingLetterChanged(String s);
}
注意到定义了这个接口,这个接口是暴露给ListView,用于ListView能够更新自己显示的位置。
下面我们来看下主界面的代码:
package com.hgao.main;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.TextView;
import com.hgao.utils.CharacterParser;
import com.hgao.utils.PinyinComparator;
import com.hgao.view.MyLetterView;
import com.hgao.view.MyLetterView.OnTouchingLetterChangedListener;
public class MainActivity extends Activity {
private ListView list_friends;
private TextView dialog;
private MyLetterView right_letter;
private List<Friend> friends;
/**
* 根据拼音来排列ListView中的数据
*/
private PinyinComparator pinyinComparator;
private CharacterParser characterParser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initView();
}
private void initData() {
characterParser = CharacterParser.getInstance();
pinyinComparator = new PinyinComparator();
String[] names = getResources().getStringArray(R.array.names); //这里ListView展示的数据不是从网络加载的
friends = new ArrayList<Friend>(); //数据写在string.xml文件中
for (int i = 0; i < names.length; i++) {
Friend f = new Friend();
f.setName(names[i]);
String pinyin = characterParser.getSelling(names[i]);
String sortString = pinyin.substring(0, 1).toUpperCase();
// 正则表达式,判断首字母是否是英文字母
if (sortString.matches("[A-Z]")) {
f.setSortLetters(sortString.toUpperCase());
} else {
f.setSortLetters("#");
}
friends.add(f);
}
Collections.sort(friends, pinyinComparator); //将数据进行,pinyinComparator是一个比较器
}
private void initView() {
list_friends = (ListView) findViewById(R.id.list_friends);
dialog = (TextView) findViewById(R.id.dialog);
right_letter = (MyLetterView) findViewById(R.id.right_letter);
right_letter.setTextDialog(dialog);
final FriendsAdapter adapter = new FriendsAdapter(this,friends);
right_letter
.setOnTouchingLetterChangedListener(new OnTouchingLetterChangedListener() {
@Override
public void onTouchingLetterChanged(String s) {
// 该字母首次出现的位置
int position = adapter.getPositionForSection(s.charAt(0));
if (position != -1) {
list_friends.setSelection(position);
}
}
});
list_friends.setAdapter(adapter);
}
}
主界面的逻辑其实很简单,初始化数据,并初始化相关控件,并为控件设置响应的事件监听,这样,这个控件的实现简单吧。
这里面用到了一些工具类,用于汉字和拼音的转换,后面有源码,还是看源码吧。
接下来看下数据适配器的代码:
package com.hgao.main;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class FriendsAdapter extends BaseAdapter {
List<Friend> friends;
Context context;
public FriendsAdapter(Context context, List<Friend> friends) {
this.friends = friends;
this.context = context;
}
@Override
public int getCount() {
return friends.size();
}
@Override
public Object getItem(int position) {
return friends.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if(convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.item_user_friend, null);
viewHolder = new ViewHolder();
viewHolder.name = (TextView) convertView.findViewById(R.id.tv_friend_name);
viewHolder.alpha = (TextView) convertView.findViewById(R.id.alpha);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
Friend friend = friends.get(position);
viewHolder.name.setText(friend.getName());
// 根据position获取分类的首字母的Char ascii值
int section = getSectionForPosition(position);
// 如果当前位置等于该分类首字母的Char的位置 ,则认为是第一次出现
if (position == getPositionForSection(section)) {
viewHolder.alpha.setVisibility(View.VISIBLE);
viewHolder.alpha.setText(friend.getSortLetters());
} else {
viewHolder.alpha.setVisibility(View.GONE);
}
return convertView;
}
static class ViewHolder {
TextView name;
TextView alpha;;
}
//判断当前位置是否是该位置对应的字母第一次出现
public int getPositionForSection(int section) {
for (int i = 0; i < getCount(); i++) {
String sortStr = friends.get(i).getSortLetters();
char firstChar = sortStr.toUpperCase().charAt(0);
if (section == firstChar) {
return i;
}
}
return -1;
}
/**
* 根据ListView的当前位置获取分类的首字母的Char ascii值
*/
private int getSectionForPosition(int position) {
return friends.get(position).getSortLetters().charAt(0);
}
}
adapter里面的逻辑,注释都写了,也没什么好讲的,理解理解应该就懂了。
最后把这个项目的代码分享下,还是菜鸟,感觉还有点小bug,见笑了!
权当是记录自己的学习笔记,O(∩_∩)O~!
源码地址:QuickSearchView