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

Posted JasonGaoH

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了仿微信通讯录右侧快速定位字母表控件的实现相关的知识,希望对你有一定的参考价值。

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

先看效果图:


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



以上是关于仿微信通讯录右侧快速定位字母表控件的实现的主要内容,如果未能解决你的问题,请参考以下文章

仿微信通讯录字母排序列表

Android仿微信地图定位和位置选择(下)

分享基于Android系统的XMPP即时通讯技术项目实战(仿微信开发架构,自定义控件)

android 仿微信demo————微信通讯录界面功能实现(移动端,服务端)

android 仿微信demo————微信通讯录界面功能实现(移动端,服务端)

android 仿微信demo————微信顶部操作栏搜索按钮实现(查询通讯录好友功能)