联系人的界面需求是这样的:所有的联系人以首字母分组,右边是字母导航栏,滑动或者单击相应的字母,跳转到对应的分组。
首先实现这个自定义字母导航栏。
public class TextNavigationView extends View{
private int mWidth, mHeight; // 控件宽高
private int position = -1; // 点击位置
private NavigationListener mNavigationListener;
public TextNavigationView(Context context) {
this(context, null);
}
public TextNavigationView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TextNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
// 精确赋值
mWidth = widthSize;
} else {
// 模糊赋值
mWidth = widthSize / 2; // 正常来说,这个地方的值需要我去计算的,mWidth = getPaddingLeft() + 实际宽度 + getPaddingRight()
}
if (heightMode == MeasureSpec.EXACTLY) {
// 精确赋值
mHeight = heightSize;
} else {
// 模糊赋值
mHeight = heightSize / 2; // 正常来说,这个地方的值需要我去计算的,mWidth = getPaddingTop() + 实际高度 + getPaddingBottom()
}
setMeasuredDimension(mWidth, mHeight);
}
private boolean isSlide; // 是否正常滑动
private int yStart; // y开始坐标
private int mTouchSlop; // 最小滑动距离
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
yStart = y;
isSlide = false;
break;
case MotionEvent.ACTION_MOVE:
if (y - yStart > mTouchSlop) {
isSlide = true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
position = y / (mHeight / NameUtil.firstPinyins.size());
invalidate();
break;
case MotionEvent.ACTION_MOVE:
position = y / (mHeight / NameUtil.firstPinyins.size());
invalidate();
break;
case MotionEvent.ACTION_UP:
if (mNavigationListener!=null && position>=0 && position<NameUtil.firstPinyins.size()) {
mNavigationListener.onSelect(NameUtil.firstPinyins.get(position));
}
position = -1; // 回归初始值
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint mPaint = new Paint();
mPaint.setTextSize(40);
for (int i=0; i<NameUtil.firstPinyins.size(); i++) {
if (i == position) {
// 选中
mPaint.setColor(getContext().getResources().getColor(R.color.font_select));
} else {
// 未选中
mPaint.setColor(getContext().getResources().getColor(R.color.font_not_select));
}
canvas.drawText(NameUtil.firstPinyins.get(i), 0, i * (mHeight / NameUtil.firstPinyins.size()), mPaint);
}
}
public void setNavigationListener(NavigationListener mNavigationListener) {
this.mNavigationListener = mNavigationListener;
}
/**
* 导航栏回调监听
*/
public interface NavigationListener {
void onSelect(String text);
}
}
姓名封装:
public class NameInfo {
private String name; // 名字
private String namePinyin; // 名字拼音
private String firstPinyin; // 名字拼音第一个字母
public String getName() {
return name;
}
public String getNamePinyin() {
return namePinyin;
}
public String getFirstPinyin() {
return firstPinyin;
}
public void setName(String name) {
this.name = name;
}
public void setNamePinyin(String namePinyin) {
this.namePinyin = namePinyin;
}
public void setFirstPinyin(String firstPinyin) {
this.firstPinyin = firstPinyin;
}
}
姓名工具类:
public class NameUtil {
// TODO: 2016/8/15 假设数据源
private static String[] stringCitys = new String[]{
"合肥", "张家界", "宿州", "淮北", "阜阳", "蚌埠", "淮南", "滁州",
"马鞍山", "芜湖", "铜陵", "安庆", "安阳", "黄山", "六安", "巢湖",
"池州", "宣城", "亳州", "明光", "天长", "桐城", "宁国",
"徐州", "连云港", "宿迁", "淮安", "盐城", "扬州", "泰州",
"南通", "镇江", "常州", "无锡", "苏州", "江阴", "宜兴",
"邳州", "新沂", "金坛", "溧阳", "常熟", "张家港", "太仓",
"昆山", "吴江", "如皋", "通州", "海门", "启东", "大丰",
"东台", "高邮", "仪征", "江都", "扬中", "句容", "丹阳",
"兴化", "姜堰", "泰兴", "靖江", "福州", "南平", "三明",
"复兴", "高领", "共兴", "柯家寨", "匹克", "匹夫", "旗舰", "启航",
"如阳", "如果", "科比", "韦德", "诺维斯基", "麦迪", "乔丹", "姚明"
};
public static List<String> firstPinyins; // 首字母列表
public static List<NameInfo> nameInfos; // 名字列表
/**
* 汉字转换成拼音
* @param name
* @return
*/
public static String toPinyin(CharSequence name) {
if (name == null)
return null;
StringBuffer sb = new StringBuffer();
for (int i=0; i<name.length(); i++) {
sb.append(Pinyin.toPinyin(name.charAt(i)));
}
return sb.toString();
}
static {
firstPinyins = new ArrayList<>();
nameInfos = new ArrayList<>();
String pinyin;
for (int i=0; i<stringCitys.length; i++) {
pinyin = toPinyin(stringCitys[i]);
NameInfo nameInfo = new NameInfo();
nameInfo.setName(stringCitys[i]);
nameInfo.setNamePinyin(pinyin);
nameInfo.setFirstPinyin(pinyin.charAt(0) + "");
if (!firstPinyins.contains(pinyin.charAt(0) + "")) {
// 去重
firstPinyins.add(pinyin.charAt(0) + "");
}
nameInfos.add(nameInfo);
}
// 排序
Collections.sort(firstPinyins);
Collections.sort(nameInfos, new PinyinComparator());
}
/**
* 拼音排序比较
*/
static class PinyinComparator implements Comparator<NameInfo> {
@Override
public int compare(NameInfo first, NameInfo second) {
return first.getNamePinyin().compareTo(second.getNamePinyin());
}
}
}
XML文件中布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:keng="http://schemas.android.com/apk/res-auto"
style="@style/match_match">
<com.hy.keng.ui.view.TitleBarView
style="@style/title_bar_view"
keng:titleText="@string/contact"
/>
<com.hy.keng.ui.view.TextNavigationView
android:id="@+id/navigation"
android:layout_width="25dp"
android:layout_height="350dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
/>
</RelativeLayout>
在主文件中使用和设置监听:
mTextNavigationView = (TextNavigationView) view.findViewById(R.id.navigation);
mTextNavigationView.setNavigationListener(new TextNavigationView.NavigationListener() {
@Override
public void onSelect(String text) {
showToast(text);
}
});
当滑动到选定字母时,toast显示出来,效果图如下(只显示导航栏,还没有显示联系人内容):
还可优化的问题:
1、现在的布局的宽高是在布局中写死的,后续应该根据字母的多少自动布局;
2、字母居中对齐,而不是居左对齐
参考:
http://blog.csdn.net/tyk0910/article/details/52066891?utm_source=tuicool&utm_medium=referral
http://blog.csdn.net/harvic880925/article/details/38875149
http://blog.csdn.net/harvic880925/article/details/38926877
http://blog.csdn.net/harvic880925/article/details/50423762
http://blog.csdn.net/lmj623565791/article/details/24252901/
http://www.trinea.cn/android/touch-event-delivery-mechanism/