2014-01-07 09:54:13 将百度空间里的东西移过来。
本文从点击“添加联系人”Button开始,分析新建联系人页面UI是如何加载,以及新的联系人信息是如何保存的,借此,我们一探Phonebook复杂的自定义View的加载机制。
1. 从前文分析我们知道,New Contact页面是随着帐号类型的不同,而显示不同的UI,这次我们以LocalAccountType为例来分析。
在联系人列表页面最下方,有一个“Add” Button, 点击新建联系人,这个Button其实一个MenuItem,在ContactsListFragment里面,点击事件处理在onOptionsItemSelected()方法,如下:
1 @Override 2 public boolean onOptionsItemSelected(final MenuItem aItem) { 3 case R.id.menu_add_contact: 4 startActivityForResult(new Intent(Intent.ACTION_INSERT, 5 Contacts.CONTENT_URI), 6 SUBACTIVITY_ADD_CONTACT); 7 break;
处理Intent.ACTION_INSERT这个Action的是AddNewContactActivity,如果是第一次添加联系人,那么会让用户选择需要添加的账户,下次添加时会使用第一次选择的账户作为默认账户,我们以默认账户为例:
1 startCreateContactActivity(mAccountUtils.getDefaultAccount()); 2 // mAccountUtils.getDefaultAccount()返回一个默认账户,我们假设 3 // 默认的账户是本地账户,也就是LocalAccountType。
startCreateContactActivity()方法如下:
1 private void startCreateContactActivity(AccountWithDataSet account) { 2 Intent intent = new Intent(this, ContactEditorActivity.class); 3 intent.setAction(ContactEditorActivity.ACTION_NEW_CONTACT); 4 5 if(mIntentExtras != null) { 6 intent.putExtras(mIntentExtras); 7 } 8 9 intent.putExtra(Intents.Insert.ACCOUNT, account); 10 startActivity(intent); 11 finish(); 12 }
启动ContactEditorActivity,Intent同时封装了account信息,用"Intents.Insert.ACCOUNT",也就是上面获得默认的本地联系人的帐号信息。下面我们进入ContactEditorActivity。
2. 向ContactEditorActivity出发
1 @Override 2 public void onCreate(Bundle savedState) { 3 super.onCreate(savedState); 4 setContentView(R.layout.contact_editor_activity); 5 6 ActionBar actionBar = getActionBar(); 7 if (actionBar != null) { 8 View saveMenuItem = customActionBarView.findViewById(R.id.save_menu_item); 9 saveMenuItem.setOnClickListener(new OnClickListener() { 10 @Override 11 public void onClick(View v) { 12 mFragment.doSaveAction(); 13 } 14 }); 15 } 16 17 mFragment = (ContactEditorFragment) getFragmentManager() 18 .findFragmentById(R.id.contact_editor_fragment); 19 mFragment.setListener(mFragmentListener); 20 Uri uri = Intent.ACTION_EDIT.equals(action) ? 21 getIntent().getData() : null; 22 mFragment.load(action, uri, getIntent().getExtras()); 23 }
在onCreate()方法中,使用的布局文件是contact_editor_activity.xml,同时为Save Contact MenuItem注册了监听事件:mFragment.doSaveAction()。如果是编辑联系人,那么会取出uri,并查询,然后将查询到的信息填到New ContactUI里面,不过我们不管新编辑联系人。看contact_editor_activity.xml:
1 <FrameLayout 2 android:layout_width="match_parent" 3 android:layout_height="match_parent"> 4 5 <fragment class="com.android.contacts.editor.ContactEditorFragment" 6 android:id="@+id/contact_editor_fragment" 7 android:layout_width="match_parent" 8 android:layout_height="match_parent" /> 9 </FrameLayout>
可以看到,ContactEditorActivity所有UI的显示和逻辑处理都在ContactEditorFragment,我们后续分析它。再看mFragment.load(action, uri, getIntent().getExtras());这行代码很重要,做了一些初始化的操作,同时将Intent中封装的account信息传给ContactEditorFragment。
3. ContactEditorFragment分析
进入ContactEditorFragment的onCreateView()方法:
1 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { 2 final View view = inflater.inflate(R.layout.contact_editor_fragment, container, false); 3 4 mContent = (LinearLayout)view.findViewById(R.id.editors); 5 mAccountTypeManager = AccountTypeManager.getInstance(mContext); 6 mInflater = (LayoutInflater)mContext.getSystemService( 7 Context.LAYOUT_INFLATER_SERVICE); 8 9 setHasOptionsMenu(true); 10 11 return view; 12 }
看contact_editor_fragment.xml-->contact_editor_fragment_container.xml:
1 <LinearLayout 2 android:id="@+id/editors" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 android:orientation="vertical" > 6 7 </LinearLayout>
最后我们发现整个ContactEditorFragment的根布局就是一个editors, 而且mContent = (LinearLayout)view.findViewById(R.id.editors),是一个LinearLayout。那么现在最关键的问题就是代码中mContent添加了那些东西,而这些动态添加的东西才是真正会显示的东西。接着往下看,onActivityCreated():
1 if (ContactEditorActivity.ACTION_NEW_CONTACT.equals(mAction)) { 2 AccountWithDataSet accountWithDataSet = mIntentExtras == null ? null : 3 (AccountWithDataSet) mIntentExtras.getParcelable(Intents.Insert.ACCOUNT); 4 if (accountWithDataSet != null && accountWithDataSet.type != null) { 5 createContact(accountWithDataSet); 6 }
上面代码中取出了传过来的account,并封装成一个accountWithDataSet对象,调用createContact()-->bindEditorsForNewContact():
1 private void bindEditorsForNewContact(AccountWithDataSet newAccount, 2 final AccountType newAccountType, RawContactDelta oldState, 3 AccountType oldAccountType) { 4 5 final RawContact rawContact = new RawContact(mContext); 6 if (newAccount != null) { 7 rawContact.setAccount(newAccount); 8 } else { 9 rawContact.setAccountToLocalContact(); 10 } 11 12 RawContactDelta insert = new RawContactDelta( 13 ValuesDelta.fromAfter(rawContact.getValues())); 14 15 if (mState == null) { 16 // Create state if none exists yet 17 mState = RawContactDeltaList.fromSingle(insert); 18 } else { 19 // Add contact onto end of existing state 20 mState.add(insert); 21 } 22 mRequestFocus = true; 23 24 bindEditors(); 25 }
上面的代码中首先用传进来的account创建了一个RawContact,然后构造了一个RawContactDelta对象insert,并调用mState.add(insert)。我们接着看bindEditors()方法:
1 RawContactDelta rawContactDelta = getFirstVisibleContact(); 2 if (rawContactDelta != null) { 3 editor = createContactEditorView(rawContactDelta); 4 } 5 mContent.addView(editor);
发现,mContent添加的竟然是一个editor,那么我们就看看这个editor到底是个什么东西,到底是怎么生成的。
4. createContactEditorView方法解析
在调用createContactEditorView()方法时,传入了一个参数,是一个RawContactDelta,看一下getFirstVisibleContact():
1 private RawContactDelta getFirstVisibleContact() { 2 for (final RawContactDelta rawContactDelta : mState) { 3 if (!rawContactDelta.isVisible()) continue; 4 return rawContactDelta; 5 } 6 return null; 7 }
发现rawContactDelta其实就是mState中第一个对象,也就是在bindEditorsForNewContact()方法中添加进去的insert。
我们看createContactEditorView()中构造editor的代码:
1 int layout = mIsLinkedContact ? 2 R.layout.raw_contact_editor_tab_view : 3 R.layout.raw_contact_editor_view; 4 editor = (BaseRawContactEditorView)mInflater.inflate(layout, null, false); 5 ... 6 editor.setEnabled(mEnabled); 7 editor.setState(rawContactDelta, type, mViewIdGenerator, isEditingUserProfile()); 8 ... 9 return editor;
先看raw_contact_editor_view.xml:
1 <com.android.contacts.editor.RawContactEditorView 2 xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 android:orientation="vertical" > 6 7 <LinearLayout 8 android:id="@+id/body" 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:orientation="vertical"> 12 13 <LinearLayout 14 android:background="@color/add_edit_header_background" 15 android:layout_width="match_parent" 16 android:layout_height="wrap_content" 17 android:orientation="horizontal" 18 android:paddingStart="@dimen/edit_contact_padding_start" 19 android:paddingTop="@dimen/raw_contact_edit_view_padding_top" 20 android:paddingBottom="@dimen/raw_contact_edit_view_padding_bottom"> 21 22 <include 23 android:id="@+id/edit_photo" 24 android:layout_width="wrap_content" 25 android:layout_height="wrap_content" 26 android:layout_marginEnd="@dimen/raw_contact_edit_photo_margin_end" 27 layout="@layout/item_photo_editor" /> 28 29 <LinearLayout 30 android:layout_width="match_parent" 31 android:layout_height="wrap_content" 32 android:orientation="vertical" 33 android:layout_gravity="bottom" > 34 35 <include 36 android:id="@+id/edit_name" 37 layout="@layout/structured_name_editor_view" /> 38 39 </LinearLayout> 40 41 </LinearLayout> 42 43 <include layout="@layout/editor_account_header_with_dropdown" /> 44 45 <include layout="@layout/raw_contact_editor_body" /> 46 47 </LinearLayout> 48 49 </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:
1 <merge > 2 <LinearLayout 3 android:id="@+id/sect_fields" 4 android:layout_width="match_parent" 5 android:layout_height="wrap_content" 6 android:layout_marginStart="@dimen/edit_contact_padding_start" 7 android:layout_marginTop="@dimen/raw_contact_sect_fields_margin_top" 8 android:layout_marginBottom="@dimen/raw_contact_sect_fields_margin_bottom" 9 android:orientation="vertical" /> 10 11 <Button 12 android:id="@+id/button_add_field" 13 android:layout_width="wrap_content" 14 android:layout_height="wrap_content" 15 android:layout_gravity="start" 16 android:layout_marginStart="@dimen/edit_contact_padding_start" 17 android:layout_marginBottom="@dimen/raw_contact_add_another_field_margin_bottom" 18 android:text="@string/add_field" /> 19 </merge>
其中button_add_field指的是“Add another field”Button,其余部分都包含在sect_fields里面。这个id的处理是在RawContactEditorView的父类RawContactCommonEditorView中,如下:
1 @Override 2 protected void onFinishInflate() { 3 super.onFinishInflate(); 4 5 mInflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 6 7 mName = (StructuredNameEditorView)findViewById(R.id.edit_name); 8 mName.setDeletable(false); 9 10 mFields = (ViewGroup)findViewById(R.id.sect_fields); 11 12 mAddFieldButton = (Button) findViewById(R.id.button_add_field); 13 mAddFieldButton.setOnClickListener(new OnClickListener() { 14 @Override 15 public void onClick(View v) { 16 showAddInformationPopupWindow(); 17 } 18 }); 19 }
接着看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方法,该方法有一个非常重要的循环体:
1 for (DataKind kind : type.getSortedDataKinds()) { 2 // Skip kind of not editable 3 if (!kind.editable) continue; 4 5 final String mimeType = kind.mimeType; 6 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) { 7 final ValuesDelta primary = state.getPrimaryEntry(mimeType); 8 mName.setValues(type.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE), 9 primary, state, false, vig); 10 } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) { 11 if (mGroupMembershipView != null) { 12 mGroupMembershipView.setState(state); 13 } 14 } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) { 15 } else { 16 if (kind.fieldList == null) continue; 17 final KindSectionView section = (KindSectionView)mInflater.inflate( 18 R.layout.item_kind_section, mFields, false); 19 section.setEnabled(isEnabled()); 20 section.setState(kind, state, false, vig); 21 mFields.addView(section); 22 } 23 }
我们好好分析一下这个循环体,因为这个循环体里面的内容根前文中提到的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,并将他们显示出来。