标签:总结 hot ora blank adapter 通过 token content 高亮
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显示效果和功能。
void setSelected(boolean selected); //设置当前chip是否选中
CharSequence getDisplay(); //获取显示内容,一般就是联系人名称
long getContactId(); //联系人数据库id
RecipientEntry getEntry(); //联系人信息
CharSequence getOriginalText(); //原始的字符串内容,一般就是号码
Rect getBounds(); //获取图片显示区域
void draw(Canvas canvas); //图片绘制方法
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过多或者控件失去焦点显示为收缩状态的时候使用。 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填充各个字段 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();
}最重要的是实现了图片的绘制,该图片会替换对应文字。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的方法。public void setAccount(Account account);该文件只有一个接口,设置账户。
public static boolean supportsChipsUi() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
}只有一个方法,判断是否系统版本是否支持该控件。 public static final int PHOTO_CACHE_SIZE = 20; //缓存数常量
void populatePhotoBytesAsync(RecipientEntry entry, PhotoManagerCallback callback); //唯一的方法,从异步获取照片,然后回调PhotoManagerCallback
interface PhotoManagerCallback { //三个回调方法
void onPhotoBytesPopulated(); //如果在缓冲中,则直接返回,不用走异步获取的流程
void onPhotoBytesAsynchronouslyPopulated(); //异步返回结果
void onPhotoBytesAsyncLoadFailed(); //获取失败
} 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...一个用于查询号码对应联系人,一个用于查询邮件地址对应联系人。 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,但是代码中并无任何地方使用这个值,所以目前这两个方法是完全一样的效果
。class SingleRecipientArrayAdapter extends ArrayAdapter<RecipientEntry>
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对象。public class BaseRecipientAdapter extends BaseAdapter implements Filterable, AccountSpecifier,
PhotoManager.PhotoManagerCallbackMultiAutoCompleteTextView匹配列表布局使用 @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等数据都是自成一体,不会共享出来的。 @Override
public View getView(int position, View convertView, ViewGroup parent) {
...
return mDropdownChipLayouter.bindView(convertView, parent, entry, position,
AdapterType.BASE_RECIPIENT, constraint);
}
标签:总结 hot ora blank adapter 通过 token content 高亮
原文地址:http://blog.csdn.net/firedancer0089/article/details/70207359