RecipientEditTextView相关类

概述

RecipientEditTextView是Android原生短信和电子邮件中用到的控件,代码位于frameworks/opt/chips(mtk代码中有对其修改,位于frameworks/ex/chips),会编译成libchips的jar包,app在编译时把它作为静态库编译。

如图所示,其中有“+10”字样的所在行就是RecipientEditTextView控件。每个号码有对应联系人的话会显示相应头像和名称,图像为一个圆角矩形,代码中对应的数据机构为一个chip。10表示已有十个chip(其实从代码中可以看出10本身也是一个chip,名称为mMoreChip),没有焦点的时候会收缩显示,点击获取焦点后会展开显示全部chip(但是有数量限制的,默认数量限制是50),可以滑动查看所有chip。控件获取焦点后再次点击某个chip,效果如下:

当chip对应联系人有多个号码的时候,可以选择并替换当前号码。chip中头像消失,显示删除按键,点击后删除该chip(该特性是mtk所加的功能,google原生此功能不在图示位置)。如果该chip无对应联系人的时,该chip会显示为文本编辑状态,并且自动移动到最后一个chip的位置,这个是因为基类MultiAutoCompleteTextView只能在最后的位置进行编辑操作。

总结下RecipientEditTextView,它其实本质就是textview,文本内容是以分隔符(逗号或者引号)分隔的一系列联系方式(一般是号码,也可以是邮件地址),拿常用的电子邮件收件地址栏对比很容易理解。它的最大作用是UI的显示,显示每个联系方式为图片方式,并提供了附加的一些操作,例如pc上foxmail收件人地址一栏不是原原本本的邮件地址,而是会显示为邮件地址对应联系人的名称,点击后可以查看联系人详情,编辑和查看往来邮件等。RecipientEditTextView就是要提供类似于foxmail收件人地址栏的UI显示效果和功能。

com.android.ex.chips.recipientchip

该目录下是chip相关类

BaseRecipientChip

接口类,chip最基本的功能

    void setSelected(boolean selected); //设置当前chip是否选中
    CharSequence getDisplay(); //获取显示内容,一般就是联系人名称
    long getContactId(); //联系人数据库id
    RecipientEntry getEntry(); //联系人信息
    CharSequence getOriginalText(); //原始的字符串内容,一般就是号码

DrawableRecipientChip

继承BaseRecipientChip,还是个接口,增加了两个方法:

    Rect getBounds(); //获取图片显示区域
    void draw(Canvas canvas); //图片绘制方法

InvisibleRecipientChip

接口的实现类,如名字所述就是用来显示不可见chip的。

public class InvisibleRecipientChip extends ReplacementSpan implements DrawableRecipientChip 

实现了DrawableRecipientChip接口

    @Override
    public void draw(final Canvas canvas, final CharSequence text, final int start, final int end,
            final float x, final int top, final int y, final int bottom, final Paint paint) {
        // Do nothing.
    }

    @Override
    public Rect getBounds() {
        return new Rect(0, 0, 0, 0);
    }

不绘制任何东西,这个在chip过多或者控件失去焦点显示为收缩状态的时候使用。

继承自ReplacementSpan,Span详细可见字符级Span解析。Span可以逐字符级别的设置文本的样式,例如常见的ForegroundColorSpan可以设置某些字符为高亮色,ReplacementSpan比较特殊,它不是设置当前字符的某些样式(例如ForegroundColorSpan就是设置文字颜色),它是直接替换对应字符索引为预设置的Object,在RecipientEditTextView中其实就是替换为一副图像,那么不难推断就是将每个号码都替换为一张图片,该图片显示了头像和名字。

InvisibleRecipientChip就是什么也不显示。

SimpleRecipientChip

实现了BaseRecipientChip接口

    public SimpleRecipientChip(final RecipientEntry entry) {
        mDisplay = entry.getDisplayName();
        mValue = entry.getDestination().trim();
        mContactId = entry.getContactId();
        mDirectoryId = entry.getDirectoryId();
        mLookupKey = entry.getLookupKey();
        mDataId = entry.getDataId();
        mEntry = entry;
    }

构造函数中依据RecipientEntry填充各个字段

ReplacementDrawableSpan

继承ReplacementSpan

   protected Drawable mDrawable;

    public ReplacementDrawableSpan(Drawable drawable) {
        super();
        mDrawable = drawable;
    }

    @Override
    public void draw(Canvas canvas, CharSequence charSequence, int start, int end, float x, int top,
                     int y, int bottom, Paint paint) {
        canvas.save();
        int transY = (bottom - mDrawable.getBounds().bottom + top) / 2;
        canvas.translate(x, transY);
        mDrawable.draw(canvas);
        canvas.restore();
    }

    protected Rect getBounds() {
        return mDrawable.getBounds();
    }

最重要的是实现了图片的绘制,该图片会替换对应文字。

VisibleRecipientChip

public class VisibleRecipientChip extends ReplacementDrawableSpan implements DrawableRecipientChip

类似InvisibleRecipientChip,不过这个是真正负责显示chip的类

    @Override
    public Rect getBounds() {
        return super.getBounds();
    }

    @Override
    public void draw(final Canvas canvas) {
        mDrawable.draw(canvas);
    }

DrawableRecipientChip的两个方法都有实现,getBounds就是直接调用基类ReplacementDrawableSpan的方法。

总结:chip主要由两部分组成,一部分是联系人相关信息,另外一部分负责显示字符的替换。

com.android.ex.chips

该目录下首先最主要的就是是RecipientEditTextView这个文件了,其余的类都是围绕它,可以分为两大类:一类是工具类,另外一类是adapter。

AccountSpecifier

    public void setAccount(Account account);

该文件只有一个接口,设置账户。

ChipsUtil

    public static boolean supportsChipsUi() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
    }

只有一个方法,判断是否系统版本是否支持该控件。

CircularImageView

如名字,显示圆形图片,用于显示联系人头像

PhotoManager

接口类

    public static final int PHOTO_CACHE_SIZE = 20; //缓存数常量

    void populatePhotoBytesAsync(RecipientEntry entry, PhotoManagerCallback callback); //唯一的方法,从异步获取照片,然后回调PhotoManagerCallback

    interface PhotoManagerCallback { //三个回调方法
        void onPhotoBytesPopulated();  //如果在缓冲中,则直接返回,不用走异步获取的流程
        void onPhotoBytesAsynchronouslyPopulated(); //异步返回结果
        void onPhotoBytesAsyncLoadFailed(); //获取失败
    }

DefaultPhotoManager

实现了PhotoManager,使用AsyncTask完成异步获取头像功能。

Queries

 static abstract class Query {
        private final String[] mProjection; //查询时使用的projection
        private final Uri mContentFilterUri; //查询使用的filter uri
        private final Uri mContentUri;   //查询时使用的uri

        public static final int NAME = 0;                // String 使用cursor时的常量,名字
        public static final int DESTINATION = 1;         // String 号码或者邮件地址
        public static final int DESTINATION_TYPE = 2;    // int 号码类型int值
        public static final int DESTINATION_LABEL = 3;   // String 号码类型字符串
        public static final int CONTACT_ID = 4;          // long 联系人Id
        public static final int DATA_ID = 5;             // long 号码对应data id
        public static final int PHOTO_THUMBNAIL_URI = 6; // String 小头像uri
        public static final int DISPLAY_NAME_SOURCE = 7; // int 名字source
        public static final int LOOKUP_KEY = 8;          // String lookup值,用于查询联系人
        public static final int MIME_TYPE = 9;           // String mime类型

        public Query(String[] projection, Uri contentFilter, Uri content) {
            mProjection = projection;
            mContentFilterUri = contentFilter;
            mContentUri = content;
        }

        ...

        public abstract CharSequence getTypeLabel(Resources res, int type, CharSequence label);
    }

定义了Query类,常量字段含义见联系人存储ContactsProvider表分析,并且初始化了两个实例:

    public static final Query PHONE = new Query(new String[] {...}, Phone.CONTENT_FILTER_URI, Phone.CONTENT_URI) {

            @Override
            public CharSequence getTypeLabel(Resources res, int type, CharSequence label) {
                return Phone.getTypeLabel(res, type, label);
            }

    };

    public static final Query EMAIL...

一个用于查询号码对应联系人,一个用于查询邮件地址对应联系人。

RecipientEntry

查询到的联系人数据封装。

    protected RecipientEntry(int entryType, String displayName, String destination,
            int destinationType, String destinationLabel, long contactId, Long directoryId,
            long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
            String lookupKey) {
        mEntryType = entryType;
        mIsFirstLevel = isFirstLevel;
        mDisplayName = displayName;
        mDestination = destination;
        mDestinationType = destinationType;
        mDestinationLabel = destinationLabel;
        mContactId = contactId;
        mDirectoryId = directoryId;
        mDataId = dataId;
        mPhotoThumbnailUri = photoThumbnailUri;
        mPhotoBytes = null;
        mIsDivider = false;
        mIsValid = isValid;
        mLookupKey = lookupKey;
    }

对应成员含义基本对应对应Query中的常量,该类中有几个construct开头的方法以便生成RecipientEntry:

    public static RecipientEntry constructFakeEntry(final String address, final boolean isValid) { //fake表示虚假的联系人,其实只有address有意义
        final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
        final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address;

        return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress,
                INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
                INVALID_CONTACT, null, true, isValid, null /* lookupKey */);
    }

    public static RecipientEntry constructFakePhoneEntry(final String phoneNumber,   //生成address为号码的虚假RecipientEntry
            final boolean isValid) {
        ...
    }

    public static RecipientEntry constructGeneratedEntry(String display, String address,
            boolean isValid) { //依据名字和address生成RecipientEntry,但是ContactsProvider中并无对应的数据,只有邮件会使用
        ...
    }

    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
            String destination, int destinationType, String destinationLabel, long contactId,
            Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid,
            String lookupKey) {
        ...
    } //该方法和后续的方法是给MultiAutoCompleteTextView过滤联系人的时候使用的,top是下拉列表第一条数据对应的RecipientEntry,second是后续条目对应的

    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
            String destination, int destinationType, String destinationLabel, long contactId,
            Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid,
            String lookupKey) {
        ...
    }

    public static RecipientEntry constructSecondLevelEntry(String displayName,
            int displayNameSource, String destination, int destinationType,
            String destinationLabel, long contactId, Long directoryId, long dataId,
            String thumbnailUriAsString, boolean isValid, String lookupKey) {
        ...
    }

TopLevel和SecondLevel从代码中看只会导致mIsDivider的值不同,top中赋值为true,second中赋值为false,但是代码中并无任何地方使用这个值,所以目前这两个方法是完全一样的效果

SingleRecipientArrayAdapter

class SingleRecipientArrayAdapter extends ArrayAdapter<RecipientEntry>

RecipientEditTextView中showAddress方法中使用的adapter,chip单击事件会引起showAddress的调用,目前从代码看showAddress只有在地址类型是Email的情况下才有可能会走。单击后会弹出dialog,dialog中显示对应条目(只有一个条目所以名称有single字样),有删除按键可以删除该chip(该点击的唯一作用从代码看就是删除功能了)。

RecipientAlternatesAdapter

public class RecipientAlternatesAdapter extends CursorAdapter

该adapter依然是给单击chip后弹出的dialog使用,被RecipientEditTextView中的showAlternates方法使用,这个可以有多个条目,点击后替换当前的chip的号码或邮件地址(所以名称中有alternate字样)。

    public interface RecipientMatchCallback {
        public void matchesFound(Map<String, RecipientEntry> results);
        /**
         * Called with all addresses that could not be resolved to valid recipients.
         */
        public void matchesNotFound(Set<String> unfoundAddresses);
    }

    public static void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
            ArrayList<String> inAddresses, Account account, RecipientMatchCallback callback) {
        ...
    }

getMatchingRecipients是更新chip数据的主要方法,inAddresses是控件中的address列表,依据address查询数据库最终生成对应的RecipientEntry,回调callback得到的结果是以address为key,RecipientEntry为value的Map对象。

BaseRecipientAdapter

public class BaseRecipientAdapter extends BaseAdapter implements Filterable, AccountSpecifier,
        PhotoManager.PhotoManagerCallback

MultiAutoCompleteTextView匹配列表布局使用

    @Override
    public Filter getFilter() {
        return new DefaultFilter();
    }

过滤器是自定义的新类,查询数据库并返回RecipientEntry,生成的结果在三个成员中

    private LinkedHashMap<Long, List<RecipientEntry>> mEntryMap; //最原始的数据
    private List<RecipientEntry> mNonAggregatedEntries; //不在ContactsProvider中存储的数据
    private Set<String> mExistingDestinations; //号码集合,去重

mNonAggregatedEntries这个很难理解,因为我从来没有见过有国内有应用实现DirectoryProvider(见联系人存储ContactsProvider表分析),google系列的应用国内又无法使用。这种数据来源于其它app,例如google
talk,可以通过Android联系人的标准查询查询数据。国内的qq等数据都是自成一体,不会共享出来的。

DropdownChipLayouter

上述三个adapter的布局都是通过该类进行的。例如BaseRecipientAdapter中的getView:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ...
        return mDropdownChipLayouter.bindView(convertView, parent, entry, position,
                AdapterType.BASE_RECIPIENT, constraint);
    }

所有相关类都已分析完毕,RecipientEditTextView.java的代码这个重头会新开一篇分析

时间: 2024-10-26 07:08:23

RecipientEditTextView相关类的相关文章

Bayes++ Library入门学习之熟悉UKF相关类

UKF-SLAM是一种比较流行SLAM方案.相比EKF-SLAM,UKF利用unscented transform代替了EKF的线性化趋近,因而具有更高的精度.Bayes++库中的unsFlt.hpp中给出了UKF实现的相关类. namespace Bayesian_filter 39 { 40 41 class Unscented_predict_model : public Predict_model_base 42 /* Specific Unscented prediction mode

【Anroid】第9章 列表视图(1)--ListView相关类及其适配器

分类:C#.Android.VS2015: 创建日期:2016-02-18 一.简介 列表视图(ListView)是Android应用程序中使用最频繁的UI组件,从无处不在短菜单选项列表到冗长的联系人或internet收藏夹列表--都会用它来实现. ListView控件的用法非常灵活,既可以使用内置的视图呈现基本的样式,也可以用自定义视图呈现各种特殊的视觉效果. 要完全掌握Android提供的ListView控件的用法,需要解决下面的问题: (1)如何用Android内置的视图呈现列表视图中的每

Web---演示Servlet的相关类、下载技术、线程问题、自定义404页面

Servlet的其他相关类: ServletConfig – 代表Servlet的初始化配置参数. ServletContext – 代表整个Web项目. ServletRequest – 代表用户的请求. ServletResponse – 代表用户的响应. HttpSession – 代表用户的一次会话. 本篇博客演示:ServletConfig类 和 ServletContext 类(网页点击量统计,留言板和图片下载技术(其他类型文件类似)) ServletConfig: 它包含了Serv

List 接口以及实现类和相关类源码分析

List 接口以及实现类和相关类源码分析 List接口分析 接口描述 用户可以对列表进行随机的读取(get),插入(add),删除(remove),修改(set),也可批量增加(addAll),删除(removeAll,retainAll),获取(subList). 还有一些判定操作:包含(contains[All]),相等(equals),索引(indexOf,lastIndexOf),大小(size). 还有获取元素类型数组的操作:toArray() 注意事项 两种迭代器Iterator和L

hive的shims相关类分析

在hive的源码中经常可以看到shims相关的类,shims相关类是用来兼容不同的hadoop和hive版本的,以HadoopShims为例org.apache.hadoop.hive.shims.HadoopShims是一个接口,具体的实现类为 org.apache.hadoop.hive.shims.Hadoop20Shims org.apache.hadoop.hive.shims.Hadoop20SShims org.apache.hadoop.hive.shims.Hadoop23Sh

Android 网络编程 API笔记 - java.net 包 权限 地址 套接字 相关类 简介

Android 网络编程相关的包 : 9 包, 20 接口, 103 类, 6 枚举, 14异常; -- Java包 : java.net 包 (6接口, 34类, 2枚举, 12异常); -- Android包 : android.net 包 (1接口, 19类, 3枚举, 1异常), android.net.http 包 (6类), android.net.nsd 包 (3接口, 2类), android.net.rtp (4类), android.net.sip 包 (1接口, 9类, 1

ajax相关类web前端面试题

ajax相关类 1. ajax的跨域问题和缓存原理? 答: 推荐:ajax处理跨域有几种方式?实现原理是什么? 推荐:ajax缓存原理 2. 同步与异步的区别? 答: 1. 同步请求:顺序处理,即当我们向服务器发出一个请求时,在服务器没返回结果给客户端之前,我们要一直处于等待状态直至服务器将结果返回到客户端,我们才能执行下一步操作. 2. 异步请求:并行处理,当我们向服务器发出一个请求时,在服务器没返回结果之前,我们还是可以执行其他操作. 3. ajax属于javascript? 答:javas

(转载)VS2010/MFC编程入门之十三(对话框:属性页对话框及相关类的介绍)

前面讲了模态对话框和非模态对话框,本节开始鸡啄米讲一种特殊的对话框--属性页对话框.另外,本套教程所讲大部分对VC++各个版本均可适用或者稍作修改即可,但考虑到终究还是基于VS2010版本的,所以将<VC++/MFC编程入门>改为<VS2010/MFC编程入门>. 属性页对话框的分类 属性页对话框想必大家并不陌生,XP系统中桌面右键点属性,弹出的就是属性页对话框,它通过标签切换各个页面.另外,我们在创建MFC工程时使用的向导对话框也属于属性页对话框,它通过点击"Next&

iOS开发RunLoop学习:三:Runloop相关类(source和Observer)

一:RunLoop相关类: 其中:source0指的是非基于端口por,说白了也就是处理触摸事件,selector事件,source1指的是基于端口的port:是处理系统的一些事件 注意:创建一个RunLoop之后,有默认的运行模式mode,也可以为RunLoop指定运行模式,RunLoop启动必须得有运行模式,而且在运行模式中必须还有timer或是source事件其中之一,否则RunLoop就会退出.启动RunLoop必须调用start方法 二:RunLoop运行处理逻辑 RunLoop通知观