您的位置:首页 > 移动开发 > Android开发

Android-联系人A~Z列表

2015-12-04 16:55 260 查看

Android-联系人A~Z列表实现旅途

将右侧A~Z显示出来

自定义一个A~Z垂直显示的View(自定义控件命名为:LetterView.java)

/**
* 靠右的字母控件
*/
public class LetterView extends View
{
/**纵向显示的所有字符*/
public static final String letters = "*ABCDEFGHIJKLMNOPQRSTUVWXYZ#";
private int width;//控件宽度
private int height;//控件高度
private int abcHeight;//每个字母的高度
private Paint paint;
public LetterView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init()
{
paint = new Paint();
//抗锯齿
paint.setAntiAlias(true);
//加粗
paint.setFakeBoldText(true);
//字体大小
paint.setTextSize(Float.parseFloat(getResources().getString(R.string.text_size)));
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (width == 0 || height == 0)//判断是否是第一次绘画,给宽高赋值
{
width = getWidth();
height = getHeight();
abcHeight = height / letters.length();
}
for (int i = 0, length = letters.length(); i < length; i++)
{
//计算字母绘制的xy坐标,paint.measureText(letters.charAt(i)+"")  得到字母的宽
float x = (width - paint.measureText(letters.charAt(i) + "")) / 2;
float y = abcHeight * i + abcHeight - paint.measureText(letters.charAt(i) + "")/2;
canvas.drawText(letters.charAt(i) + "", x, y, paint);
}
}
}


在xml布局文件中添加(布局命名为:letters_layout.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<com.view.LetterView
android:layout_width="30dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"/>
</RelativeLayout>


效果图



为A~Z设置点击监听

1、 自定义控件LetterView.java(#开头的注释表示新添加的内容)

/**
* 靠右的字母
*/
public class LetterView extends View {
/**
* #>触碰时候的背景颜色
*/
public static final int COLOR_BG = 0x17000000;
/**
* #>没有触碰时的背景颜色
*/
public static final int COLOR_NO_BG = 0x07000000;
/**
* #>触碰状态下所有字母的颜色
*/
public static final int TEXT_COLOR_NORMAL = 0xff545454;
/**
* #>选中的字母颜色
*/
public static final int TEXT_COLOR_SELECTED = 0xffff5e00;
/**
* 没有触碰状态下的字母颜色
*/
public static final int TEXT_COLOR_UNTOUCH = 0xffa3a3a3;
public static final String letters = "☆ABCDEFGHIJKLMNOPQRSTUVWXYZ#";
private int width;
private int height;
//每个字母的高度
private int abcHeight;
private Paint paint;
private int selectedIndex = 1;//#>被选中字母的下标
private boolean isTouch = false;//#>是否处于触碰的状态
public LetterView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

private void init() {
paint = new Paint();
paint.setAntiAlias(true);
//加粗
paint.setFakeBoldText(true);
paint.setTextSize(Float.parseFloat(getResources().getString(R.string.text_size)));
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//#>根据触碰状态改变控件的背景颜色
if (isTouch) {
setBackgroundColor(COLOR_BG);
} else {
setBackgroundColor(COLOR_NO_BG);
}
if (width == 0 || height == 0) {
width = getWidth();
height = getHeight();
abcHeight = height / letters.length();
}
for (int i = 0, length = letters.length(); i < length; i++) {
if (selectedIndex == i)//#>设置被选中的字母颜色
{
paint.setColor(TEXT_COLOR_SELECTED);
} else {
if (isTouch)//#>设置触碰状态下的所有的字母颜色
{
paint.setColor(TEXT_COLOR_NORMAL);
} else//#>设置非点击状态下的所有的字母颜色
{
paint.setColor(TEXT_COLOR_UNTOUCH);
}
}
//计算字母绘制的xy坐标,paint.measureText(letters.charAt(i)+"")  得到字母的宽
float x = (width - paint.measureText(letters.charAt(i) + "")) / 2;
float y = abcHeight * i + abcHeight - paint.measureText(letters.charAt(i) + "") / 2;
canvas.drawText(letters.charAt(i) + "", x, y, paint);
}
}

private float y;//#>点击的y坐标
private int lastSelectedIndex = -1;//#>记录上一次的位置
@Override
public boolean onTouchEvent(MotionEvent event) {
y = event.getY();//#>获取当前触摸时的y坐标
selectedIndex = (int) (y / abcHeight);//#>计算出当前触碰到的字母下标
if (selectedIndex <= 0) selectedIndex = 1;//#>如果下标处于0将,下标改为1,不让下标为0的☆产生监听
if (selectedIndex >= letters.length() - 1) selectedIndex = letters.length() - 2;//#>如果下标处于最后将,下标改为letters.length() - 2,不让最下面的#产生监听
if (selectedIndex != lastSelectedIndex) {//#>如果触摸的地方不是上一次的y轴位置,重绘,调用回调中间显示字母
invalidate();
if (letterChangeListener != null)
{
letterChangeListener.onLetterChange(selectedIndex);
}
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isTouch = true;
break;
case MotionEvent.ACTION_UP:
letterChangeListener.onClickUp();//
isTouch = false;
break;
}
invalidate();
return true;
}

/**
* #>回调接口,处理字母的点击事件
*/
//#>回调接口(当前是哪一栏,则字母就显示哪一个)
public interface OnLetterChangeListener {
void onLetterChange(int selectedIndex);//#>当位置发生改变时调用
void onClickUp();//#>当触摸后,放开时调用
}

private OnLetterChangeListener letterChangeListener;

public void setOnLetterChangeListener(OnLetterChangeListener letterChangeListener) {
this.letterChangeListener = letterChangeListener;
}

//设置当前那个字母被选中
public void setSelected(int section) {

this.selectedIndex = section;
invalidate();
}
}


2、 在xml布局文件letters_layout.xml中

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<com.view.LetterView
android:id="@+id/letterView"
android:layout_width="30dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"/>
<!--中间显示被点到的字母-->
<TextView
android:id="@+id/show_now_abc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A"
android:layout_centerInParent="true"
android:visibility="gone"
android:textSize="60sp"/>
</RelativeLayout>


3、在Activity中(MainActivity.java)

public class MainActivityextends Activity{
private LetterView letterView;
private TextView tvToast;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.letters_layout);
letterView = (LetterView) findViewById(R.id.letterView);
tvToast = (TextView) findViewById(R.id.show_now_abc);
letterView.setOnLetterChangeListener(new LetterView.OnLetterChangeListener() {
@Override
public void onLetterChange(int selectedIndex) {
tvToast.setText(LetterView.letters.charAt(selectedIndex) + "");//设置中间显示的字母
tvToast.setVisibility(View.VISIBLE);//设置为可见
}
@Override
public void onClickUp() {
tvToast.setVisibility(View.GONE);//当放开时,设置为不可见
}
});
}


当前效果图



最终要实现效果,在下面解析



加上一个ListView列表布局

1、主布局letters_layout.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"
android:orientation="vertical">

<ListView
android:id="@+id/listView_express"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="none"/>
<!-- 引用另一个布局文件 -->

<include
layout="@layout/express_overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />

<com.view.LetterView
android:id="@+id/letterView"
android:layout_width="30dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true" />

<TextView
android:id="@+id/show_now_abc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="A"
android:textColor="#ff5e00"
android:textSize="60sp"
android:visibility="gone" />
</RelativeLayout>


2、顶部布局express_overlay.xml



<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/viewOverlay_express"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<TextView
android:id="@+id/tvOverlay_express"
android:layout_width="fill_parent"
android:layout_height="45dp"
android:background="@android:color/holo_orange_light"
android:gravity="center_vertical"
android:paddingLeft="15dp"
android:text="A"
android:textColor="@android:color/holo_green_dark"
android:textSize="20sp"
android:textStyle="bold" />

</FrameLayout>


3、item子布局item_express.xml



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:id="@+id/tvLetter_item_express"
android:layout_width="fill_parent"
android:layout_height="45dp"
android:background="#17000000"
android:clickable="true"
android:gravity="center_vertical"
android:paddingLeft="15dp"
android:text="A"
android:textSize="20sp"
android:textStyle="bold" />

<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="60dp">

<TextView
android:id="@+id/tvCompanyName_item_express"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:gravity="center_vertical"
android:paddingLeft="20dp"
android:singleLine="true"
android:text="申通快递"
android:textColor="#000000"
android:textSize="20sp" />
</RelativeLayout>

</LinearLayout>


为ListView设置适配器

1、这里使用了一个数据库,表结构如下图



2、这里使用了AlphabetIndexer字母索引辅助类。去了解AlphabetIndexer

3、下面是适配器代码

public class OrderAdapter extends BaseAdapter
{
private Cursor cursor;//接收根据字母排序好了的cursor
private LayoutInflater inflater;//布局填充器,加载ListView子布局
private AlphabetIndexer indexer;//AlphabetIndexer字母索引辅助类
public OrderAdapter(Context context, Cursor cursor, AlphabetIndexer indexer)
{
this.cursor = cursor;
inflater = LayoutInflater.from(context);
this.indexer = indexer;
}
@Override
public int getCount() {
return cursor.getCount();
}

@Override
public Cursor getItem(int position) {
cursor.moveToPosition(position);
return cursor;
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null)
{
convertView = inflater.inflate(R.layout.item_express, parent, false);
holder = new ViewHolder();
holder.tvLetter = (TextView) convertView.findViewById(R.id.tvLetter_item_express);
holder.tvCompanyName = (TextView) convertView.findViewById(R.id.tvCompanyName_item_express);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
cursor.moveToPosition(position);
holder.tvCompanyName.setText(cursor.getString(cursor.getColumnIndex(ExpressDbHelper.TABLE_COMPANY_COMPANY_NAME)));
//获取这个位置代表的字符在字符表中的位置
int section = indexer.getSectionForPosition(position);
//判断当前位置是否是第一个出现这个字符,indexer.getPositionForSection(section)获取第一次出现的位置
if (position == indexer.getPositionForSection(section))
{
holder.tvLetter.setVisibility(View.VISIBLE);
holder.tvLetter.setText(cursor.getString(cursor.getColumnIndex(ExpressDbHelper.TABLE_COMPANY_COMPANY_INITIAL)));
}
else
{
holder.tvLetter.setVisibility(View.GONE);
}
return convertView;
}
private class ViewHolder
{
TextView tvLetter;
TextView tvCompanyName;
}
}


4、数据库帮助类(ExpressDbHelper.java)

public class ExpressDbHelper extends SQLiteOpenHelper{
public static final String DB_NAME = "express.db";
public static final int VERSION = 1;
/**快递公司名字**/
public static final String TABLE_COMPANY_COMPANY_NAME = "company_name";
/**快递公司对应code**/
public static final String TABLE_COMPANY_COMPANY_CODE = "company_code";
/**公司名字对应的首字母**/
public static final String TABLE_COMPANY_COMPANY_INITIAL = "initial";
public ExpressDbHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {

}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}
}


5、在MainActivity.java中

public class MainActivity extends Activity {
private ExpressDbHelper helper;
private LetterView letterView;
private TextView tvToast;
private ListView lv;
/*顶部的view*/
private View viewTop;
/*顶部显示的字母*/
private TextView tvTop;
private RelativeLayout.LayoutParams params;
/*字母索引辅助类*/
private AlphabetIndexer indexer;
/*字母索引*/
private String alphabet = "☆ABCDEFGHIJKLMNOPQRSTUVWXYZ";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
copyDatabase();//将assets目录下的数据库拷贝到程序的包中
initView();
afterInit();//主要处理部分
}

protected void initView() {
letterView = (LetterView) findViewById(R.id.letterView);
tvToast = (TextView) findViewById(R.id.show_now_abc);
lv = (ListView) findViewById(R.id.listView_express);
viewTop =  findViewById(R.id.viewOverlay_express);
tvTop = (TextView) findViewById(R.id.tvOverlay_express);
params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
}

protected void afterInit() {
helper = new ExpressDbHelper(this);//获得一个数据库帮助类
/*从数据库获取所有数据并根据公司对应的首字母排序*/
Cursor cursor = helper.getReadableDatabase().rawQuery("select * from company order by initial", null);
/*
* 参数1:包含数据的Cursor对象
* 参数2:进行索引排序的列号
* 参数3:字母表(空格将会作为第一个字符。字母要大写,并且按ascii/unicode排序。)
*/
indexer =  new AlphabetIndexer(cursor, cursor.getColumnIndex("initial"), alphabet);
lv.setAdapter(new OrderAdapter(this, cursor, indexer));

letterView.setOnLetterChangeListener(new LetterView.OnLetterChangeListener() {
@Override
public void onLetterChange(int selectedIndex) {
lv.setSelection(indexer.getPositionForSection(selectedIndex));//跳到以这个字母为索引的第一个名字位置
tvToast.setText(LetterView.letters.charAt(selectedIndex) + "");//设置中间显示的字母
tvToast.setVisibility(View.VISIBLE);//设置为可见
}

@Override
public void onClickUp() {
tvToast.setVisibility(View.GONE);//当放开时,设置为不可见
}
});
//给ListView设置滑动的监听,动态的改变顶部标题的显示
lv.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {

}
/*
* firstVisibleItem表示在现时屏幕第一个ListItem(部分显示的ListItem也算)
* totalItemCount表示ListView的ListItem总数
* view 表示整个ListView
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//按指定数据项的位置,返回匹配的索引项。
int section = indexer.getSectionForPosition(firstVisibleItem);
//得到下一个索引项
int nextSection = section+1;
//按指定索引查找,返回匹配的第一行数据项位置或比较接近的数据项的位置
int nextPosition = indexer.getPositionForSection(nextSection);
//#=>判断ListView第二排的item的位置是否是下一个索引项的开始
if (firstVisibleItem + 1 != nextPosition)
{//标题显示不改变的状态下
params.topMargin = 0;
viewTop.setLayoutParams(params);
tvTop.setText(alphabet.charAt(section) + "");
}
else
{//标题显示改变的状态下
View v = view.getChildAt(0);
if (v == null)
{
return;
}
int dex = v.getBottom() - tvTop.getHeight();
if (dex <= 0)
{
params.topMargin = dex;
}
else
{
params.topMargin = 0;
}
tvTop.setText(alphabet.charAt(section) + "");
viewTop.setLayoutParams(params);
}
letterView.setSelected(section);
}
});
}

/**
* 拷贝express.db到数据库
*/
private void copyDatabase(){
try {
InputStream in = getAssets().open("express.db");
//得到数据库文件的路径
File file = getDatabasePath("express.db");
if(!file.exists()){
if(!file.getParentFile().exists()){
file.getParentFile().mkdir();
}
file.createNewFile();
}
else
{
return;
}
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int lenght;
while((lenght = in.read(buffer))!=-1){
fos.write(buffer, 0, lenght);
}
fos.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}


项目代码地址

http://download.csdn.net/detail/u014314614/9326443

我的感受

把这玩意理解完一下子就觉得简单多了,开始老是看代码去理解并不能真的理清思路,下次要看代码最好先知道整体的结构,然后从一个突破点一步步做下去,避免东想西想浪费时间。记住一切复杂都是从简单的小结构出发

这里的主要使用的新知识点就是AlphabetIndexer字母索引辅助类,让右侧字母索引和ListView的item显示联系起来

通过这个例子,以后若要用,我只要提供了一个数据库,数据库里面包含名字和名字的开头字母,我就能改下数据库就能方便的使用这个案例了

想说的话

博客坚持写,今后学习了新的东西就在这里记录一下,以便今后回顾,也希望小小笔记能帮助你们

若内容有什么地方不对、不清楚,还望吐槽,希望大家能一起成长

来一个: 阅读源码是一个学好编程的重要途径,不仅学习到编程思路、实现技巧和风格,更是能形成自己独特思路和风格。其中一定有很多无法理解的代码、英文看不懂等问阻扰着,坚持下去,突破重重困难。了解全局理清细节
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: