zoukankan      html  css  js  c++  java
  • Android Phonebook编写联系人UI加载及联系人保存流程(三)

    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,并将他们显示出来。

  • 相关阅读:
    linux下常见的网络相关参数简介
    nginx编译安装
    mysql主从同步报错Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs; these UUIDs must be different for replication to work.
    mysql报错Do you already have another mysqld server running on socket
    php编译安装
    APP测试の: MonKeyRunner___录制与回放
    Python生成指定容量文本文档
    django 误人子弟快速上手
    curl 使用方法
    APP 自动化框架实现结构图
  • 原文地址:https://www.cnblogs.com/wlrhnh/p/3508312.html
Copyright © 2011-2022 走看看