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

本文首发于CSDN博客,转载请注明出处: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掉的,只有手指在字母表上滑动时才让它显示出来。修改后的布局文件代码如下:

<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中新增如下代码:

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的完整代码如下:

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系统联系人全特效都实现了!

好了,今天的讲解到此结束,有疑问的朋友请在下面留言。

源码下载,请点击这里

时间: 2024-08-04 18:34:18

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

android系统联系人分组特效实现(2)---字母表快速滚动

要实现这种功能,只需要在   android系统联系人分组特效实现(1)---分组导航和挤压动画  的基础上再加上一个自定义控件即可完成. 1.新建项目,继续新建一个java类,BladeView,用于作为导航栏 public class BladeView extends View { private Handler handler = new Handler(); public BladeView(Context context, AttributeSet attrs) { super(co

Android系统联系人全特效实现(上),分组导航和挤压动画

本文首发于CSDN博客,转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9033553 记得在我刚接触Android的时候对系统联系人中的特效很感兴趣,它会根据手机中联系人姓氏的首字母进行分组,并在界面的最顶端始终显示一个当前的分组.如下图所示: 最让我感兴趣的是,当后一个分组和前一个分组相碰时,会产生一个上顶的挤压动画.那个时候我思考了各种方法想去实现这种特效,可是限于功夫不到家,都未能成功.如今两年多过去了,自己也成长了很多,

android系统联系人分组特效实现(1)---分组导航和挤压动画

1.打开activity_main.xml <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&q

android系统 关闭应用

1.概述: 应用程序部署到手机上对应的进程有五种优先级:前台进程.可视进程.服务进程.后台进程.空进程. android系统中,默认情况下是不会把进程杀死掉的,这种做法的目的是:再次打开这个应用程序的时候可以快速的启动,节省时间. 但是这样做也带来了一个问题,有很多的应用程序都在后台留一个空进程,这样就会占据一些内存空间,影响手机的运行速度. 作为一个有责任感的程序员,我们要保证应用程序在结束时释放掉对应的进程. 2.关闭方式 第一种:首先获取当前进程的id,然后杀死该线程. //获取当前进程的

Android系统源码学习步骤

Android系统是基于Linux内核来开发的,在分析它在运行时库层的源代码时,我们会经常碰到诸如管道(pipe).套接字(socket)和虚拟文件系统(VFS)等知识. 此外,Android系统还在Linux内核中增加了一些专用的驱动程序,例如用于日志系统的Logger驱动程序.用于进程间通信的Binder驱动程序和用于辅助内存管理的匿名共享内存Ashmem驱动程序.在分析这些Android专用驱动程序的时候,也会碰到Linux内核中与进程.内存管理相关的数据结构. 因此,我们有必要掌握一些L

对Android系统权限的认识

Android系统是运行在Linux内核上的,Android与Linux分别有自己的一套严格的安全及权限机制 Android系统权限相关的内容 (一)linux文件系统上的权限 -rwxr-x--x system system 4156 2012-06-30 16:12 test.apk. 代表的是相应的用户/用户组及其他人对此文件的访问权限,与此文件运行起来具有的权限完全不相关. 比如上面的例子只能说明system用户拥有对此文件的读写执行权限:system组的用户对此文件拥有读.执行权限:其

Android读取系统联系人

使用 ContentProvider共享数据: 当应用继承 ContentProvider 类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据.虽 然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外 共享数据,需要进行文件操作读写数据:采用 sharedpreferences 共享数据,需要使用 sharedpreferences API 读写数据.而使用 ContentProvider 共享数据的好处是 统一了数据访问方式 . 当

android: 通过内容提供器读取系统联系人

读取系统联系人 由于我们之前一直使用的都是模拟器,电话簿里面并没有联系人存在,所以现在需要自 己手动添加几个,以便稍后进行读取.打开电话簿程序,界面如图 7.1 所示. 图   7.1 可以看到,目前电话簿里是没有任何联系人的,我们可以通过点击 Create a new contact按钮来对联系人进行创建.这里就先创建两个联系人吧,分别填入他们的姓名和手机号,如 图 7.2 所示. 图   7.2 这样准备工作就做好了,现在新建一个 ContactsTest 项目,让我们开始动手吧. 首先还是

[转]Android有趣的全透明效果--Activity及Dialog的全透明(附android系统自带图标大全

原文:http://blog.csdn.net/sodino/article/details/5822147 1.Activity全透明 同学zzm给了这个有趣的代码,现在公布出来. 先在res/values下建colors.xml文件,写入: <? xml   version = "1.0"   encoding = "UTF-8" ?>    < resources >        < color   name = "t