仿微信通讯录右侧快速定位字母表控件的实现

仿微信通讯录右侧快速定位字母表控件

先看效果图:


界面比较单调,凑合看,主要看功能。这种控件在很多应用的通讯录的界面,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



 

posted @ 2016-08-22 22:42  凌风天涯  阅读(760)  评论(0编辑  收藏  举报