今天我们来仿一个IOS联系人列表,首先得支持字母行置顶(有阴影和没阴影置顶)。支持右边字母视图点击和滑动到置顶的字母行。搜索栏支持中英文搜索。有了这个需求,我们现在就得开始动手做。
动手前我们得理清思路:
1、需要重写一个有置顶功能的列表控件;
2、需要写一个右边字母控件竖排视图;
3、支持中英文就得把中文转成拼音,这里我用了google系统用的HanziToPinyin这个类来做。
4、然后我们得有一个全部联系人的集合。一个显示UI联系人的集合和一个每个联系人的首字母第一次出现的Map集合来保存下标;
5、先贴下动态图,马上开始动手做;
首先我们先写右边的字母视图代码如下(由于个人习惯写代码一般喜欢加上注释,这里不加以详细描述):
package com.example.phonedemo;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
public class LetterIndexView extends LinearLayout {
/**
* 上下文环境
*/
private Context context;
/**
* 字母控件
*/
private TextView[] lettersTxt = new TextView[28];
/**
* 触碰字母索引接口
*/
private OnTouchLetterIndex touchLetterIndex;
public LetterIndexView(Context context) {
super(context);
this.context = context;
}
public LetterIndexView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
/**
* 初始化控件.
*/
public void init(OnTouchLetterIndex touchLetterIndex) {
this.touchLetterIndex = touchLetterIndex;
this.setBackgroundColor(getResources().getColor(R.color.transparent));
this.setOrientation(LinearLayout.VERTICAL);
this.setGravity(Gravity.CENTER);
//创建字母控件实例
for (int i = 0; i < 28; i++) {
lettersTxt[i] = new TextView(context);
lettersTxt[i].setGravity(Gravity.CENTER);
char tab = (char) (i + 63);
if (i == 0)
lettersTxt[i].setText("!");
else if (i == 1)
lettersTxt[i].setText("#");
else
lettersTxt[i].setText("" + tab);
lettersTxt[i].setPadding(10, 0,10, 0);
lettersTxt[i].setBackgroundColor(0xFF0000);
lettersTxt[i].setTextSize(12);
lettersTxt[i].setTextColor(Color.BLACK);
LayoutParams letterParam = new LayoutParams(LayoutParams.WRAP_CONTENT, 0);
letterParam.weight = 1;
lettersTxt[i].setLayoutParams(letterParam);
this.addView(lettersTxt[i]);
}
this.setOnTouchListener(new OnTouchListener() {
//移动y轴的距离
private int y;
//控件的高度
private int height;
//按到了哪个字母
private String tab;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下时改变背景和字体颜色
setTextColor(Color.WHITE);
LetterIndexView.this.setBackgroundColor(getResources().getColor(R.color.ff7c7c7c));
case MotionEvent.ACTION_MOVE:
// 获取触发事件点的纵坐标
y = (int) event.getY();
height = LetterIndexView.this.getHeight();
int location = (int) (y / (height / 28) + 0.5f);
if (location == 0) {
tab = "!";
} else if (location == 1) {
tab = "#";
} else if (location > 0 && location <= 27) {
tab = String.valueOf((char) (location + 63));
}
if (LetterIndexView.this.touchLetterIndex!=null) {
//调用接口
LetterIndexView.this.touchLetterIndex.touchLetterWitch(tab);
}
break;
case MotionEvent.ACTION_UP:
LetterIndexView.this.setBackgroundColor(getResources().getColor(R.color.transparent));
if (LetterIndexView.this.touchLetterIndex!=null) {
//调用接口
LetterIndexView.this.touchLetterIndex.touchFinish();
}
setTextColor(Color.BLACK);
break;
}
return true;
}
});
}
/**
* 设置字体颜色
*/
private void setTextColor(int color) {
for (int i = 0; i < 28; i++) {
lettersTxt[i].setTextColor(color);
}
}
/**
* 触碰字母索引接口
*/
public interface OnTouchLetterIndex {
/**
* 触摸字母空间接口.
*/
void touchLetterWitch(String letter);
/**
* 结束查询
*/
void touchFinish();
}
}
由于时间原因自己也没研究置顶功能的listview,于是我拿的网上开源控件这里就不贴出来了。主界面代码如下(由于个人习惯写代码一般喜欢加上注释,这里不加以详细描述):
package com.example.phonedemo;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
public class MainActivity extends Activity {
/**
* 搜索栏
*/
EditText edit_search;
/**
* 列表
*/
PinnedSectionListView listView;
/**
* 右边字母列表
*/
LetterIndexView letterIndexView;
/**
* 中间显示右边按的字母
*/
TextView txt_center;
/**
* 所有名字集合
*/
private ArrayList<PhoneBean> list_all;
/**
* 显示名字集合
*/
private ArrayList<PhoneBean> list_show;
/**
* 列表适配器
*/
private PhoneAdapter adapter;
/**
* 保存名字首字母
*/
public HashMap<String, Integer> map_IsHead;
/**
* item标识为0
*/
public static final int ITEM = 0;
/**
* item标题标识为1
*/
public static final int TITLE = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit_search = (EditText) findViewById(R.id.edit_search);
listView = (PinnedSectionListView) findViewById(R.id.phone_listview);
letterIndexView = (LetterIndexView) findViewById(R.id.phone_LetterIndexView);
txt_center = (TextView) findViewById(R.id.phone_txt_center);
initView();
initData();
}
private void initView() {
// 输入监听
edit_search.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i,
int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1,
int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
list_show.clear();
map_IsHead.clear();
//把输入的字符改成大写
String search = editable.toString().trim().toUpperCase();
if (TextUtils.isEmpty(search)) {
for (int i = 0; i < list_all.size(); i++) {
PhoneBean bean = list_all.get(i);
//中文字符匹配首字母和英文字符匹配首字母
if (!map_IsHead.containsKey(bean.getHeadChar())) {// 如果不包含就添加一个标题
PhoneBean bean1 = new PhoneBean();
// 设置名字
bean1.setName(bean.getName());
// 设置标题type
bean1.setType(MainActivity.TITLE);
list_show.add(bean1);
// map的值为标题的下标
map_IsHead.put(bean1.getHeadChar(),
list_show.size() - 1);
}
// 设置Item type
bean.setType(MainActivity.ITEM);
list_show.add(bean);
}
} else {
for (int i = 0; i < list_all.size(); i++) {
PhoneBean bean = list_all.get(i);
//中文字符匹配首字母和英文字符匹配首字母
if (bean.getName().indexOf(search) != -1|| bean.getName_en().indexOf(search) != -1) {
if (!map_IsHead.containsKey(bean.getHeadChar())) {// 如果不包含就添加一个标题
PhoneBean bean1 = new PhoneBean();
// 设置名字
bean1.setName(bean.getName());
// 设置标题type
bean1.setType(MainActivity.TITLE);
list_show.add(bean1);
// map的值为标题的下标
map_IsHead.put(bean1.getHeadChar(),
list_show.size() - 1);
}
// 设置Item type
bean.setType(MainActivity.ITEM);
list_show.add(bean);
}
}
}
adapter.notifyDataSetChanged();
}
});
// 右边字母竖排的初始化以及监听
letterIndexView.init(new LetterIndexView.OnTouchLetterIndex() {
//实现移动接口
@Override
public void touchLetterWitch(String letter) {
// 中间显示的首字母
txt_center.setVisibility(View.VISIBLE);
txt_center.setText(letter);
// 首字母是否被包含
if (adapter.map_IsHead.containsKey(letter)) {
// 设置首字母的位置
listView.setSelection(adapter.map_IsHead.get(letter));
}
}
//实现抬起接口
@Override
public void touchFinish() {
txt_center.setVisibility(View.GONE);
}
});
/** listview点击事件 */
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view,int i, long l) {
if (list_show.get(i).getType() == MainActivity.ITEM) {// 标题点击不给操作
Toast.makeText(MainActivity.this,list_show.get(i).getName(), Toast.LENGTH_LONG).show();
}
}
});
// 设置标题部分有阴影
// listView.setShadowVisible(true);
}
protected void initData() {
list_all = new ArrayList<PhoneBean>();
list_show = new ArrayList<PhoneBean>();
map_IsHead = new HashMap<String, Integer>();
adapter = new PhoneAdapter(MainActivity.this, list_show, map_IsHead);
listView.setAdapter(adapter);
// 开启异步加载数据
new Thread(runnable).start();
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
adapter.notifyDataSetChanged();
}
};
private Runnable runnable = new Runnable() {
@Override
public void run() {
String[] str = getResources().getStringArray(R.array.phone_all);
for (int i = 0; i < str.length; i++) {
PhoneBean cityBean = new PhoneBean();
cityBean.setName(str[i]);
list_all.add(cityBean);
}
//按拼音排序
MemberSortUtil sortUtil = new MemberSortUtil();
Collections.sort(list_all, sortUtil);
// 初始化数据,顺便放入把标题放入map集合
for (int i = 0; i < list_all.size(); i++) {
PhoneBean cityBean = list_all.get(i);
if (!map_IsHead.containsKey(cityBean.getHeadChar())) {// 如果不包含就添加一个标题
PhoneBean cityBean1 = new PhoneBean();
// 设置名字
cityBean1.setName(cityBean.getName());
// 设置标题type
cityBean1.setType(MainActivity.TITLE);
list_show.add(cityBean1);
// map的值为标题的下标
map_IsHead.put(cityBean1.getHeadChar(),list_show.size() - 1);
}
list_show.add(cityBean);
}
handler.sendMessage(handler.obtainMessage());
}
};
public class MemberSortUtil implements Comparator<PhoneBean> {
/**
* 按拼音排序
*/
@Override
public int compare(PhoneBean lhs, PhoneBean rhs) {
Comparator<Object> cmp = Collator
.getInstance(java.util.Locale.CHINA);
return cmp.compare(lhs.getName_en(), rhs.getName_en());
}
}
}
然而适配器则是用的getViewTypeCount()这种方式,缓存2个不同的view来实现,个人认为是已经不能再优化的方案了。如有更好方案欢迎提出来,我加以改正,代码如下(由于个人习惯写代码一般喜欢加上注释,这里不加以详细描述):
package com.example.phonedemo;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import com.example.phonedemo.PinnedSectionListView.PinnedSectionListAdapter;
public class PhoneAdapter extends BaseAdapter implements PinnedSectionListAdapter {
private LayoutInflater layoutInflater;
/**
* 数据集
*/
private ArrayList<PhoneBean> list;
/**
* 首字母
*/
public HashMap<String, Integer> map_IsHead;
public PhoneAdapter(Context context, ArrayList<PhoneBean> list, HashMap<String, Integer> map_IsHead) {
this.list = list;
this.map_IsHead = map_IsHead;
layoutInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int i) {
return list.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
return list.get(position).getType();
}
//实现自定义listview的接口
@Override
public boolean isItemViewTypePinned(int viewType) {
return viewType == MainActivity.TITLE;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
final ViewHolder viewHolder;
switch (getItemViewType(i)) {
case MainActivity.ITEM:
if (view == null) {
viewHolder = new ViewHolder();
view = layoutInflater.inflate(R.layout.item_phone_item, null);
viewHolder.txt = (TextView) view.findViewById(R.id.item_phone_txt_name);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
//设置名字
viewHolder.txt.setText(list.get(i).getName());
break;
case MainActivity.TITLE:
if (view == null) {
viewHolder = new ViewHolder();
view = layoutInflater.inflate(R.layout.item_phone_title, null);
viewHolder.txt = (TextView) view.findViewById(R.id.item_phone_txt_head);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
//设置标题
viewHolder.txt.setText(list.get(i).getHeadChar());
break;
}
return view;
}
private class ViewHolder {
private TextView txt;
}
}
而中文切成拼音,我则是直接放到bean里面来做,方便点代码如下(由于个人习惯写代码一般喜欢加上注释,这里不加以详细描述):
package com.example.phonedemo;
import java.util.ArrayList;
import android.text.TextUtils;
public class PhoneBean {
/**
* 城市名字首字母
*/
private String headChar;
/**
* 城市id
*/
private String city_id;
/**
* 省id
*/
private String pro_id;
/**
* 城市名字
*/
private String name;
/**
* 城市字母名字
*/
private String name_en;
/**
* 是否是标题
*/
private int type;
public String getHeadChar() {
return headChar;
}
public String getName() {
return name;
}
public String getName_en() {
return name_en;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public void setName(String name) {
this.name = name;
name_en = getPinYin(name);//获取字母名称
name_en = name_en.toUpperCase();//把小写字母换成大写字母
if (!TextUtils.isEmpty(name_en)) {
char head = name_en.charAt(0);
if (head < 'A' || head > 'Z') {
head = '#';
}
headChar = head + "";
}
}
/**
* 汉字转换拼音,字母原样返回,都转换为小写
*/
public String getPinYin(String input) {
ArrayList<HanziToPinyin.Token> tokens = HanziToPinyin.getInstance().get(input);
StringBuilder sb = new StringBuilder();
if (tokens != null && tokens.size() > 0) {
for (HanziToPinyin.Token token : tokens) {
if (token.type == HanziToPinyin.Token.PINYIN) {
sb.append(token.target);
} else {
sb.append(token.source);
}
}
}
return sb.toString().toLowerCase();
}
public String getCity_id() {
return city_id;
}
public void setCity_id(String city_id) {
this.city_id = city_id;
}
public String getPro_id() {
return pro_id;
}
public void setPro_id(String pro_id) {
this.pro_id = pro_id;
}
}
最后贴下xml界面布局如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<EditText
android:id="@+id/edit_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:drawableLeft="@drawable/edit_search"
android:drawablePadding="10dp"
android:hint="请输入名字或者拼音"
android:padding="15dp"
android:singleLine="true"
android:textColor="@android:color/black"
android:textColorHint="#ff333333"
android:textSize="16sp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/edit_search"
android:background="@android:color/white" >
<com.example.phonedemo.PinnedSectionListView
android:id="@+id/phone_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@null" />
<com.example.phonedemo.LetterIndexView
android:id="@+id/phone_LetterIndexView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true" />
</RelativeLayout>
<TextView
android:id="@+id/phone_txt_center"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
android:background="#30000000"
android:gravity="center"
android:textColor="#ff7c7c7c"
android:visibility="gone" />
</RelativeLayout>
好了,只有部分代码没有贴出来,我觉得是无关紧要的,所以就没贴。其他的基本上都贴出来了。我写代码的习惯就是边写代码边注释,所以在博客上就不浪费时间做重复的事情了。望见谅;
希望大家多多关注我的博客,多多支持我。
如有好意见或更好的方式欢迎留言谈论。
尊重原创转载请注明:(http://blog.csdn.net/u013895206) !
下面是地址传送门:
http://download.csdn.net/detail/u013895206/9273575