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

Posted

tags:

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

参考技术A 先看下效果图

在这里我们先分析下效果,左边是一个按照字母排序的并有子列表,右边是直接一个字母的排列,屏幕中间有一个显示你点击了右边的那个字母的显示。
在这里我们先去处理右边的字母列表。我们先去自定义一个view,把26个字母和“#”放到一个数组里面,再用画笔在ondraw()里面画出来。

当我们在这个字母列表上面滑动的时候,被点击到的字母会变色,这就需要有两个颜色的画笔去分别画不同的字母。
我们可以在onTouchEvent方法里面拿到按下和滑动的事件,在MotionEvent.ACTION_DOWN和MotionEvent.ACTION_MOVE的时候处理字母的变色。我们首先得那个我们点击的是哪个字母,我们可以拿到当前点击位置的y方向的距离去除以每个字母的高度,然后取整就知道那个字母被点击了,这时候调用invalidate方法去重新绘制字母列表,这样就可以实现当那个字母被点击或者是滑动时被覆盖的字母变色的效果。然后在这里我们写一个回调,去让页面中间的那个textview知道如何去显示那个字母被点击了。

我们ManActivity的布局文件是这样的

在ManActivity里面我们给RecyclerView添加数据,先给RecyclerView添加字母列表,在RecyclerView的布局文件在添加一个子RecyclerView。

给子RecyclerView添加数据之后,在会掉里面,当右边的列表那个被惦记了,就将父RecyclerView的那个对应位置的条目滚动到顶部,这样效果就实现了

项目已经放到github上面: https://github.com/chenzhikaizg/LetterListView

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

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

先看效果图:


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



以上是关于仿微信通讯录字母排序列表的主要内容,如果未能解决你的问题,请参考以下文章

小程序超实用组件:仿微信通讯录

微信通讯录分组/好友排序有啥规则?

微信通讯录ABCD是啥意思

C语言制作通讯录怎么将联系人的名字按26个字母的顺序排序

thinkphp如何对查询出来的姓名按字母顺序排序?像手机通讯录那样。我知道用order

苹果6通讯录怎么按字母顺序排列