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

Android系统联系人全特效实现(下),字母表快速滚动

2017-07-29 23:59 525 查看
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9050671

在上一篇文章中,我和大家一起实现了类似于Android系统联系人的分组导航和挤压动画功能,不过既然文章名叫做《android系统联系人全特效实现》,那么没有快速滚动功能显然是称不上”全”的。因此本篇文章我将带领大家在上篇文章的代码基础上改进,加入快速滚动功能。

如果还没有看过我上一篇文章,请抓紧去阅读一下 Android系统联系人全特效实现(上),分组导航和挤压动画

其实ListView本身是有一个快速滚动属性的,可以通过在XML中设置android:fastScrollEnabled=”true”来启用。包括以前老版本的Android联系人中都是使用这种方式来进行快速滚动的。效果如下图所示:



不过这种快速滚动方式比较丑陋,到后来很多手机厂商在定制自己ROM的时候都将默认快速滚动改成了类似iPhone上A-Z字母表快速滚动的方式。这里我们怎么能落后于时代的潮流呢!我们的快速滚动也要使用A-Z字母表的方式!

下面就来开始实现,首先打开上次的ContactsDemo工程,修改activity_main.xml布局文件。由于我们要在界面上加入字母表,因此我们需要一个Button,将这个Button的背景设为一张A-Z排序的图片,然后居右对齐。另外还需要一个TextView,用于在弹出式分组布局上显示当前的分组,默认是gone掉的,只有手指在字母表上滑动时才让它显示出来。修改后的布局文件代码如下:
[html] view plain copy print?<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:scrollbars=“none”
android:fadingEdge=“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>

<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>
<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:scrollbars="none"
android:fadingEdge="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>

<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>
然后打开MainActivity进行修改,毫无疑问,我们需要对字母表按钮的touch事件进行监听,于是在MainActivity中新增如下代码:[java] view plain copy print?private void setAlpabetListener() { alphabetButton.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { float alphabetHeight = alphabetButton.getHeight(); float y = event.getY(); int sectionPosition = (int) ((y / alphabetHeight) / (1f / 27f)); if (sectionPosition < 0) { sectionPosition = 0; } else if (sectionPosition > 26) { sectionPosition = 26; } String sectionLetter = String.valueOf(alphabet.charAt(sectionPosition)); int position = indexer.getPositionForSection(sectionPosition); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: alphabetButton.setBackgroundResource(R.drawable.a_z_click); sectionToastLayout.setVisibility(View.VISIBLE); sectionToastText.setText(sectionLetter); contactsListView.setSelection(position); break; case MotionEvent.ACTION_MOVE: sectionToastText.setText(sectionLetter); contactsListView.setSelection(position); break; default: alphabetButton.setBackgroundResource(R.drawable.a_z); sectionToastLayout.setVisibility(View.GONE); } return true; } }); }
private void setAlpabetListener() {
alphabetButton.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
float alphabetHeight = alphabetButton.getHeight();
float y = event.getY();
int sectionPosition = (int) ((y / alphabetHeight) / (1f / 27f));
if (sectionPosition < 0) {
sectionPosition = 0;
} else if (sectionPosition > 26) {
sectionPosition = 26;
}
String sectionLetter = String.valueOf(alphabet.charAt(sectionPosition));
int position = indexer.getPositionForSection(sectionPosition);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
alphabetButton.setBackgroundResource(R.drawable.a_z_click);
sectionToastLayout.setVisibility(View.VISIBLE);
sectionToastText.setText(sectionLetter);
contactsListView.setSelection(position);
break;
case MotionEvent.ACTION_MOVE:
sectionToastText.setText(sectionLetter);
contactsListView.setSelection(position);
break;
default:
alphabetButton.setBackgroundResource(R.drawable.a_z);
sectionToastLayout.setVisibility(View.GONE);
}
return true;
}
});
}
可以看到,在这个方法中我们注册了字母表按钮的onTouch事件,然后在onTouch方法里做了一些逻辑判断和处理,下面我来一一详细说明。首先通过字母表按钮的getHeight方法获取到字母表的总高度,然后用event.getY方法获取到目前手指在字母表上的纵坐标,用纵坐标除以总高度就可以得到一个用小数表示的当前手指所在位置(0表在#端,1表示在Z端)。由于我们的字母表中一共有27个字符,再用刚刚算出的小数再除以1/27就可以得到一个0到27范围内的浮点数,之后再把这个浮点数向下取整,就可以算出我们当前按在哪个字母上了。然后再对event的action进行判断,如果是ACTION_DOWN或ACTION_MOVE,就在弹出式分组上显示当前手指所按的字母,并调用ListView的setSelection方法把列表滚动到相应的分组。如果是其它的action,就将弹出式分组布局隐藏。

MainActivity的完整代码如下:[java] view plain copy print?public class MainActivity extends Activity {

/**
* 分组的布局
*/
private LinearLayout titleLayout;

/**
* 弹出式分组的布局
*/
private RelativeLayout sectionToastLayout;

/**
* 右侧可滑动字母表
*/
private Button alphabetButton;

/**
* 分组上显示的字母
*/
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);
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() { alphabetButton.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { float alphabetHeight = alphabetButton.getHeight(); float y = event.getY(); int sectionPosition = (int) ((y / alphabetHeight) / (1f / 27f)); if (sectionPosition < 0) { sectionPosition = 0; } else if (sectionPosition > 26) { sectionPosition = 26; } String sectionLetter = String.valueOf(alphabet.charAt(sectionPosition)); int position = indexer.getPositionForSection(sectionPosition); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: alphabetButton.setBackgroundResource(R.drawable.a_z_click); sectionToastLayout.setVisibility(View.VISIBLE); sectionToastText.setText(sectionLetter); contactsListView.setSelection(position); break; case MotionEvent.ACTION_MOVE: sectionToastText.setText(sectionLetter); contactsListView.setSelection(position); break; default: alphabetButton.setBackgroundResource(R.drawable.a_z); sectionToastLayout.setVisibility(View.GONE); } return true; } }); }

/**
* 获取sort key的首个字符,如果是英文字母就直接返回,否则返回#。
*
* @param sortKeyString
* 数据库中读取出的sort key
* @return 英文字母或者#
*/
private String getSortKey(String sortKeyString) {
alphabetButton.getHeight();
String key = sortKeyString.substring(0, 1).toUpperCase();
if (key.matches(“[A-Z]”)) {
return key;
}
return “#”;
}

}
public class MainActivity extends Activity {

/**
* 分组的布局
*/
private LinearLayout titleLayout;

/**
* 弹出式分组的布局
*/
private RelativeLayout sectionToastLayout;

/**
* 右侧可滑动字母表
*/
private Button alphabetButton;

/**
* 分组上显示的字母
*/
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);
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() { alphabetButton.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { float alphabetHeight = alphabetButton.getHeight(); float y = event.getY(); int sectionPosition = (int) ((y / alphabetHeight) / (1f / 27f)); if (sectionPosition < 0) { sectionPosition = 0; } else if (sectionPosition > 26) { sectionPosition = 26; } String sectionLetter = String.valueOf(alphabet.charAt(sectionPosition)); int position = indexer.getPositionForSection(sectionPosition); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: alphabetButton.setBackgroundResource(R.drawable.a_z_click); sectionToastLayout.setVisibility(View.VISIBLE); sectionToastText.setText(sectionLetter); contactsListView.setSelection(position); break; case MotionEvent.ACTION_MOVE: sectionToastText.setText(sectionLetter); contactsListView.setSelection(position); break; default: alphabetButton.setBackgroundResource(R.drawable.a_z); sectionToastLayout.setVisibility(View.GONE); } return true; } }); }

/**
* 获取sort key的首个字符,如果是英文字母就直接返回,否则返回#。
*
* @param sortKeyString
* 数据库中读取出的sort key
* @return 英文字母或者#
*/
private String getSortKey(String sortKeyString) {
alphabetButton.getHeight();
String key = sortKeyString.substring(0, 1).toUpperCase();
if (key.matches("[A-Z]")) {
return key;
}
return "#";
}

}
好了,就改动了以上两处,其它文件都保持不变,让我们来运行一下看看效果:



非常不错!当你的手指在右侧字母表上滑动时,联系人的列表也跟着相应的变动,并在屏幕中央显示一个当前的分组。

现在让我们回数一下,分组导航、挤压动画、字母表快速滚动,Android系统联系人全特效都实现了。好了,今天的讲解到此结束,有疑问的朋友请在下面留言。

源码下载,请点击这里

blockquote{
border-left: 10px solid rgba(128,128,128,0.075);
background-color: rgba(128,128,128,0.05);
border-radius: 0 5px 5px 0;
padding: 15px 20px;
关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。
微信扫一扫下方二维码即可关注:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: