Android进阶之路 - 完美获取通讯录且回传显示

场景:因部分用户跳转通讯录后习惯使用右侧的的字母导航,然后我们发现部分机型的右侧导航使用之后有时候无效,有时候会导致app当前界面崩溃,在这种需求驱动下,我整理了同事写的代码,然后做了细致划分,Tired …

注:只要你做Android一年以上,那么根据文中步骤操作的话,实现这个功能没什么难度 ~

Learning is the first driving force

目录
  • 提前掌握
  • 准备工作
  • 实际应用
提前掌握

注:整个功能Demo基本是围绕以下几个功能综合而成的,代码略微较长,为了方便查看,我已将它打包上传

Demo下载地址:点我,点我,快点我 > <

准备工作

二级目录

  • 权限部分
  • 通讯录部分
  • EventBus信息传递部分
  • Adapter部分
权限部分
 implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
 implementation 'io.reactivex.rxjava2:rxjava:2.0.2'
 implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
通讯录部分
  • 添加权限 - manifests
 <uses-permission android:name="android.permission.READ_CONTACTS" />
 <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  • Abbreviated
package nkwl.com.contentdemo;

/**
 * @author paul
 * @date
 * desc:
 */
public interface Abbreviated {
    String getInitial();
}
  • 用于接收联系人信息的Model - ShareContactsBean
/**
 * Copyright 2017 ChenHao Dendi
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nkwl.com.contentdemo;

public class ShareContactsBean implements Abbreviated, Comparable<ShareContactsBean> {
    private final String mName;
    private final String mPhone;
    private final String mAbbreviation;
    private final String mInitial;

    ShareContactsBean(String name, String phone) {
        this.mName = name;
        this.mPhone = phone;
        this.mAbbreviation = ContactsUtils.getAbbreviation(name);
        this.mInitial = mAbbreviation.substring(0, 1);
    }

    @Override
    public String getInitial() {
        return mInitial;
    }

    public String getName() {
        return mName;
    }

    public String getPhone() {
        return mPhone;
    }

    @Override
    public int compareTo(ShareContactsBean r) {
        if (mAbbreviation.equals(r.mAbbreviation)) {
            return 0;
        }
        boolean flag;
        if ((flag = mAbbreviation.startsWith("#")) ^ r.mAbbreviation.startsWith("#")) {
            return flag ? 1 : -1;
        }
        return getInitial().compareTo(r.getInitial());
    }
}
  • 获取联系人的工具类 - ContactsManager
package nkwl.com.contentdemo;

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract;

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

import io.reactivex.annotations.NonNull;

public class ContactsManager {

    @NonNull
    public static ArrayList<ShareContactsBean> getPhoneContacts(Context context) {
        ArrayList<ShareContactsBean> result = new ArrayList<>();
        ContentResolver resolver = context.getContentResolver();
        Cursor phoneCursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME}, null, null, null);
        if (phoneCursor != null) {
            while (phoneCursor.moveToNext()) {
                String phoneNumber = phoneCursor.getString(0).replace(" ", "").replace("-", "");
                String contactName = phoneCursor.getString(1);
                result.add(new ShareContactsBean(contactName, phoneNumber));
            }
            phoneCursor.close();
        }

        Collections.sort(result, new Comparator<ShareContactsBean>() {
            @Override
            public int compare(ShareContactsBean l, ShareContactsBean r) {
                return l.compareTo(r);
            }
        });
        return result;
    }
}
EventBus信息传递部分
  • build.fradle(app)
    implementation 'org.greenrobot:eventbus:3.0.0'
  • Eventbus大多都会有一个实体类用于数据传递 - EventBusBean
package nkwl.com.contentdemo;
/**
 * Created by paul on 2017/8/10.
 * EventBus发送的事件实体
 */
public class EventBusBean<T> {
    private int code;
    private T data;

    public EventBusBean(int code) {
        this.code = code;
    }

    public EventBusBean(int code, T data) {
        this.code = code;
        this.data = data;
    }

    public int getCode() {
        return code;
    }

    public T getData() {
        return data;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public void setData(T data) {
        this.data = data;
    }
}
  • EventBus封装的工具类 - EventBusUtil
package nkwl.com.contentdemo;

import org.greenrobot.eventbus.EventBus;

public class EventBusUtil {
    public static void register(Object subscriber) {
        EventBus.getDefault().register(subscriber);
    }

    public static void unregister(Object subscriber) {
        EventBus.getDefault().unregister(subscriber);
    }

    public static void postEvent(EventBusBean eventBusBean) {
        EventBus.getDefault().post(eventBusBean);
    }

    //event3.0粘性事件
    public static void postStickyEvent(EventBusBean eventBusBean) {
        EventBus.getDefault().postSticky(eventBusBean);
    }
}
Adapter部分
  • build.gradle
 implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'
 implementation 'com.android.support:recyclerview-v7:28.0.0'
  • 通讯录右侧字母导航的自定义控件 - AzSidebar
package nkwl.com.contentdemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;


/**
 * @author paul
 * @date desc:字母索引
 */
public class AzSidebar extends View {
    /**
     * 索引字母颜色
     */
    private static final int LETTER_COLOR = 0xFF757575;
    /**
     * 索引字母背景圆圈颜色
     */
    private static final int BG_COLOR = 0xFF4081D6;
    /**
     * 索引字母数组
     */
    public String[] indexs = {"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 int mWidth;
    private int mHeight;
    /**
     * 单元格的高度
     */
    private float mCellHeight;
    /**
     * 顶部间距
     */
    private float mMarginTop;
    /**
     * 手指按下的字母索引
     */
    private int touchIndex = -1;

    private Paint mPaint;

    private Paint bgPaint;

    /**
     * 手指是否触目屏幕
     */
    /*private boolean isTouched = false;*/

    /**
     * 手指抬起是否显示背景
     */
    private boolean isDisplay = true;

    public AzSidebar(Context context) {
        super(context);
        init();
    }

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

    private void init() {
        mPaint = new Paint();
        mPaint.setColor(LETTER_COLOR);
        mPaint.setTextSize(dp2px(getContext(), 11));
        mPaint.setAntiAlias(true);

        bgPaint = new Paint();
        bgPaint.setColor(BG_COLOR);
        bgPaint.setAntiAlias(true);
    }

    public void setIndexs(String[] indexs) {
        this.indexs = indexs;
        mMarginTop = (mHeight - mCellHeight * indexs.length) / 2;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //字母的坐标点:(x,y)
        if (indexs.length <= 0) {
            return;
        }
        drawLetter(canvas);
    }

    /**
     * 画字母
     */
    private void drawLetter(Canvas canvas) {
        for (int i = 0; i < indexs.length; i++) {
            String letter = indexs[i];
            float textX = mWidth / 2 - getTextWidth(letter) / 2;
            float textY = mCellHeight / 2 + getTextHeight(letter) / 2 + mCellHeight * i + mMarginTop;
            if (touchIndex == i && isDisplay) {
                //绘制文字圆形背景
                float circleX = mWidth / 2 + dp2px(getContext(), 0.5f);
                float circleY = mCellHeight / 2 + mCellHeight * i + mMarginTop;
                canvas.drawCircle(circleX, circleY, dp2px(getContext(), 8), bgPaint);
                mPaint.setColor(Color.WHITE);
            } else {
                mPaint.setColor(LETTER_COLOR);
            }

            canvas.drawText(letter, textX, textY, mPaint);
        }
    }

    /**
     * dp 转化为 px
     *
     * @param context context
     * @param dpValue dpValue
     * @return int
     */
    public static int dp2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 获取字符的宽度
     *
     * @param text 需要测量的字母
     * @return 对应字母的高度
     */
    public float getTextWidth(String text) {
        Rect bounds = new Rect();
        mPaint.getTextBounds(text, 0, text.length(), bounds);
        return bounds.width();
    }

    /**
     * 获取字符的高度
     *
     * @param text 需要测量的字母
     * @return 对应字母的高度
     */
    public float getTextHeight(String text) {
        Rect bounds = new Rect();
        mPaint.getTextBounds(text, 0, text.length(), bounds);
        return bounds.height();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        //26个字母加上"#"
        mCellHeight = (mHeight * 1f / 27);
        mMarginTop = (mHeight - mCellHeight * indexs.length) / 2;
    }

    public void setDisplay(boolean d) {
        this.isDisplay = d;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                // 按下字母的下标
                int letterIndex = (int) ((event.getY() - mMarginTop) / mCellHeight);
                if (letterIndex != touchIndex) {
                    if (letterIndex > indexs.length - 1) {
                        touchIndex = indexs.length - 1;
                    } else if (letterIndex < 0) {
                        touchIndex = 0;
                    } else {
                        touchIndex = letterIndex;
                    }
                }
                // 判断是否越界
                if (letterIndex >= 0 && letterIndex < indexs.length) {
                    // 显示按下的字母
                    if (textView != null) {
                        textView.setVisibility(View.VISIBLE);
                        textView.setText(indexs[letterIndex]);
                    }
                    //通过回调方法通知列表定位
                    if (mOnIndexChangedListener != null) {
                        mOnIndexChangedListener.onIndexChanged(indexs[letterIndex]);
                    }
                }
                //isTouched = true;
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (textView != null) {
                    textView.setVisibility(View.GONE);
                }
                //isTouched = false;
                break;
            default:
        }
        invalidate();
        return true;
    }

    public void setTouchIndex(String word) {
        for (int i = 0; i < indexs.length; i++) {
            if (indexs[i].equals(word)) {
                touchIndex = i;
                invalidate();
                return;
            }
        }
    }

    public interface OnIndexChangedListener {
        /**
         * 按下字母改变了
         *
         * @param index 按下的字母
         */
        void onIndexChanged(String index);
    }

    private OnIndexChangedListener mOnIndexChangedListener;

    private TextView textView;

    public void setOnIndexChangedListener(OnIndexChangedListener onIndexChangedListener) {
        this.mOnIndexChangedListener = onIndexChangedListener;
    }

    /**
     * 设置显示按下首字母的TextView
     */
    public void setSelectedIndexTextView(TextView textView) {
        this.textView = textView;
    }
}
  • 通讯录adaptere的Item布局 - item_contacts
<?xml version="1.0" encoding="utf-8"?>
<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="wrap_content"
    android:background="#fff">

    <View
        android:id="@+id/item_top_line"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="#E4E4E4"
        android:layout_marginLeft="15dp"
        android:layout_below="@+id/tv_letter"
        android:visibility="gone"/>

    <TextView
        android:id="@+id/tv_letter"
        android:layout_width="match_parent"
        android:layout_height="28dp"
        android:background="#F0F0F0"
        android:gravity="center_vertical"
        android:paddingLeft="15dp"
        android:textColor="#757575"
        android:visibility="gone"
        tools:text="A"
        tools:visibility="visible"/>

    <TextView
        android:id="@+id/tv_contacts_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_letter"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="10dp"
        android:ellipsize="end"
        android:maxLength="10"
        android:singleLine="true"
        tools:text="boy"/>

    <TextView
        android:id="@+id/tv_contacts_phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_contacts_name"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="6dp"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="6dp"
        android:gravity="center_vertical"
        android:textColor="#999999"
        android:textSize="14sp"
        tools:text="13764959025"/>
</RelativeLayout>
  • 通讯录的适配器 - ContactsAdapter
package nkwl.com.contentdemo;

import android.support.annotation.Nullable;
import android.view.View;

import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;

import java.util.List;

/**
 * @date 2018/12/3
 * desc:
 */
public class ContactsAdapter extends BaseQuickAdapter<ShareContactsBean, BaseViewHolder> {
    private List<ShareContactsBean> data;

    public ContactsAdapter(int layoutResId, @Nullable List<ShareContactsBean> data) {
        super(layoutResId, data);
        this.data = data;
    }

    @Override
    protected void convert(BaseViewHolder helper, ShareContactsBean item) {
        boolean isVisible = helper.getLayoutPosition() == getPositionForLetterName(item.getInitial());
        helper.getView(R.id.tv_letter).setVisibility(isVisible ? View.VISIBLE : View.GONE);
        helper.getView(R.id.item_top_line).setVisibility(isVisible ? View.GONE : View.VISIBLE);
        helper.setText(R.id.tv_contacts_name, item.getName())
                .setText(R.id.tv_contacts_phone, item.getPhone())
                .setText(R.id.tv_letter, item.getInitial());
    }

    public int getPositionForLetterName(String firstLetterChar) {
        for (int i = 0; i < data.size(); i++) {
            if (data.get(i).getInitial().equals(firstLetterChar)) {
                return i;
            }
        }
        return -1;
    }
}
实际应用
  • 一级页面UI - activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_phone"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:gravity="center"
        android:text="获取联系人信息" />

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:gravity="center"
        android:text="" />

    <TextView
        android:id="@+id/tv_num"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:gravity="center"
        android:text="" />

</LinearLayout>
  • 一级页面,主要包含权限请求、EventBus回调处理 - ManiActivity
package nkwl.com.contentdemo;

import android.Manifest;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.tbruyelle.rxpermissions2.RxPermissions;

import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;

public class MainActivity extends AppCompatActivity {
    private TextView mName;
    private TextView mPhone;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EventBusUtil.register(this);
        mName = findViewById(R.id.tv_name);
        mPhone = findViewById(R.id.tv_num);

        findViewById(R.id.tv_phone).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getPhoneData();
            }
        });
    }


    //动态权限
    private void getPhoneData() {
        RxPermissions rxPermissions = new RxPermissions(this);
        //申请一个权限,这个权限组就已经授权
        rxPermissions.request(Manifest.permission.READ_CONTACTS, Manifest.permission.READ_PHONE_STATE).subscribe(new Observer<Boolean>() {
            @Override
            public void onSubscribe(Disposable d) {

            }

            @Override
            public void onNext(Boolean aBoolean) {
                //有权限的状态
                if (aBoolean) {
                    Intent intent = new Intent(MainActivity.this, ContactsActivity.class);
                    startActivity(intent);
                }
                //无权限的状态
                else {
                    Toast.makeText(MainActivity.this, "没有授权", Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onComplete() {

            }
        });

    }

    //EventBus回调监听
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void receiceEvent(EventBusBean eventBusBean) {
        if (eventBusBean != null) {
            switch (eventBusBean.getCode()) {
                case 178:
                    ShareContactsBean shareContactsBean = (ShareContactsBean) eventBusBean.getData();
                    mName.setText(shareContactsBean.getName());
                    mPhone.setText(shareContactsBean.getPhone());
                    break;
                default:
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBusUtil.unregister(this);
    }
}

  • 通讯录UI - activity_contacts
<?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">

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

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_contacts"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

        <nkwl.com.contentdemo.AzSidebar
            android:id="@+id/sidebar"
            android:layout_width="30dp"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="20dp"/>
    </RelativeLayout>
</LinearLayout>
  • 通讯录(关键点) - ContactsActivity
package nkwl.com.contentdemo;

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.chad.library.adapter.base.BaseQuickAdapter;

import java.util.ArrayList;
import java.util.List;

public class ContactsActivity extends Activity {
    private List<ShareContactsBean> constactsList;
    private List<String> letterList;
    private ContactsAdapter contactsAdapter;
    private LinearLayoutManager layoutManager;
    private RecyclerView rvContacts;
    private AzSidebar sideBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contacts);
        rvContacts = findViewById(R.id.rv_contacts);
        sideBar = findViewById(R.id.sidebar);
        constactsList = new ArrayList<>();
        letterList = new ArrayList<>();
        initView();
        fetchData();
        setListener();
    }

    private void initView() {
        contactsAdapter = new ContactsAdapter(R.layout.item_contacts, constactsList);
        layoutManager = new LinearLayoutManager(this);
        rvContacts.setLayoutManager(layoutManager);
        rvContacts.setAdapter(contactsAdapter);
    }

    private void fetchData() {
        letterList.clear();
        constactsList.clear();
        ArrayList<ShareContactsBean> phoneContacts = ContactsManager.getPhoneContacts(this);
        constactsList.addAll(phoneContacts);
        for (int i = 0; i < constactsList.size(); i++) {
            ShareContactsBean shareContactsBean = constactsList.get(i);
            String initial = shareContactsBean.getInitial();
            if (!letterList.contains(initial)) {
                letterList.add(initial);
            }
        }
        sideBar.setIndexs(letterList.toArray(new String[letterList.size()]));
        contactsAdapter.notifyDataSetChanged();
    }

    private void setListener() {

        contactsAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
                List data = adapter.getData();
                ShareContactsBean shareContactsBean = (ShareContactsBean) data.get(position);
                EventBusUtil.postEvent(new EventBusBean<>(178, shareContactsBean));
                finish();
            }
        });

        sideBar.setOnIndexChangedListener(new AzSidebar.OnIndexChangedListener() {
            @Override
            public void onIndexChanged(String index) {
                int positionForLetterName = contactsAdapter.getPositionForLetterName(index);
                layoutManager.scrollToPositionWithOffset(positionForLetterName, 0);
            }
        });

        rvContacts.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (constactsList.size() > 0) {
                    int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
                    sideBar.setTouchIndex(constactsList.get(firstVisibleItemPosition).getInitial());
                }
            }
        });
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

远方那座山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值