android 带搜索框、checkbox、sidebar的联系人列表【contacts listview with checkbox and sidebar in Android】

主要就三个部分,一个是listview,一个是一侧的sidebar,还有就是加入一个checkbox的处理


1、ListView

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout 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:background="@color/bg_grey"
    android:fitsSystemWindows="true"
    android:orientation="vertical"
    tools:context="com.cn.app_bxb.ui.ActImportAddressBook">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/title_height"
        android:background="@color/white">

        <ImageView
            android:id="@+id/back"
            style="@style/back_icon" />

        <TextView
            style="@style/UpTitle"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="选择手机联系人" />

    </RelativeLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/item_height"
        android:layout_margin="15dp"
        android:background="@drawable/bg_login_edit"
        android:orientation="horizontal"
        android:padding="5dp"
        android:shadowRadius="160">

        <ImageView
            android:layout_width="20dp"
            android:layout_height="match_parent"
            android:src="@mipmap/add_search" />

        <EditText
            android:id="@+id/et_search_edit"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@null"
            android:hint="搜索客户"
            android:inputType="number"
            android:padding="10dp"
            android:textColorHint="@color/edit_text_hint_grey"
            android:textSize="15dp" />

    </LinearLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:id="@+id/above_frame"
            android:layout_width="match_parent"
            android:layout_marginBottom="2dp"
            android:layout_height="320dp">

            <ListView
                android:id="@+id/ls_contacts_listview"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/white"
                android:divider="@color/bg_grey"
                android:dividerHeight="3dp"></ListView>

            <TextView
                android:id="@+id/tv_letter_notice"
                style="@style/addressCircleButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:gravity="center"
                android:textSize="20sp"
                android:visibility="gone" />

            <TextView
                android:id="@+id/pb_nocontacts_notice"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:gravity="center"
                android:text="暂无匹配联系人"
                android:visibility="gone" />

            <com.cn.app_bxb.view.AlphabetScrollBar
                android:id="@+id/alphabetscrollbar"
                android:layout_width="30dp"
                android:layout_height="match_parent"
                android:layout_gravity="right" />

        </FrameLayout>
        <LinearLayout
            android:layout_below="@id/above_frame"
            android:id="@+id/bottom_buttons"
            android:layout_alignParentBottom="true"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tv_selected_number"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_gravity="left"
                android:layout_weight="1"
                android:background="@drawable/bg_import_button"
                android:gravity="center"
                android:text="已选择:0人"
                android:textSize="25sp" />

            <TextView
                android:id="@+id/btn_yes"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="@drawable/bg_import_button"
                android:clickable="true"
                android:gravity="center"
                android:onClick="onClick"
                android:textColor="@color/theme_red"
                android:text="确定"
                android:textSize="25sp" />
        </LinearLayout>

    </RelativeLayout>

</LinearLayout>  

效果如下 当然,要定制一下listview里面的item

<?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/cut_item_LetterTag"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/bg_grey"
        android:paddingLeft="5dp"
        android:visibility="gone" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/contacts_first_letter"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/addressCircleButton"
            android:layout_alignParentLeft="true"
            android:layout_marginLeft="10dp"/>

        <TextView
            android:id="@+id/contacts_name"
            android:layout_width="wrap_content"
            android:textSize="20dp"
            android:layout_centerInParent="true"
            android:layout_gravity="center"
            android:layout_height="wrap_content" />

        <CheckBox
            android:id="@+id/ck_is_checked"
            android:layout_gravity="center"
            android:layout_alignParentRight="true"
            android:gravity="right"
            android:paddingRight="30dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </RelativeLayout>


</LinearLayout>  

预览图如下

2、sidebar,也就是导航用的侧边栏,a~z的

package com.cn.app_bxb.view;  
import android.content.Context;  
import android.graphics.Canvas;  
import android.graphics.Color;  
import android.graphics.Paint;  
import android.util.AttributeSet;  
import android.view.MotionEvent;  
import android.view.View;  
import android.widget.TextView;

/**
 * Created by liushuqing on 16/3/11.
 * 右侧的滑动点击条,用来选择联系人
 */
public class AlphabetScrollBar extends View {

    private Paint mPaint = new Paint();
    private String[] mAlphabet = new String[] {
            "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","#"
    };
    private boolean mPressed;
    private int mCurPosIdx = -1;
    private int mOldPosIdx = -1;
    private OnTouchBarListener mTouchListener;
    private TextView LetterNotice;

    public AlphabetScrollBar(Context arg0, AttributeSet arg1, int arg2) {
        super(arg0, arg1, arg2);
    }

    public AlphabetScrollBar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public AlphabetScrollBar(Context context) {
        super(context);
    }

    public void setTextView(TextView LetterNotice) {
        this.LetterNotice = LetterNotice;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int width = this.getWidth();
        int height = this.getHeight();

        int singleLetterH = height/mAlphabet.length;

        if(mPressed) {
            //如果处于按下状态,改变背景及相应字体的颜色
            canvas.drawColor(Color.parseColor("#40000000"));
        }

        for(int i=0; i<mAlphabet.length; i++) {
            mPaint.setColor(Color.parseColor("#000000"));
            mPaint.setAntiAlias(true);
            mPaint.setTextSize(23);

            float x = width/2 - mPaint.measureText(mAlphabet[i])/2;
            float y = singleLetterH*i+singleLetterH;

            if(i == mCurPosIdx)
            {
                mPaint.setColor(Color.parseColor("#0000FF"));
                mPaint.setFakeBoldText(true);
            }
            canvas.drawText(mAlphabet[i], x, y, mPaint);
            mPaint.reset();
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent arg0) {

        int action = arg0.getAction();
        switch(action) {
            case MotionEvent.ACTION_DOWN:
                mPressed = true;
                mCurPosIdx =(int)( arg0.getY()/this.getHeight() * mAlphabet.length);
                if(mTouchListener != null && mOldPosIdx!=mCurPosIdx){
                    if((mCurPosIdx>=0) && (mCurPosIdx<mAlphabet.length)) {
                        mTouchListener.onTouch(mAlphabet[mCurPosIdx]);
                        this.invalidate();
                    }
                    mOldPosIdx = mCurPosIdx;
                }

                LetterNotice.setText(mAlphabet[mCurPosIdx]);
                LetterNotice.setVisibility(View.VISIBLE);

                return true;
            case MotionEvent.ACTION_UP:

                if (LetterNotice != null) {
                    LetterNotice.setVisibility(View.INVISIBLE);
                }

                mPressed = false;
                mCurPosIdx = -1;
                this.invalidate();
                return true;
            case MotionEvent.ACTION_MOVE:
                mCurPosIdx =(int)( arg0.getY()/this.getHeight() * mAlphabet.length);
                if(mTouchListener != null && mCurPosIdx!=mOldPosIdx){
                    if((mCurPosIdx>=0) && (mCurPosIdx<mAlphabet.length)) {
                        mTouchListener.onTouch(mAlphabet[mCurPosIdx]);
                        this.invalidate();
                    }
                    mOldPosIdx = mCurPosIdx;
                }

                if(mCurPosIdx >= 0 && mCurPosIdx < mAlphabet.length)
                {
                    LetterNotice.setText(mAlphabet[mCurPosIdx]);
                    LetterNotice.setVisibility(View.VISIBLE);
                }

                return true;
            default:
                return super.onTouchEvent(arg0);
        }

    }

    /**
     * 接口
     */
    public static interface OnTouchBarListener {
        void onTouch(String letter);
    }

    /**
     * 向外公开的方法
     */
    public void setOnTouchBarListener (OnTouchBarListener listener) {
        mTouchListener = listener;
    }
}

3、Activity里面对于checkbox的处理

package com.cn.app_bxb.ui;

import android.content.ContentResolver;  
import android.content.Context;  
import android.database.Cursor;  
import android.os.Bundle;  
import android.provider.ContactsContract;  
import android.text.Editable;  
import android.text.TextWatcher;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.ViewGroup;  
import android.widget.BaseAdapter;  
import android.widget.CheckBox;  
import android.widget.CompoundButton;  
import android.widget.EditText;  
import android.widget.ListView;  
import android.widget.TextView;

import com.alibaba.fastjson.JSON;  
import com.cn.app_bxb.R;  
import com.cn.app_bxb.api.model.User;  
import com.cn.app_bxb.view.AlphabetScrollBar;

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.Comparator;  
import java.util.List;

import bxb.cn.base.BaseActivity;  
import bxb.cn.base.injection.Id;  
import bxb.cn.base.injection.Layout;  
import bxb.cn.base.util.LogUtil;  
import bxb.cn.base.util.NullUtil;  
import bxb.cn.base.util.PinyinUtils;

@Layout(R.layout.activity_import_address_book)
public class ActImportAddressBook extends BaseActivity {  
    private static final String TAG = "bxb";

    @Id(R.id.tv_selected_number)
    private TextView tvSelectedNumber;
    @Id(R.id.btn_yes)
    private TextView btnImport;
    //字母列视图View
    @Id(R.id.alphabetscrollbar)
    private AlphabetScrollBar side_bar;
    //显示选中的字母
    @Id(R.id.tv_letter_notice)
    private TextView tvFirstLetterNotice;
    //联系人的列表
    @Id(R.id.ls_contacts_listview)
    private ListView contactsListView;
    //联系人列表的适配器
    private ListAdapter listViewAdapter;
    //所有联系人数组
    private ArrayList<User> persons = new ArrayList<>();
    //被选中联系人数组
    private List<User> checkedUsers = new ArrayList<>();
    //过滤列表中的联系人数组
    ArrayList<User> filterpersons = new ArrayList<User>();
    //搜索过滤联系人EditText
    @Id(R.id.et_search_edit)
    private EditText etSearchEditText;
    //没有匹配联系人时显示的TextView
    @Id(R.id.pb_nocontacts_notice)
    private TextView listEmptyText;

    private int checkedNum=0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //获取手机中的联系人,并将所有联系人保存perosns数组中
        //联系人比较多的话,初始化中会比较耗时,以后再优化
        getContacts();
        //得到字母列的对象,并设置触摸响应监听器
        side_bar.setOnTouchBarListener(new ScrollBarListener());
        side_bar.setTextView(tvFirstLetterNotice);

        // 根据拼音为联系人数组进行排序
        Collections.sort(persons, new UserPinyinComparator());

        //得到联系人列表,并设置适配器
        listViewAdapter = new ListAdapter(this, persons);
        contactsListView.setAdapter(listViewAdapter);
        //初始化搜索编辑框,设置文本改变时的监听器
        etSearchEditText.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

                if (!"".equals(s.toString().trim())) {
                    //根据编辑框值过滤联系人并更新联系列表
                    filterContacts(s.toString().trim());
                    side_bar.setVisibility(View.GONE);
                } else {
                    side_bar.setVisibility(View.VISIBLE);
                    listViewAdapter.updateListView(persons);
                }
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count,
                                          int after) {

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_selected_number:
                break;
            case R.id.btn_yes:
                updateSelectedUsers();
                importsers();
                LogUtil.i(TAG,"这些人被选中"+JSON.toJSONString(checkedUsers));
                break;
        }
        super.onClick(v);
    }

    /**
     *更新被选中的人
     */
    private void updateSelectedUsers(){
        checkedUsers.clear();
        for(User user:persons){
            if(user.isChecked()&&!checkedUsers.contains(user)){
                checkedUsers.add(user);
            }
        }

        for(User user:filterpersons){
            if(user.isChecked()&&!checkedUsers.contains(user)){
                checkedUsers.add(user);
            }
        }
    }

    /**
     * TODO 调用导入联系人API
     */
    private void importsers(){
        LogUtil.d(TAG,"导入联系人");
    }

    /**
     * 按照名字拼音进行排序
     */
    public class UserPinyinComparator implements Comparator<User> {

        @Override
        public int compare(User lhs, User rhs) {
            String str1 = lhs.getPinyinName();
            String str2 = rhs.getPinyinName();
            return str1.compareToIgnoreCase(str2);
        }
    }

    public class ListAdapter extends BaseAdapter {
        private LayoutInflater m_inflater;
        private ArrayList<User> users;
        private Context context;

        public ListAdapter(Context context,
                           ArrayList<User> persons) {
            this.m_inflater = LayoutInflater.from(context);
            this.users = persons;
            this.context = context;
        }

        //当联系人列表数据发生变化时,用此方法来更新列表
        public void updateListView(ArrayList<User> persons) {
            this.users = persons;

            notifyDataSetChanged();
        }


        @Override
        public int getCount() {
            return users.size();
        }

        @Override
        public Object getItem(int position) {
            return users.get(position);
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final int mPosition = position;
            LogUtil.d(TAG, "list里面应该展示的" + JSON.toJSONString(users.get(position)));
            //获得一个联系人item,下面的所有都是从这个item里面提取
            convertView = m_inflater.inflate(R.layout.address_item, null);
            //名字
            TextView name = (TextView) convertView.findViewById(R.id.contacts_name);
            name.setText(users.get(position).getName());
            //复选框
            final CheckBox checkBox=(CheckBox)convertView.findViewById(R.id.ck_is_checked);
            checkBox.setChecked(users.get(position).isChecked());//保证已经被选中的还可以拿得到
            checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    users.get(mPosition).setChecked(isChecked);
                    updateSelectedUsers();
                    tvSelectedNumber.setText("已经被选中"+ checkedUsers.size()+"人");
                    LogUtil.i(TAG,users.get(mPosition).getName()+": 被改变选中状态");
                    notifyDataSetChanged();//更新选中数据
                    listViewAdapter.updateListView(persons);
                }
            });

            //名字第一个字,圆形里面展示用的
            TextView firstLetter = (TextView) convertView.findViewById(R.id.contacts_first_letter);
            firstLetter.setText(users.get(position).getFirstLetter());
            //字母提示textview的显示,就是不同之母之间分解的时候出现的
            TextView letterTag = (TextView) convertView.findViewById(R.id.cut_item_LetterTag);
            //获得当前姓名的拼音首字母,排序用的,并不展示
            String sortLetter = users.get(position).getSortLetter();

            //TODO 这儿实现的好巧妙啊啊啊嗷嗷啊啊啊  如果是第1个联系人 那么letterTag始终要显示
            if (position == 0) {
                letterTag.setVisibility(View.VISIBLE);
                letterTag.setText(sortLetter);
            } else {
                //获得上一个姓名的拼音首字母
                String firstLetterPre = users.get(position - 1).getSortLetter();
                //比较一下两者是否相同
                if (sortLetter.equals(firstLetterPre)) {
                    letterTag.setVisibility(View.GONE);
                } else {
                    letterTag.setVisibility(View.VISIBLE);
                    letterTag.setText(sortLetter);
                }
            }
            return convertView;
        }

    }

    //字母列触摸的监听器
    private class ScrollBarListener implements AlphabetScrollBar.OnTouchBarListener {

        @Override
        public void onTouch(String letter) {

            //触摸字母列时,将联系人列表更新到首字母出现的位置
            for (int i = 0; i < persons.size(); i++) {
                if (persons.get(i).getSortLetter().compareToIgnoreCase(letter) == 0) {
                    contactsListView.setSelection(i);
                    break;
                }
            }
        }
    }

    /**
     * 读取联系人
     */
    public void getContacts() {
        ContentResolver contentResolver = getContentResolver();
        // 获得所有联系人数据集的游标
        Cursor cursor = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
        // 循环遍历
        if (cursor.moveToFirst()) {

            int idColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID);
            int displayNameColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
            int NumberColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);

            while (cursor.moveToNext()) {
                User person = new User();
                // 获得联系人的ID号
                String contactId = cursor.getString(idColumn);

                // 获得联系人姓名
                person.setName(cursor.getString(displayNameColumn));
                person.setPinyinName(PinyinUtils.getPingYin(person.getName()));
                person.setSortLetter(PinyinUtils.getFirstSpell(person.getName()));
                //名字可能是空的,如果是null,就让它是空格吧
                person.setFirstLetter(NullUtil.ept(person.getName()) ? "" : person.getName().substring(0, 1));
                person.setPhone(cursor.getString(NumberColumn));
                LogUtil.d(TAG, JSON.toJSONString(person));
                persons.add(person);
            }
            cursor.close();
        }
    }

    /**
     * 过滤联系人
     *
     * @param filterStr
     */
    private void filterContacts(String filterStr) {
        //先清空原来的
        filterpersons.clear();
        //遍历所有联系人数组,筛选出包含关键字的联系人
        for (int i = 0; i < persons.size(); i++) {
            //过滤的条件
            if (isStrInString(persons.get(i).getPhone(), filterStr)
                    || isStrInString(persons.get(i).getPinyinName(), filterStr)
                    || persons.get(i).getName().contains(filterStr)
                    || isStrInString(persons.get(i).getFirstLetter(), filterStr)) {
                //将筛选出来的联系人重新添加到filterpersons数组中
                filterpersons.add(persons.get(i));
            }
        }

        //如果没有匹配的联系人
        if (filterpersons.isEmpty()) {
            contactsListView.setEmptyView(listEmptyText);
        }

        //将列表更新为过滤的联系人
        listViewAdapter.updateListView(filterpersons);
    }

    public boolean isStrInString(String bigStr, String smallStr) {
        if (bigStr.toUpperCase().indexOf(smallStr.toUpperCase()) > -1) {
            return true;
        } else {
            return false;
        }
    }
}

备注

1、里面用了一个朋友提供的,直接使用@Layout(id)或者@Id(id)来关联activity和layout的框架

2、显示效果是比较丑的,不过定制化改进很简单哒。图示如下

刘摸鱼

退堂鼓表演艺术家

杭州