最近倍感代码功底不够,于是乎拼命学习他人的优秀代码,偶然间读到 Android系统联系人全特效实现(上),分组导航和挤压动画这篇文章,受益颇多,也解决了一个自己一直想实现的效果,非常钦佩博主的开源精神。这篇文章右侧导航栏是用图片实现的,个人感觉这样用户体验不是很好,于是自定义了一个View来实现右侧导航菜单。在这里跟大家分享一下
运行效果截图
界面布局如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/contacts_list_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:fadingEdge="none"
android:scrollbars="none" >
</ListView>
<LinearLayout
android:id="@+id/title_layout"
android:layout_width="fill_parent"
android:layout_height="18dip"
android:layout_alignParentTop="true"
android:background="#303030" >
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="10dip"
android:textColor="#ffffff"
android:textSize="13sp" />
</LinearLayout>
<com.example.contactsdemo.view.MyLetterListView
android:id="@+id/mLetterListView"
android:layout_width="30dip"
android:layout_height="fill_parent"
android:layout_alignParentRight="true"
android:background="#00ffffff" />
<!--
<Button
android:id="@+id/alphabetButton"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentRight="true"
android:background="@drawable/a_z"
/>
-->
<RelativeLayout
android:id="@+id/section_toast_layout"
android:layout_width="70dip"
android:layout_height="70dip"
android:layout_centerInParent="true"
android:background="@drawable/section_toast"
android:visibility="gone" >
<TextView
android:id="@+id/section_toast_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="30sp" />
</RelativeLayout>
</RelativeLayout>
布局比较简单,只是有一个自定义的View
右侧导航菜单 MyLetterListView.java
package com.example.contactsdemo.view;
import com.example.contactsdemo.R;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* 联系人 右侧的导航菜单
*
* @author FX_SKY 2012.11.15
*
*/
public class MyLetterListView extends View {
OnTouchingLetterChangedListener onTouchingLetterChangedListener;
String[] keyword = { "#","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" };
int choose = -1;
Paint paint = new Paint();
boolean showBkg = false;
public static final byte FINGER_ACTION_DOWN = -3;
public static final byte FINGER_ACTION_MOVE = -2;
public static final byte FINGER_ACTION_UP = -1;
public MyLetterListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyLetterListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLetterListView(Context context) {
super(context);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (showBkg) {
canvas.drawColor(Color.parseColor("#454545"));
}
int height = getHeight();
int width = getWidth();
int singleHeight = height / keyword.length;
for (int i = 0; i < keyword.length; i++) {
paint.setColor(Color.WHITE); //设置字体的颜色
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setTextSize(getResources().getDimensionPixelSize(R.dimen.navigation_fontsize));//设置字体的大小
paint.setAntiAlias(true);
if (i == choose) {
paint.setColor(Color.parseColor("#3399ff"));
paint.setFakeBoldText(true);
}
float xPos = width / 2 - paint.measureText(keyword[i]) / 2;
float yPos = singleHeight * i + singleHeight;
canvas.drawText(keyword[i], xPos, yPos, paint);
paint.reset();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float y = event.getY();
final int oldChoose = choose;
final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
final int c = (int) (y / getHeight() * keyword.length);
switch (action) {
case MotionEvent.ACTION_DOWN:
showBkg = true;
if (oldChoose != c && listener != null) {
if (c >= 0 && c < keyword.length) {
listener.onTouchingLetterChanged(c,keyword[c],FINGER_ACTION_DOWN);
choose = c;
invalidate();
}
}
break;
case MotionEvent.ACTION_MOVE:
if (oldChoose != c && listener != null) {
if (c > 0 && c < keyword.length) {
listener.onTouchingLetterChanged(c,keyword[c],FINGER_ACTION_MOVE);
choose = c;
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
showBkg = false;
choose = -1;
listener.onTouchingLetterChanged(-1,null,FINGER_ACTION_UP);
invalidate();
break;
}
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
public void setOnTouchingLetterChangedListener(
OnTouchingLetterChangedListener onTouchingLetterChangedListener) {
this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
}
public interface OnTouchingLetterChangedListener {
public void onTouchingLetterChanged(int selectionIndex,String sectionLetter,int state);
}
}
在MyLetterListView 中定义了一个回调接口 OnTouchingLetterChangedListener,用于处理 用户触摸事件。
主界面 MainActivity.java
package com.example.contactsdemo;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AlphabetIndexer;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.example.contactsdemo.view.MyLetterListView;
import com.example.contactsdemo.view.MyLetterListView.OnTouchingLetterChangedListener;
/**
* 主界面,联系人列表界面。
*
*/
public class MainActivity extends Activity {
/**
* 分组的布局
*/
private LinearLayout titleLayout;
/**
* 弹出式分组的布局
*/
private RelativeLayout sectionToastLayout;
/**
* 右侧可滑动字母表
*/
private MyLetterListView mLetterListView;
/**
* 分组上显示的字母
*/
private TextView title;
/**
* 弹出式分组上的文字
*/
private TextView sectionToastText;
/**
* 联系人ListView
*/
private ListView contactsListView;
/**
* 联系人列表适配器
*/
private ContactAdapter adapter;
/**
* 用于进行字母表分组
*/
private AlphabetIndexer indexer;
/**
* 存储所有手机中的联系人
*/
private List<Contact> contacts = new ArrayList<Contact>();
/**
* 定义字母表的排序规则
*/
private String alphabet = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/**
* 上次第一个可见元素,用于滚动时记录标识。
*/
private int lastFirstVisibleItem = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adapter = new ContactAdapter(this, R.layout.contact_item, contacts);
titleLayout = (LinearLayout) findViewById(R.id.title_layout);
sectionToastLayout = (RelativeLayout) findViewById(R.id.section_toast_layout);
title = (TextView) findViewById(R.id.title);
sectionToastText = (TextView) findViewById(R.id.section_toast_text);
// alphabetButton = (Button) findViewById(R.id.alphabetButton);
mLetterListView = (MyLetterListView) findViewById(R.id.mLetterListView);
contactsListView = (ListView) findViewById(R.id.contacts_list_view);
Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
Cursor cursor = getContentResolver().query(uri,
new String[] { "display_name", "sort_key" }, null, null,
"sort_key");
if (cursor.moveToFirst()) {
do {
String name = cursor.getString(0);
String sortKey = getSortKey(cursor.getString(1));
Contact contact = new Contact();
contact.setName(name);
contact.setSortKey(sortKey);
contacts.add(contact);
} while (cursor.moveToNext());
}
startManagingCursor(cursor);
indexer = new AlphabetIndexer(cursor, 1, alphabet);
adapter.setIndexer(indexer);
if (contacts.size() > 0) {
setupContactsListView();
setAlpabetListener();
}
}
/**
* 为联系人ListView设置监听事件,根据当前的滑动状态来改变分组的显示位置,从而实现挤压动画的效果。
*/
private void setupContactsListView() {
contactsListView.setAdapter(adapter);
contactsListView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
int section = indexer.getSectionForPosition(firstVisibleItem);
int nextSecPosition = indexer
.getPositionForSection(section + 1);
if (firstVisibleItem != lastFirstVisibleItem) {
MarginLayoutParams params = (MarginLayoutParams) titleLayout
.getLayoutParams();
params.topMargin = 0;
titleLayout.setLayoutParams(params);
title.setText(String.valueOf(alphabet.charAt(section)));
}
if (nextSecPosition == firstVisibleItem + 1) {
View childView = view.getChildAt(0);
if (childView != null) {
int titleHeight = titleLayout.getHeight();
int bottom = childView.getBottom();
MarginLayoutParams params = (MarginLayoutParams) titleLayout
.getLayoutParams();
if (bottom < titleHeight) {
float pushedDistance = bottom - titleHeight;
params.topMargin = (int) pushedDistance;
titleLayout.setLayoutParams(params);
} else {
if (params.topMargin != 0) {
params.topMargin = 0;
titleLayout.setLayoutParams(params);
}
}
}
}
lastFirstVisibleItem = firstVisibleItem;
}
});
}
/**
* 设置字母表上的触摸事件,根据当前触摸的位置结合字母表的高度,计算出当前触摸在哪个字母上。
* 当手指按在字母表上时,展示弹出式分组。手指离开字母表时,将弹出式分组隐藏。
*/
private void setAlpabetListener() {
mLetterListView
.setOnTouchingLetterChangedListener(new OnTouchingLetterChangedListener() {
@Override
public void onTouchingLetterChanged(int selectionIndex,
String sectionLetter, int state) {
int position = indexer
.getPositionForSection(selectionIndex);
switch (state) {
case MyLetterListView.FINGER_ACTION_DOWN: // 手指按下
sectionToastLayout.setVisibility(View.VISIBLE);
sectionToastText.setText(sectionLetter);
contactsListView.setSelection(position);
break;
case MyLetterListView.FINGER_ACTION_MOVE: // 手指滑动
sectionToastText.setText(sectionLetter);
contactsListView.setSelection(position);
break;
case MyLetterListView.FINGER_ACTION_UP:
sectionToastLayout.setVisibility(View.GONE);// 手指离开
break;
default:
break;
}
}
});
}
/**
* 获取sort key的首个字符,如果是英文字母就直接返回,否则返回#。
*
* @param sortKeyString
* 数据库中读取出的sort key
* @return 英文字母或者#
*/
private String getSortKey(String sortKeyString) {
String key = sortKeyString.substring(0, 1).toUpperCase();
if (key.matches("[A-Z]")) {
return key;
}
return "#";
}
}
ContactAdapter类
package com.example.contactsdemo;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.SectionIndexer;
import android.widget.TextView;
/**
* 联系人列表适配器。
*
*/
public class ContactAdapter extends ArrayAdapter<Contact> {
/**
* 需要渲染的item布局文件
*/
private int resource;
/**
* 字母表分组工具
*/
private SectionIndexer mIndexer;
public ContactAdapter(Context context, int textViewResourceId, List<Contact> objects) {
super(context, textViewResourceId, objects);
resource = textViewResourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Contact contact = getItem(position);
LinearLayout layout = null;
if (convertView == null) {
layout = (LinearLayout) LayoutInflater.from(getContext()).inflate(resource, null);
} else {
layout = (LinearLayout) convertView;
}
TextView name = (TextView) layout.findViewById(R.id.name);
LinearLayout sortKeyLayout = (LinearLayout) layout.findViewById(R.id.sort_key_layout);
TextView sortKey = (TextView) layout.findViewById(R.id.sort_key);
name.setText(contact.getName());
int section = mIndexer.getSectionForPosition(position);
if (position == mIndexer.getPositionForSection(section)) {
sortKey.setText(contact.getSortKey());
sortKeyLayout.setVisibility(View.VISIBLE);
} else {
sortKeyLayout.setVisibility(View.GONE);
}
return layout;
}
/**
* 给当前适配器传入一个分组工具。
*
* @param indexer
*/
public void setIndexer(SectionIndexer indexer) {
mIndexer = indexer;
}
}
contact_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/sort_key_layout"
android:layout_width="fill_parent"
android:layout_height="18dip"
android:background="#303030" >
<TextView
android:id="@+id/sort_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="10dip"
android:textColor="#ffffff"
android:textSize="13sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/name_layout"
android:layout_width="fill_parent"
android:layout_height="50dip" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:src="@drawable/icon" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="#ffffff"
android:textSize="22sp" />
</LinearLayout>
</LinearLayout>
联系人实体 Contact
package com.example.contactsdemo;
/**
* 联系人实体类
*
*/
public class Contact {
/**
* 联系人姓名
*/
private String name;
/**
* 排序字母
*/
private String sortKey;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSortKey() {
return sortKey;
}
public void setSortKey(String sortKey) {
this.sortKey = sortKey;
}
}
主要思路借鉴这篇文章:http://blog.youkuaiyun.com/sinyu890807/article/details/9033553