短信ui分析--会话列表

来源:互联网 发布:安卓淘宝客户端下载 编辑:程序博客网 时间:2024/06/12 22:03

1、前言

短信会话列表,对于一位使用android智能机的同胞来说,这个界面肯定不陌生。它就是我们进入短信应用看到的第一个界面,它也是短信UI中最重要的组成部分之一,它给用户提供了哪些功能,这里简单概括:
      、显示短信话,“会话”是什么含义了?简单的讲,张三给李四发短信,
             张三和李四之间的这种关系就是一个会话;
     二、新建一个会话;
      三、提供设置功能;
      四、提供关键字搜索短信的功能;
     、删除会话功能。
下图是基于android2.3的一个截图,4.0对于这个界面又些许改动,但改动不大只是将一些menu变成了actionbar而已。
 
图1
注意:下面的章节将会对每个功能进行解析,本文对于新建会话和设置功能不会做详细的讲解,在后面会用专门的文章来讲解这两个功能,这里重点讲解的功能是会话列表的实现原理、search的实现原理、删除会话这三个功能以及ui整体布局;

2、涉及的主要类和文件

[plain] view plaincopyprint?
  1. com.android.mms.ui.ConversationList 
  2. com.android.mms.ui.ConversationListItem 
  3. com.android.mms.ui.ConversationListItemData 
  4. com.android.mms.ui.ConversationListAdapter 
  5. res/layout/conversation_list_screen.xml 

ConversationList该类在Manifest.xml中的声明,该类用于呈现图1的界面,其他文件时辅助它完成这些功能。

[plain] view plaincopyprint?
  1. <activity android:name=".ui.ConversationList" 
  2.              android:label="@string/app_label" 
  3.              android:configChanges="orientation|keyboardHidden" 
  4.              android:launchMode="singleTop"> 
  5.        <intent-filter> 
  6.            <action android:name="android.intent.action.MAIN" /> 
  7.            <category android:name="android.intent.category.LAUNCHER" /> 
  8.            <category android:name="android.intent.category.DEFAULT" /> 
  9.        </intent-filter> 
  10.        <intent-filter> 
  11.            <action android:name="android.intent.action.MAIN" /> 
  12.            <category android:name="android.intent.category.DEFAULT" /> 
  13.            <data android:mimeType="vnd.android.cursor.dir/mms" /> 
  14.        </intent-filter> 
  15.        <intent-filter> 
  16.            <action android:name="android.intent.action.MAIN" /> 
  17.            <category android:name="android.intent.category.DEFAULT" /> 
  18.            <data android:mimeType="vnd.android-dir/mms-sms" /> 
  19.        </intent-filter> 
  20.    </activity> 

3、UI及功能实现

  从图1可以看出ui大致分为三块:一是新建会话;二是会话列表;三是menu。咱们都讲了这么多主角也该上场了,这里简单介绍ConversationList类,它是一个ListActivity,它是干什么的了,正如它的名字一样,用于显示会话列表,也即是实现了本文描述的一系列的功能,所有的ui都是在该类来呈现的,说的这就不得不提conversation_list_screen.xml该文件就是它的布局文件,有以下代码为证:
[plain] view plaincopyprint?
  1. @Override 
  2. protected void onCreate(Bundle savedInstanceState) { 
  3.     super.onCreate(savedInstanceState); 
  4.  
  5.     requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 
  6.     setContentView(R.layout.conversation_list_screen); 
下面就让我们先来看看这个布局文件吧。

3.1 conversation_list_screen.xml布局文件分析

  
[plain] view plaincopyprint?
  1. <LinearLayout 
  2.     xmlns:android="http://schemas.android.com/apk/res/android" 
  3.     android:layout_width="fill_parent" 
  4.     android:layout_height="fill_parent" 
  5.     android:background="@android:color/darker_gray" 
  6.     android:orientation="vertical"> 
  7.     <com.android.mms.ui.ConversationListItem 
  8.         xmlns:android="http://schemas.android.com/apk/res/android" 
  9.         android:id="@+id/creat_new_message" 
  10.         android:layout_width="match_parent" 
  11.         android:layout_height="?android:attr/listPreferredItemHeight" 
  12.         android:background="@drawable/conversation_item_background_unread" 
  13.         android:paddingRight="10dip" > 
  14.         <TextView 
  15.             android:text="@string/new_message" 
  16.             android:layout_width="wrap_content" 
  17.             android:layout_height="wrap_content" 
  18.             android:textAppearance="?android:attr/textAppearanceMediumInverse" 
  19.             android:singleLine="true" 
  20.             android:layout_marginTop="6dip" 
  21.             android:layout_marginRight="5dip" 
  22.             android:layout_marginLeft="7dip" 
  23.             android:layout_alignParentTop="true" 
  24.             android:layout_toRightOf="@id/avatar" 
  25.             android:layout_toLeftOf="@id/presence" 
  26.             android:layout_alignWithParentIfMissing="true" 
  27.             android:ellipsize="marquee"  /> 
  28.         <TextView 
  29.             android:text="@string/create_new_message" 
  30.             android:layout_width="wrap_content" 
  31.             android:layout_height="wrap_content" 
  32.             android:textAppearance="?android:attr/textAppearanceSmallInverse" 
  33.             android:singleLine="true" 
  34.             android:layout_marginBottom="10dip" 
  35.             android:layout_marginLeft="7dip" 
  36.             android:layout_alignParentBottom="true" 
  37.             android:layout_toRightOf="@id/avatar" 
  38.             android:layout_alignWithParentIfMissing="true" 
  39.             android:layout_toLeftOf="@id/date" 
  40.             android:ellipsize="end" /> 
  41.     </com.android.mms.ui.ConversationListItem> 
  42. <!---------备注-------------新建会话的ui,大致就可以看到图1所示的信息---------------> 
  43.     <ListView android:id="@android:id/list" xmlns:android="http://schemas.android.com/apk/res/android" 
  44.     style="?android:attr/listViewWhiteStyle" 
  45.     android:layout_width="match_parent" 
  46.     android:layout_height="match_parent" 
  47.     android:drawSelectorOnTop="false" 
  48.     android:scrollbarStyle="insideOverlay" 
  49.     android:background="@android:color/white" 
  50.     android:cacheColorHint="@android:color/white" 
  51.     android:fadingEdgeLength="16dip" /> 
  52. <!--------备注-------listview 用于显示从数据库读取的短信会话--重点在此----------------> 
  53. </LinearLayout> 
大家可以从布局文件中可以看到“新建会话“就是两个textview,但是在布局文件中确有一个ConversationListItem的类,大家肯定好奇了这个半路杀出来的程咬金是做什么的?以及显示会话的listview又是怎么填充数据的?大家先别急,且听下级慢慢道来。
     

3.2 新建会话


   上面的讨论留下了关于ConversationListItem类是用来做什么的悬疑,这里给大家揭开谜团。下面且看该类的一个继承关系。
     
[plain] view plaincopyprint?
  1. public class ConversationListItem extends RelativeLayout implements Contact.UpdateListener { 
  2.     private static final String TAG = "ConversationListItem"; 
  3.     private static final boolean DEBUG = false; 
      看到了吗?它竟然是一个RelativeLayout,这里大家肯定唏嘘不已,以为是一个神马了不起的杰作,终于知道为什么在布局文件中可以嵌套textview。那它在显示图1的“新建回话”这个ui仅仅是作为一个RelativeLayout当然正如其名字一样它还有一个用处就是定义了一个回话所包含的元素,这里暂时不多讲在后面会提到。
      新建回话的点击事件仍然是放在ConversationList类中,该类加载好布局后就给该ui设置了点击事件,请看下面的代码:
[html] view plaincopyprint?
  1. View new_message =findViewById(R.id.creat_new_message); 
  2. new_message.setOnClickListener(new View.OnClickListener () { 
  3.         public void onClick(View v) { 
  4.             createNewMessage(); 
  5.         } 
  6.     }); 

createNewMessage()方法大家可能已经猜到
[html] view plaincopyprint?
  1. private void createNewMessage() { 
  2.     startActivity(ComposeMessageActivity.createIntent(this, 0)); 
就是跳转到新建会话界面,这里对于该界面不做分析后面会专门分析该界面的一些功能。

3.3 会话列表

      会话列表是该界面最重要的功能,用于显示用户所有会话,如图1会话列表部分。在布局文件中定义了一个listview来显示这些会话列表,这里有三个问题有助于我们解读这部分代码:1)数据的填充;2)一个子项的数据怎么定义的?3)数据发生变化怎么更新ui?
     这里大家不要心急以一个一个解决。

3.3.1 数据的填充   

首先来看看咱们listview是怎么填充数据的,这部分定义仍然在ConversationList,前面有提到过该类的重要性,后面会反复提到,希望大家重视。其相关代码如下所示

[html] view plaincopyprint?
  1. ListView listView =getListView(); 
  2.         listView.setOnCreateContextMenuListener(mConvListOnCreateContextMenuListener); 
  3.         listView.setOnKeyListener(mThreadListKeyListener); 
  4.         initListAdapter(); 
  5.         <!---对应的方法---> 
  6. private void initListAdapter() { 
  7.         mListAdapter =new ConversationListAdapter(this, null); 
  8.         mListAdapter.setOnContentChangedListener(mContentChangedListener); 
  9.         setListAdapter(mListAdapter); 
  10.         getListView().setRecyclerListener(mListAdapter); 
  11.     } 
  12. <span style="font-family: Arial; background-color: rgb(255, 255, 255);"></span> 

大家可以从填充数据来看Mms自定义了一个adapter,这和传统大家在使用listview填充数据时有一些区别,一般我们都是使用系统自带的ArrayAdapter、CursorAdapter之类的来实现,但google的开发人员并不认为这是一种好的方式,自定义使其操作更方便,功能更丰富。废话少说我们来看看ConversationListAdapter的高明之处;下面了将几个重要的方法摘录下来:

[html] view plaincopyprint?
  1. public class ConversationListAdapter extends CursorAdapter implements AbsListView.RecyclerListener { 
  2.       private final LayoutInflater mFactory; 
  3.     private OnContentChangedListener mOnContentChangedListener; 
  4.  
  5.     public ConversationListAdapter(Context context, Cursor cursor) { 
  6.         super(context, cursor, false /* auto-requery */); 
  7.         mFactory = LayoutInflater.from(context); 
  8.     } 
  9.     public void bindView(View view, Context context, Cursor cursor) { 
  10.         if (!(view instanceof ConversationListItem)) { 
  11.             Log.e(TAG, "Unexpected bound view: " + view); 
  12.             return; 
  13.         } 
  14.         ConversationListItem headerView = (ConversationListItem) view; 
  15.         Conversation conv =Conversation.from(context, cursor); 
  16.         ConversationListItemData ch =new ConversationListItemData(context, conv); 
  17.         headerView.bind(context, ch); 
  18.     } 
  19.     public View newView(Context context, Cursor cursor, ViewGroup parent) { 
  20.         if (LOCAL_LOGV) Log.v(TAG, "inflating new view"); 
  21.         return mFactory.inflate(R.layout.conversation_list_item, parent, false); 
  22.     } 
  23.     public interface OnContentChangedListener { 
  24.         void onContentChanged(ConversationListAdapter adapter); 
  25.     } 
  26.     public void setOnContentChangedListener(OnContentChangedListener l) { 
  27.         mOnContentChangedListener =l
  28.     }  protected void onContentChanged() { 
  29.         if (mCursor != null && !mCursor.isClosed()) { 
  30.             if (mOnContentChangedListener != null) { 
  31.                 mOnContentChangedListener.onContentChanged(this); 
  32.             } 
  33.         } 
  34.     } 


      上述newView在adapter第一次调用时执行,将对应的布局文件加载进来并创建子项view(ConversationListItem),而bindView将cursor中的数据绑定到ConversationListItem ;onContentChanged则是在数据发生变化时重新查询,以达到刷新界面的目的。数据的填充大致就是这样,下面我们来看一个会话具体有哪些数据,以及它的布局。

3.3.2 子项数据定义

      从上面数据绑定来看用于显示一个会话的布局文件是conversation_list_item.xml,我们不妨探秘一下google开发者定义一个会话包含了那些数据和ui控件。为节约篇幅我就在代码上对每个属性的含义做解释

[html] view plaincopyprint?
  1. <com.android.mms.ui.ConversationListItemxmlns:android="http://schemas.android.com/apk/res/android" 
  2.     android:layout_width="match_parent" 
  3.     android:layout_height="?android:attr/listPreferredItemHeight" 
  4.     android:background="@drawable/conversation_item_background_unread" 
  5.     android:paddingRight="10dip"> 
  6.     <android.widget.QuickContactBadge 
  7.         android:id="@+id/avatar"  该属性为widget,也即是每个联系都可以有一个快捷方式之类的widget 
  8.         android:visibility="gone" 
  9.         android:layout_marginLeft="7dip" 
  10.         android:layout_centerVertical="true" 
  11.         style="?android:attr/quickContactBadgeStyleWindowSmall"/> 
  12.     <ImageView 
  13.         android:id="@+id/presence" 
  14.         android:visibility="gone"  用于显示联系人的图标 
  15.         android:layout_width="wrap_content" 
  16.         android:layout_height="wrap_content" 
  17.         android:layout_marginRight="5dip" 
  18.         android:layout_alignParentRight="true" 
  19.         android:layout_centerVertical="true" 
  20.         android:paddingBottom="20dip" 
  21.          /> 
  22.     <TextViewandroid:id="@+id/from" 发送者姓名,如果联系人中没有该联系人将显示号码   
  23.         android:layout_width="wrap_content" 
  24.         android:layout_height="wrap_content" 
  25.         android:textAppearance="?android:attr/textAppearanceMediumInverse" 
  26.         android:singleLine="true" 
  27.         android:layout_marginTop="6dip" 
  28.         android:layout_marginRight="5dip" 
  29.         android:layout_marginLeft="7dip" 
  30.         android:layout_alignParentTop="true" 
  31.         android:layout_toRightOf="@id/avatar" 
  32.         android:layout_toLeftOf="@id/presence" 
  33.         android:layout_alignWithParentIfMissing="true" 
  34.         android:ellipsize="marquee" /> 
  35.     <TextViewandroid:id="@+id/date"接收到短信的日期 
  36.         android:layout_marginTop="2dip" 
  37.         android:layout_marginBottom="10dip" 
  38.         android:layout_marginLeft="5dip" 
  39.         android:layout_height="wrap_content" 
  40.         android:layout_width="wrap_content" 
  41.         android:textAppearance="?android:attr/textAppearanceSmallInverse" 
  42.         android:singleLine="true" 
  43.         android:layout_alignParentRight="true" 
  44.         android:layout_alignParentBottom="true"/> 
  45.     <ImageViewandroid:id="@+id/error"  如果发送失败会有一个红的感叹号提示,以及错误提示 
  46.         android:layout_marginLeft="3dip" 
  47.         android:visibility="invisible" 
  48.         android:layout_toLeftOf="@id/date" 
  49.         android:layout_alignBottom="@id/date" 
  50.         android:layout_height="wrap_content" 
  51.         android:layout_width="wrap_content" 
  52.         android:src="@drawable/ic_list_alert_sms_failed"/> 
  53.     <ImageViewandroid:id="@+id/attachment"如果是彩信,有附件的情况将会显示该图标 
  54.         android:layout_marginLeft="3dip" 
  55.         android:layout_height="wrap_content" 
  56.         android:layout_width="wrap_content" 
  57.         android:visibility="gone" 
  58.         android:layout_toLeftOf="@id/error" 
  59.         android:layout_alignBottom="@id/date" 
  60.         android:src="@drawable/ic_attachment_universal_small"/> 
  61.     <TextViewandroid:id="@+id/subject" 彩信主题 
  62.         android:layout_width="wrap_content" 
  63.         android:layout_height="wrap_content" 
  64.         android:textAppearance="?android:attr/textAppearanceSmallInverse" 
  65.         android:singleLine="true" 
  66.         android:layout_marginBottom="10dip" 
  67.         android:layout_marginLeft="7dip" 
  68.         android:layout_alignParentBottom="true" 
  69.         android:layout_toRightOf="@id/avatar" 
  70.         android:layout_alignWithParentIfMissing="true" 
  71.         android:layout_toLeftOf="@id/date" 
  72.         android:ellipsize="end"/> 
  73. </com.android.mms.ui.ConversationListItem> 

那大致定义了上述的控件,这些控件对应的数据怎么绑定上的??这得从自定义的adapter的bindView方法说起,大家可以回过来头来看看bindView方法,这里再给大家复习一下:

[html] view plaincopyprint?
  1. ConversationListItem headerView = (ConversationListItem) view; 
  2.        Conversation conv = Conversation.from(context, cursor); 
  3.        ConversationListItemData ch =new ConversationListItemData(context, conv); 
  4.        headerView.bind(context, ch); 

将得到的数据cursor转换成Conversation对象传递到ConversationListItemData,这里了google工程师将对应的数据抽象之后专门使用类来保存,这里值得称道

     那我们不妨从ConversationListItemData来看看一个会话包含哪些数据

[html] view plaincopyprint?
  1. private long mThreadId;会话的id 
  2. private String mSubject;主题 
  3. private String mDate;时间,这里指的是接收 
  4. private boolean mHasAttachment;是否有附件 
  5. private boolean mIsRead;是否是读过了 
  6. private boolean mHasError;是否有错 
  7. private boolean mHasDraft;是否有草稿 
  8. private int mMessageCount;短信数量 
  9. // The recipients in this conversation 
  10. private ContactList mRecipients;联系人 
  11. private String mRecipientString;联系人 
  12. // the presence icon resource id displayed for the conversation thread. 
  13. private int mPresenceResId;图标的id 

  然后将对应的数据设置到对应的ui上,headerView.bind(context, ch);

[html] view plaincopyprint?
  1. public final void bind(Context context, final ConversationListItemData ch) { 
  2.     //if (DEBUG) Log.v(TAG, "bind()"); 
  3.  
  4.     setConversationHeader(ch); 
  5.  
  6.     Drawable background = ch.isRead()? 
  7.             mContext.getResources().getDrawable(R.drawable.conversation_item_background_read) : 
  8.             mContext.getResources().getDrawable(R.drawable.conversation_item_background_unread); 
  9.  
  10.     setBackgroundDrawable(background); 
  11.  
  12.     LayoutParams attachmentLayout = (LayoutParams)mAttachmentView.getLayoutParams(); 
  13.     boolean hasError =ch.hasError(); 
  14.     // When there's an error icon, the attachment icon is left of the error icon. 
  15.     // When there is not an error icon, the attachment icon is left of the date text. 
  16.     // As far as I know, there's no way to specify that relationship in xml. 
  17.     if (hasError) { 
  18.         attachmentLayout.addRule(RelativeLayout.LEFT_OF, R.id.error); 
  19.     } else { 
  20.         attachmentLayout.addRule(RelativeLayout.LEFT_OF, R.id.date); 
  21.     } 
  22.  
  23.     boolean hasAttachment =ch.hasAttachment(); 
  24.     mAttachmentView.setVisibility(hasAttachment ? VISIBLE : GONE); 
  25.  
  26.     // Date 
  27.     mDateView.setText(ch.getDate()); 
  28.  
  29.     // From. 
  30.     mFromView.setText(formatMessage(ch)); 
  31.  
  32.     // Register for updates in changes of any of the contacts in this conversation. 
  33.     ContactList contacts =ch.getContacts(); 
  34.  
  35.     if (DEBUG) Log.v(TAG, "bind: contacts.addListeners " + this); 
  36.     Contact.addListener(this); 
  37.     setPresenceIcon(contacts.getPresenceResId()); 
  38.  
  39.     // Subject 
  40.     mSubjectView.setText(ch.getSubject()); 
  41.     LayoutParams subjectLayout = (LayoutParams)mSubjectView.getLayoutParams(); 
  42.     // We have to make the subject left of whatever optional items are shown on the right. 
  43.     subjectLayout.addRule(RelativeLayout.LEFT_OF, hasAttachment ? R.id.attachment : 
  44.         (hasError ? R.id.error : R.id.date)); 
  45.  
  46.     // Transmission error indicator. 
  47.     mErrorIndicator.setVisibility(hasError ? VISIBLE : GONE); 
  48.  
  49.     updateAvatarView(); 
这里已经完成了数据到ui的具体显示,这些无外乎就是一些控件值得设置,不用我啰嗦了。到此为止ui上怎么显示的基本上走完了。还剩下最重要的问题,笔者说了这么多没有去谈及到这些数据从何而来。

3.3.3  数据的查询和更新

     走到这一步,大家终于不用管那些界面了,咱们来关心一下短信会话数据时从那来的。在ConversationList类的onCreate方法里有一些重要提示。

[html] view plaincopyprint?
  1. mQueryHandler = new ThreadListQueryHandler(getContentResolver()); 
  2. this.getContentResolver().registerContentObserver(Contacts.CONTENT_URI, true, observer); 

当数据的内容发生变法就会调用observer的onContentChange方法

[html] view plaincopyprint?
  1. private ContentObserver observer =new ContentObserver(new Handler()){ 
  2.  
  3.     public void onChange(boolean selfChange) { 
  4.         startAsyncQuery(); 
  5.         if (!Conversation.loadingThreads()) { 
  6.             Contact.invalidateCache(); 
  7.         } 
  8.     } 
  9. }; 

另外在初始化adapter时设置了一个内容的监听器;

[html] view plaincopyprint?
  1. mListAdapter = new ConversationListAdapter(this, null); 
  2.        mListAdapter.setOnContentChangedListener(mContentChangedListener); 

  看到这大家可能说这些在我开机起来没有一个触发条件啊,不要心急,紧接着走到ConversationList的onStart()方法来了,盖房调用了startAsyncQuery()方法,该方法我们虽然没看代码我们都应该猜测到它是干神马的,大家应该会欣喜若狂了吧。好吧请看下面答案揭晓答案:

[html] view plaincopyprint?
  1. private void startAsyncQuery() { 
  2.     try { 
  3.         setTitle(getString(R.string.refreshing)); 
  4.         setProgressBarIndeterminateVisibility(true); 
  5.  
  6.         Conversation.startQueryForAll(mQueryHandler, THREAD_LIST_QUERY_TOKEN); 
  7.     } catch (SQLiteException e) { 
  8.         SqliteWrapper.checkSQLiteException(this, e); 
  9.     } 

这里继续走下去

[html] view plaincopyprint?
  1. public static void startQueryForAll(AsyncQueryHandler handler, int token) { 
  2.     handler.cancelOperation(token); 
  3.     handler.startQuery(token, null, sAllThreadsUri, 
  4.             ALL_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER); 

最后会走到ThreadListQueryHandler的onQueryComplete方法中,至于怎么走到的稍微看一下就明白了,这里就不提及了,那就来看看onQueryComplete方法:

[html] view plaincopyprint?
  1. protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 
  2.     switch (token) { 
  3.     case THREAD_LIST_QUERY_TOKEN: 
  4.         mListAdapter.changeCursor(cursor); 
  5.         setTitle(mTitle); 
  6.         setProgressBarIndeterminateVisibility(false); 
  7.         if (mNeedToMarkAsSeen) { 
  8.             mNeedToMarkAsSeen = false
  9.             Conversation.markAllConversationsAsSeen(getApplicationContext()); 
  10.             // Database will be update at this time in some conditions. 
  11.             // Wait 1s and ensure update complete. 
  12.             mQueryHandler.postDelayed(new Runnable() { 
  13.                 public void run() { 
  14.                     // Delete any obsolete threads. Obsolete threads are threads that aren't 
  15.                     // referenced by at least one message in the pdu or sms tables. 
  16.                     Conversation.asyncDeleteObsoleteThreads(mQueryHandler, 
  17.                             DELETE_OBSOLETE_THREADS_TOKEN); 
  18.                 } 
  19.             }, 1000); 
  20.         } 
  21.         break; 

这里大家可以看到mListAdapter.changeCursor(cursor);调用该方法,adapter重新绑定数据到ui完成界面刷新。那如果数据发生变化上面有提到的一个就是监听了数据库,一旦发生变化就会重新查询刷新ui。这也是为啥最早提及监听数据库和adapter添加内容监听器的原因。

3.3.4  listview item的单击事件处理

上面大费周章的续写会话列表怎么显示的,数据从何而来,ui怎么绑定,数据怎么更新。现在了基于上面显示的会话列表,来看看系统是怎么处理具体的某一个会话的点击事件。
[plain] view plaincopyprint?
  1. @Override 
  2. protected void onListItemClick(ListView l, View v, int position, long id) { 
  3.         Cursor cursor  = (Cursor) getListView().getItemAtPosition(position); 
  4.         Conversation conv = Conversation.from(this, cursor); 
  5.         long tid = conv.getThreadId(); 
  6.  
  7.         if (LogTag.VERBOSE) { 
  8.             Log.d(TAG, "onListItemClick: pos=" + position + ", view=" + v + ", tid=" + tid); 
  9.         } 
  10.         openThread(tid); 
点击子项所做的工作就是打开这样的一个会话,继续看看openTread是干嘛的
[plain] view plaincopyprint?
  1. private void openThread(long threadId) { 
  2.       startActivity(ComposeMessageActivity.createIntent(this, threadId)); 
  3.   } 
看到这大家应该清楚了,打开某个会话,仍然是在ComposeMessageActivity该类来实现的,这个类不但实现了会话的加载,新建会话也是在该类来实现的,所以了后面会专门来探讨该类是怎么实现这些功能的。

3.3.5 listview item的长按事件处理

对于item的长按事件处理,2.3与4.0有一些区别,4.0仅提供了删除该会话的功能,那2.3提供了查看会话、删除会话、如果联系人没有在通讯录中还有一个“添加到联系人”的功能、如果联系人在通讯录中存在提供了一个“查看联系”的功能。下面是2.3的处理代码:
[plain] view plaincopyprint?
  1. private final OnCreateContextMenuListener mConvListOnCreateContextMenuListener = 
  2.     new OnCreateContextMenuListener() { 
  3.     public void onCreateContextMenu(ContextMenu menu, View v, 
  4.             ContextMenuInfo menuInfo) { 
  5.         Cursor cursor = mListAdapter.getCursor(); 
  6.         if (cursor == null || cursor.getPosition() < 0) { 
  7.             return; 
  8.         } 
  9.         Conversation conv = Conversation.from(ConversationList.this, cursor); 
  10.         ContactList recipients = conv.getRecipients(); 
  11.         menu.setHeaderTitle(recipients.formatNames(",")); 
  12.         menu.add(0, MENU_VIEW, 0, R.string.menu_view); 
  13.         // Only show if there's a single recipient 
  14.         if (recipients.size() == 1) { 
  15.             // do we have this recipient in contacts? 
  16.             if (recipients.get(0).existsInDatabase()) { 
  17.                 menu.add(0, MENU_VIEW_CONTACT, 0, R.string.menu_view_contact); 
  18.             } else { 
  19.                 menu.add(0, MENU_ADD_TO_CONTACTS, 0, R.string.menu_add_to_contacts); 
  20.             } 
  21.         } 
  22.         menu.add(0, MENU_DELETE, 0, R.string.menu_delete); 
  23.     } 
  24. }; 
  25. @Override 
  26. public boolean onContextItemSelected(MenuItem item) { 
  27.     Cursor cursor = mListAdapter.getCursor(); 
  28.     if (cursor != null && cursor.getPosition() >= 0) { 
  29.         Conversation conv = Conversation.from(ConversationList.this, cursor); 
  30.         long threadId = conv.getThreadId(); 
  31.         switch (item.getItemId()) { 
  32.         case MENU_DELETE: { 
  33.             confirmDeleteThread(threadId, mQueryHandler); 
  34.             break; 
  35.         } 
  36.         case MENU_VIEW: { 
  37.             openThread(threadId); 
  38.             break; 
  39.         } 
  40.         case MENU_VIEW_CONTACT: { 
  41.             Contact contact = conv.getRecipients().get(0); 
  42.             Intent intent = new Intent(Intent.ACTION_VIEW, contact.getUri()); 
  43.             intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 
  44.             startActivity(intent); 
  45.             break; 
  46.         } 
  47.         case MENU_ADD_TO_CONTACTS: { 
  48.             String address = conv.getRecipients().get(0).getNumber(); 
  49.             startActivity(createAddContactIntent(address)); 
  50.             break; 
  51.         } 
  52.         default: 
  53.             break; 
  54.         } 
  55.     } 
  56.     return super.onContextItemSelected(item); 
上面这些代码没有太多技术上的难点这里了就不做太多的深入分析。

3.4、menu

       终于走到了menu,menu的实现没有上面那样复杂冗长,但并不是说menu就不重要了,menu是在对上面的功能的补充,给用户提供更多的交互窗口。来看看我们的menu在ConversationList中怎么来定义,以及他们的点击事件怎么处理的。
[plain] view plaincopyprint?
  1. @Override 
  2. public boolean onPrepareOptionsMenu(Menu menu) { 
  3.     menu.clear(); 
  4.     menu.add(0, MENU_COMPOSE_NEW, 0, R.string.menu_compose_new).setIcon( 
  5.             com.android.internal.R.drawable.ic_menu_compose); 
  6.     if (mListAdapter.getCount() > 0) { 
  7.         menu.add(0, MENU_DELETE_ALL, 0, R.string.menu_delete_all).setIcon( 
  8.                 android.R.drawable.ic_menu_delete); 
  9.     } 
  10.     menu.add(0, MENU_SEARCH, 0, android.R.string.search_go). 
  11.         setIcon(android.R.drawable.ic_menu_search). 
  12.         setAlphabeticShortcut(android.app.SearchManager.MENU_KEY); 
  13.     menu.add(0, MENU_PREFERENCES, 0, R.string.menu_preferences).setIcon( 
  14.             android.R.drawable.ic_menu_preferences); 
  15.  
  16.     return true; 
对应的点击事件处理
[plain] view plaincopyprint?
  1. @Override 
  2.     public boolean onOptionsItemSelected(MenuItem item) { 
  3.         switch(item.getItemId()) { 
  4.             case MENU_COMPOSE_NEW: 
  5.                 createNewMessage(); 
  6.                 break; 
  7.             case MENU_SEARCH: 
  8.                 onSearchRequested(); 
  9.                 break; 
  10.             case MENU_DELETE_ALL: 
  11.                 // The invalid threadId of -1 means all threads here. 
  12.                 confirmDeleteThread(-1L, mQueryHandler); 
  13.                 break; 
  14.             case MENU_PREFERENCES: { 
  15.                 Intent intent = new Intent(this, MessagingPreferenceActivity.class); 
  16.                 startActivityIfNeeded(intent, -1); 
  17.                 break; 
  18.             } 
  19.             default: 
  20.                 return true; 
  21.         } 
  22.         return false; 
  23.     } 
1)首先,这里的compose也即是新建会话的意思所以了和上面的新建会话功能一样,这里不重复赘叙;其次,这里的删除会话和上面长按menu里的删除会话不一样,在于这里是删除全部的会话,而上面是删除某一个会话,但实现方式差不多。首先调用confirmDeleteThread方法,去查询该会话相关的数据,弄到这大家可能蒙了我删就删呗,怎么还要去查询了,大家从传递的参数大致可以看到,实际上当前会话的id我们是得到了但对于该会对应的相关数据我们并没有直接拿到。
[plain] view plaincopyprint?
  1. public static void confirmDeleteThread(long threadId, AsyncQueryHandler handler) { 
  2.     Conversation.startQueryHaveLockedMessages(handler, threadId, 
  3.             HAVE_LOCKED_MESSAGES_TOKEN); 
这里是一个异步查询的方式,查询完后会回调对应queryHandler的onQueryComplete方法的核心调用
[plain] view plaincopyprint?
  1. case HAVE_LOCKED_MESSAGES_TOKEN: 
  2.       long threadId = (Long)cookie; 
  3.       confirmDeleteThreadDialog(new DeleteThreadListener(threadId, mQueryHandler, 
  4.               ConversationList.this), threadId == -1, 
  5.               cursor != null && cursor.getCount() > 0, 
  6.               ConversationList.this); 
  7.       break; 
这里是弹出对话框,不难看出,那点击确认删除的动作就放在对应的listener中
[plain] view plaincopyprint?
  1. public void onClick(DialogInterface dialog, final int whichButton) { 
  2.     MessageUtils.handleReadReport(mContext, mThreadId, 
  3.             PduHeaders.READ_STATUS__DELETED_WITHOUT_BEING_READ, new Runnable() { 
  4.         public void run() { 
  5.             int token = DELETE_CONVERSATION_TOKEN; 
  6.             if (mThreadId == -1) { 
  7.                 Conversation.startDeleteAll(mHandler, token, mDeleteLockedMessages); 
  8.                 DraftCache.getInstance().refresh(); 
  9.             } else { 
  10.                 Conversation.startDelete(mHandler, token, mDeleteLockedMessages, 
  11.                         mThreadId); 
  12.                 DraftCache.getInstance().setDraftState(mThreadId, false); 
  13.             } 
  14.         } 
  15.     }); 
  16.     dialog.dismiss(); 
根据传进来的值来判断当前是删除全部还是当前的某个会话。

2)另外就是设置,那本文开始处以明确说明会在后文中来讲解它,但这里可以看到的是设置界面是MessagingPreferenceActivity。
      那menu还剩下一个搜索,这里对这个搜索功能做一个具体的分析。

3.5、 搜索功能


   首先来看看搜索功能的调用从上面对事件的处理来看就调用了onSearchRequested()方法
  
[plain] view plaincopyprint?
  1. @Override 
  2. public boolean onSearchRequested() { 
  3.     startSearch(null, false, null /*appData*/, false); 
  4.     return true; 
startSearch方法走到这就走不动了,下面了该方法就调用到google search去了。所以暂时就调到这了

4、总结

   上述大致将会话列表的功能做了一个简单的分析,这里仅仅是抛砖引玉,希望和大家一起来研究学习,如若有什么需要补充的大家留言,我这后续跟上,路漫漫其修远兮,吾将上下而求索。
原创粉丝点击