2014-01-07 09:54:13  将百度空间里的东西移过来。

本文从点击“添加联系人”Button开始,分析新建联系人页面UI是如何加载,以及新的联系人信息是如何保存的,借此,我们一探Phonebook复杂的自定义View的加载机制。

1. 从前文分析我们知道,New Contact页面是随着帐号类型的不同,而显示不同的UI,这次我们以LocalAccountType为例来分析。

在联系人列表页面最下方,有一个“Add” Button, 点击新建联系人,这个Button其实一个MenuItem,在ContactsListFragment里面,点击事件处理在onOptionsItemSelected()方法,如下:

 @Override
public boolean onOptionsItemSelected(final MenuItem aItem) {
case R.id.menu_add_contact:
startActivityForResult(new Intent(Intent.ACTION_INSERT,
Contacts.CONTENT_URI),
SUBACTIVITY_ADD_CONTACT);
break;

处理Intent.ACTION_INSERT这个Action的是AddNewContactActivity,如果是第一次添加联系人,那么会让用户选择需要添加的账户,下次添加时会使用第一次选择的账户作为默认账户,我们以默认账户为例:

 startCreateContactActivity(mAccountUtils.getDefaultAccount());
// mAccountUtils.getDefaultAccount()返回一个默认账户,我们假设
// 默认的账户是本地账户,也就是LocalAccountType。

startCreateContactActivity()方法如下:

 private void startCreateContactActivity(AccountWithDataSet account) {
Intent intent = new Intent(this, ContactEditorActivity.class);
intent.setAction(ContactEditorActivity.ACTION_NEW_CONTACT); if(mIntentExtras != null) {
intent.putExtras(mIntentExtras);
} intent.putExtra(Intents.Insert.ACCOUNT, account);
startActivity(intent);
finish();
}

启动ContactEditorActivity,Intent同时封装了account信息,用"Intents.Insert.ACCOUNT",也就是上面获得默认的本地联系人的帐号信息。下面我们进入ContactEditorActivity。

2. 向ContactEditorActivity出发

 @Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.contact_editor_activity); ActionBar actionBar = getActionBar();
if (actionBar != null) {
View saveMenuItem = customActionBarView.findViewById(R.id.save_menu_item);
saveMenuItem.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mFragment.doSaveAction();
}
});
} mFragment = (ContactEditorFragment) getFragmentManager()
.findFragmentById(R.id.contact_editor_fragment);
mFragment.setListener(mFragmentListener);
Uri uri = Intent.ACTION_EDIT.equals(action) ?
getIntent().getData() : null;
mFragment.load(action, uri, getIntent().getExtras());
}

在onCreate()方法中,使用的布局文件是contact_editor_activity.xml,同时为Save Contact MenuItem注册了监听事件:mFragment.doSaveAction()。如果是编辑联系人,那么会取出uri,并查询,然后将查询到的信息填到New ContactUI里面,不过我们不管新编辑联系人。看contact_editor_activity.xml:

 <FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"> <fragment class="com.android.contacts.editor.ContactEditorFragment"
android:id="@+id/contact_editor_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

可以看到,ContactEditorActivity所有UI的显示和逻辑处理都在ContactEditorFragment,我们后续分析它。再看mFragment.load(action, uri, getIntent().getExtras());这行代码很重要,做了一些初始化的操作,同时将Intent中封装的account信息传给ContactEditorFragment。

3. ContactEditorFragment分析

进入ContactEditorFragment的onCreateView()方法:

 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
final View view = inflater.inflate(R.layout.contact_editor_fragment, container, false); mContent = (LinearLayout)view.findViewById(R.id.editors);
mAccountTypeManager = AccountTypeManager.getInstance(mContext);
mInflater = (LayoutInflater)mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE); setHasOptionsMenu(true); return view;
}

看contact_editor_fragment.xml-->contact_editor_fragment_container.xml:

 <LinearLayout
android:id="@+id/editors"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" > </LinearLayout>

最后我们发现整个ContactEditorFragment的根布局就是一个editors, 而且mContent = (LinearLayout)view.findViewById(R.id.editors),是一个LinearLayout。那么现在最关键的问题就是代码中mContent添加了那些东西,而这些动态添加的东西才是真正会显示的东西。接着往下看,onActivityCreated():

 if (ContactEditorActivity.ACTION_NEW_CONTACT.equals(mAction)) {
AccountWithDataSet accountWithDataSet = mIntentExtras == null ? null :
(AccountWithDataSet) mIntentExtras.getParcelable(Intents.Insert.ACCOUNT);
if (accountWithDataSet != null && accountWithDataSet.type != null) {
createContact(accountWithDataSet);
}

上面代码中取出了传过来的account,并封装成一个accountWithDataSet对象,调用createContact()-->bindEditorsForNewContact():

 private void bindEditorsForNewContact(AccountWithDataSet newAccount,
final AccountType newAccountType, RawContactDelta oldState,
AccountType oldAccountType) { final RawContact rawContact = new RawContact(mContext);
if (newAccount != null) {
rawContact.setAccount(newAccount);
} else {
rawContact.setAccountToLocalContact();
} RawContactDelta insert = new RawContactDelta(
ValuesDelta.fromAfter(rawContact.getValues())); if (mState == null) {
// Create state if none exists yet
mState = RawContactDeltaList.fromSingle(insert);
} else {
// Add contact onto end of existing state
mState.add(insert);
}
mRequestFocus = true; bindEditors();
}

上面的代码中首先用传进来的account创建了一个RawContact,然后构造了一个RawContactDelta对象insert,并调用mState.add(insert)。我们接着看bindEditors()方法:

 RawContactDelta rawContactDelta = getFirstVisibleContact();
if (rawContactDelta != null) {
editor = createContactEditorView(rawContactDelta);
}
mContent.addView(editor);

发现,mContent添加的竟然是一个editor,那么我们就看看这个editor到底是个什么东西,到底是怎么生成的。

4. createContactEditorView方法解析

在调用createContactEditorView()方法时,传入了一个参数,是一个RawContactDelta,看一下getFirstVisibleContact():

 private RawContactDelta getFirstVisibleContact() {
for (final RawContactDelta rawContactDelta : mState) {
if (!rawContactDelta.isVisible()) continue;
return rawContactDelta;
}
return null;
}

发现rawContactDelta其实就是mState中第一个对象,也就是在bindEditorsForNewContact()方法中添加进去的insert。

我们看createContactEditorView()中构造editor的代码:

 int layout = mIsLinkedContact ?
R.layout.raw_contact_editor_tab_view :
R.layout.raw_contact_editor_view;
editor = (BaseRawContactEditorView)mInflater.inflate(layout, null, false);
...
editor.setEnabled(mEnabled);
editor.setState(rawContactDelta, type, mViewIdGenerator, isEditingUserProfile());
...
return editor;

先看raw_contact_editor_view.xml:

 <com.android.contacts.editor.RawContactEditorView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" > <LinearLayout
android:id="@+id/body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"> <LinearLayout
android:background="@color/add_edit_header_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="@dimen/edit_contact_padding_start"
android:paddingTop="@dimen/raw_contact_edit_view_padding_top"
android:paddingBottom="@dimen/raw_contact_edit_view_padding_bottom"> <include
android:id="@+id/edit_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/raw_contact_edit_photo_margin_end"
layout="@layout/item_photo_editor" /> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom" > <include
android:id="@+id/edit_name"
layout="@layout/structured_name_editor_view" /> </LinearLayout> </LinearLayout> <include layout="@layout/editor_account_header_with_dropdown" /> <include layout="@layout/raw_contact_editor_body" /> </LinearLayout> </com.android.contacts.editor.RawContactEditorView>

这个布局文件包含New Contact页面所有的UI,我们发现editor竟然是一个自定义的RawContactEditorView。

edit_photo:Photo显示以及点击添加照片的UI;

edit_name:Name相关的UI;

editor_account_header_with_dropdown:选择帐号的下拉列表框;

raw_contact_editor_body:剩下的所有部分,包括Phone,Email和Postal Address等。

如图:

关于Name的添加比较特殊,我们后面分析,先以Phone为例往下看,先看raw_contact_editor_body.xml:

 <merge >
<LinearLayout
android:id="@+id/sect_fields"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/edit_contact_padding_start"
android:layout_marginTop="@dimen/raw_contact_sect_fields_margin_top"
android:layout_marginBottom="@dimen/raw_contact_sect_fields_margin_bottom"
android:orientation="vertical" /> <Button
android:id="@+id/button_add_field"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="@dimen/edit_contact_padding_start"
android:layout_marginBottom="@dimen/raw_contact_add_another_field_margin_bottom"
android:text="@string/add_field" />
</merge>

其中button_add_field指的是“Add another field”Button,其余部分都包含在sect_fields里面。这个id的处理是在RawContactEditorView的父类RawContactCommonEditorView中,如下:

 @Override
protected void onFinishInflate() {
super.onFinishInflate(); mInflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); mName = (StructuredNameEditorView)findViewById(R.id.edit_name);
mName.setDeletable(false); mFields = (ViewGroup)findViewById(R.id.sect_fields); mAddFieldButton = (Button) findViewById(R.id.button_add_field);
mAddFieldButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showAddInformationPopupWindow();
}
});
}

接着看createContactEditorView()中的editor.setState(rawContactDelta, type, mViewIdGenerator, isEditingUserProfile()),editor从xml文件解析获得之后,调用了这句,下面进入RawContactEditorView类,该类加载了account相关的UI,如mAccountIcon。不过我们先看他的setState()方法,参数如下:

rawContactDelta:前面创建的RawContactDelta对象;

type:账户类型;

发现他首先调用了父类的super.setState(state, type, vig, isProfile);RawContactEditorView的继承关系如下:

我们进入RawContactCommonEditorView类的setState方法,该方法有一个非常重要的循环体:

 for (DataKind kind : type.getSortedDataKinds()) {
// Skip kind of not editable
if (!kind.editable) continue; final String mimeType = kind.mimeType;
if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
final ValuesDelta primary = state.getPrimaryEntry(mimeType);
mName.setValues(type.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE),
primary, state, false, vig);
} else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
if (mGroupMembershipView != null) {
mGroupMembershipView.setState(state);
}
} else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
} else {
if (kind.fieldList == null) continue;
final KindSectionView section = (KindSectionView)mInflater.inflate(
R.layout.item_kind_section, mFields, false);
section.setEnabled(isEnabled());
section.setState(kind, state, false, vig);
mFields.addView(section);
}
}

我们好好分析一下这个循环体,因为这个循环体里面的内容根前文中提到的DataKind,AccountType关系较大。跳过其他,只看else部分。前文中分析过两个重要的方法,其中一个就是getSortedDataKinds(),该方法返回一个AccountType添加的所有的DataKind。现在应该明白了吧,一个账户类根据自己的需要添加了好多DataKind,比如Name, Phone, Email或者更多,而这里就是循环加载他们的地方。不过我们发现怎么所有产生的section都被加到了mFields,还记得前面提到的mFields = (ViewGroup)findViewById(R.id.sect_fields)吗?是的,mFields就是sect_fields 这个id对应的View, 包含除了“Add another field”之外的所有UI(Name, Photo除外),到此真相大白,mFields添加了账户中包含的所有DataKind,并将他们显示出来。

Android Phonebook编写联系人UI加载及联系人保存流程(三)的更多相关文章

  1. Android Phonebook编写联系人UI加载及联系人保存流程(一)

    2014-01-06 17:05:11 将百度空间里的东西移过来. 本文适合ROM定制做Phonebook的童鞋看,其他人飘过即可- Phonebook添加/编辑联系人UI加载及保存联系人流程,是一系 ...

  2. Android Phonebook编写联系人UI加载及联系人保存流程(五)

    2014-01-07 10:46:30 将百度空间里的东西移过来. 在前面的文章中我们分析了UI的加载,其中提到了一个重要的对象:RawContactDeltaList mState,我前面说过这个对 ...

  3. Android Phonebook编写联系人UI加载及联系人保存流程(二)

    2014-01-06 17:18:29 1. Phonebook中新建/编辑联系人的UI不是用xml文件写的,它是随着帐号类型的改变来加载不同的UI,比如SIM联系人,只有Name.Phone Num ...

  4. Android Phonebook编写联系人UI加载及联系人保存流程(四)

    2014-01-07 10:23:22 将百度空间里的东西移过来. 5. KindSectionView KindSectionView是何方神圣呢?它又是怎么怎么和一个DataKind,以及一个Ra ...

  5. Android Phonebook编写联系人UI加载及联系人保存流程(六)

    2014-01-07 11:18:08 将百度空间里的东西移过来. 1. Save contact 我们前面已经写了四篇文章,做了大量的铺垫,总算到了这一步,见证奇迹的时刻终于到了. 用户添加了所有需 ...

  6. Android ViewPager Fragment使用懒加载提升性能

     Android ViewPager Fragment使用懒加载提升性能 Fragment在如今的Android开发中越来越普遍,但是当ViewPager结合Fragment时候,由于Androi ...

  7. android加载大量图片内存溢出的三种方法

    android加载大量图片内存溢出的三种解决办法 方法一:  在从网络或本地加载图片的时候,只加载缩略图. /** * 按照路径加载图片 * @param path 图片资源的存放路径 * @para ...

  8. Android开发中如何解决加载大图片时内存溢出的问题

    Android开发中如何解决加载大图片时内存溢出的问题    在Android开发过程中,我们经常会遇到加载的图片过大导致内存溢出的问题,其实类似这样的问题已经屡见不鲜了,下面将一些好的解决方案分享给 ...

  9. Android引入高速缓存的异步加载全分辨率

    Android引进高速缓存的异步加载全分辨率 为什么要缓存 通过图像缩放,我们这样做是对的异步加载优化的大图,但现在的App这不仅是一款高清大图.图.动不动就是图文混排.以图代文,假设这些图片都载入到 ...

随机推荐

  1. OpenGL的gluLookAt和glOrtho的关系

    OpenGL的gluLookAt和glOrtho的关系 一直不明白gluLookAt()和glOrtho()两者之间的关系:gluLookAt()是观察变换,glOrtho()是正交投影.glLook ...

  2. Kafka文件的存储机制

    Kafka文件的存储机制 同一个topic下有多个不同的partition,每个partition为一个目录,partition命名的规则是topic的名称加上一个序号,序号从0开始. 每一个part ...

  3. cocoapods ,错误大全

    出现这种警告 Your Podfile has had smart quotes sanitised. To avoid issues in the future, you should not us ...

  4. CSS三种定位机制

    标准文档流 块级元素撑满整个页面div,ul,li,dl,dt,p 行级元素可以一行显示多个span,strong,img,input大部分 一般不设置盒子的高度,另其自动调整 margin属性的au ...

  5. python自定义函数大全

    写的零碎的python脚本太多了,到一定阶段就会出现一个问题,即以前写过的脚本找不到了,现在临时要用,还得再重写一遍,这就非常难受了,代码不能复用. 还好我有一个比较好的习惯,我喜欢把python脚本 ...

  6. Machine Learning - 第7周(Support Vector Machines)

    SVMs are considered by many to be the most powerful 'black box' learning algorithm, and by posing构建 ...

  7. 显示回收站.reg

    显示回收站.reg Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Software\Microsoft\Windows\Current ...

  8. python urllib2 模拟网站登陆

    python urllib2 模拟网站登陆 1. 可用浏览器先登陆,然后查看网页源码,分析登录表单 2. 使用python urllib2,cookielib 模拟网页登录 import urllib ...

  9. SAP 用事务码SQVI 做简单报表 .

    集团计划总监要去德国参展,要一份离当前日期最近的出口欧美国家产品单价. 需要从A903,MARA,KONP,MATK 这4张表里取数. 1)Tcode:SQVI进入,如图 2) 在快速浏览处输入名称: ...

  10. JavaScript window

    window -- window对象是BOM中所有对象的核心 window,中文"窗口" window对象除了是BOM中所有对象的父对象外,还包含一些窗口控制函数 全局的windo ...