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. python下载地址

    https://www.python.org/downloads/release/python-351/

  2. 使用fragment兼容低版本的写法

      [1]定义fragment继承V4包中的Fragment    [2]定义的activity要继承v4包中的FragmentActivity   [3]通过这个方法getSupportFragme ...

  3. iOS 开发之 Xcode6 创建真机调试证书

    http://jingyan.baidu.com/article/ff411625b8141312e48237a7.html 1.登录苹果开发者中心 2.登录后的界面如图所示,如果没有最上面的两个选项 ...

  4. java实现数据库连接池

    package com.kyo.connection; import java.sql.Connection; import java.sql.DatabaseMetaData; import jav ...

  5. Hbase之取出行数据指定部分(类似MySQL的Limit)

    import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.CellScanner; import org. ...

  6. 安卓虚拟机启动失败intel haxm未安装

    1:环境是android studio 在AVD中启动显示,提示当前电脑为安装HAXM emulator: ERROR: x86 emulation currently requires hardwa ...

  7. drupal 2016-11-3

    我随意定义了一个hook menu发现里面的内容很快就加入到了navigation menu里面.

  8. java 集合(Map3)

    Map接口下的实现类: HashMap 1.存储原理: 向HashMap中添加元素时,首先会调用hashCode(),算的哈希值,然后 算出该元素在哈希表中的存储位置. 情况1 情况2(java  集 ...

  9. java 对象 Serializable注意事项

    在序列化时,有几点要注意的: 1:当一个对象被序列化时,只保存对象的非静态成员变量,不能保存任何的成员方法和静态的成员变量. 2:如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存. ...

  10. React添加事件

    定义个组件 组件首字母大写,调用: ReactDOM.render(<Hello></Hello>,document.getElementById('box'));