1、前言
2、涉及的主要类和文件
- com.android.mms.ui.ConversationList
- com.android.mms.ui.ConversationListItem
- com.android.mms.ui.ConversationListItemData
- com.android.mms.ui.ConversationListAdapter
- res/layout/conversation_list_screen.xml
com.android.mms.ui.ConversationList
com.android.mms.ui.ConversationListItem
com.android.mms.ui.ConversationListItemData
com.android.mms.ui.ConversationListAdapter
res/layout/conversation_list_screen.xml
ConversationList该类在Manifest.xml中的声明,该类用于呈现图1的界面,其他文件时辅助它完成这些功能。
- <activity android:name=".ui.ConversationList"
- android:label="@string/app_label"
- android:configChanges="orientation|keyboardHidden"
- android:launchMode="singleTop">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.dir/mms" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android-dir/mms-sms" />
- </intent-filter>
- </activity>
<activity android:name=".ui.ConversationList"
android:label="@string/app_label"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.dir/mms" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android-dir/mms-sms" />
</intent-filter>
</activity>
3、UI及功能实现
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
- setContentView(R.layout.conversation_list_screen);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.conversation_list_screen);
下面就让我们先来看看这个布局文件吧。
3.1 conversation_list_screen.xml布局文件分析
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="@android:color/darker_gray"
- android:orientation="vertical">
- <com.android.mms.ui.ConversationListItem
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/creat_new_message"
- android:layout_width="match_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:background="@drawable/conversation_item_background_unread"
- android:paddingRight="10dip" >
- <TextView
- android:text="@string/new_message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMediumInverse"
- android:singleLine="true"
- android:layout_marginTop="6dip"
- android:layout_marginRight="5dip"
- android:layout_marginLeft="7dip"
- android:layout_alignParentTop="true"
- android:layout_toRightOf="@id/avatar"
- android:layout_toLeftOf="@id/presence"
- android:layout_alignWithParentIfMissing="true"
- android:ellipsize="marquee" />
- <TextView
- android:text="@string/create_new_message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmallInverse"
- android:singleLine="true"
- android:layout_marginBottom="10dip"
- android:layout_marginLeft="7dip"
- android:layout_alignParentBottom="true"
- android:layout_toRightOf="@id/avatar"
- android:layout_alignWithParentIfMissing="true"
- android:layout_toLeftOf="@id/date"
- android:ellipsize="end" />
- </com.android.mms.ui.ConversationListItem>
- <!---------备注-------------新建会话的ui,大致就可以看到图1所示的信息--------------->
- <ListView android:id="@android:id/list" xmlns:android="http://schemas.android.com/apk/res/android"
- style="?android:attr/listViewWhiteStyle"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:drawSelectorOnTop="false"
- android:scrollbarStyle="insideOverlay"
- android:background="@android:color/white"
- android:cacheColorHint="@android:color/white"
- android:fadingEdgeLength="16dip" />
- <!--------备注-------listview 用于显示从数据库读取的短信会话--重点在此---------------->
- </LinearLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/darker_gray"
android:orientation="vertical">
<com.android.mms.ui.ConversationListItem
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/creat_new_message"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="@drawable/conversation_item_background_unread"
android:paddingRight="10dip" >
<TextView
android:text="@string/new_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMediumInverse"
android:singleLine="true"
android:layout_marginTop="6dip"
android:layout_marginRight="5dip"
android:layout_marginLeft="7dip"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/avatar"
android:layout_toLeftOf="@id/presence"
android:layout_alignWithParentIfMissing="true"
android:ellipsize="marquee" />
<TextView
android:text="@string/create_new_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmallInverse"
android:singleLine="true"
android:layout_marginBottom="10dip"
android:layout_marginLeft="7dip"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@id/avatar"
android:layout_alignWithParentIfMissing="true"
android:layout_toLeftOf="@id/date"
android:ellipsize="end" />
</com.android.mms.ui.ConversationListItem>
<!---------备注-------------新建会话的ui,大致就可以看到图1所示的信息--------------->
<ListView android:id="@android:id/list" xmlns:android="http://schemas.android.com/apk/res/android"
style="?android:attr/listViewWhiteStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawSelectorOnTop="false"
android:scrollbarStyle="insideOverlay"
android:background="@android:color/white"
android:cacheColorHint="@android:color/white"
android:fadingEdgeLength="16dip" />
<!--------备注-------listview 用于显示从数据库读取的短信会话--重点在此---------------->
</LinearLayout>
3.2 新建会话
- public class ConversationListItem extends RelativeLayout implements Contact.UpdateListener {
- private static final String TAG = "ConversationListItem";
- private static final boolean DEBUG = false;
public class ConversationListItem extends RelativeLayout implements Contact.UpdateListener {
private static final String TAG = "ConversationListItem";
private static final boolean DEBUG = false;
- View new_message = findViewById(R.id.creat_new_message);
- new_message.setOnClickListener(new View.OnClickListener () {
- public void onClick(View v) {
- createNewMessage();
- }
- });
View new_message = findViewById(R.id.creat_new_message);
new_message.setOnClickListener(new View.OnClickListener () {
public void onClick(View v) {
createNewMessage();
}
});
createNewMessage()方法大家可能已经猜到
- private void createNewMessage() {
- startActivity(ComposeMessageActivity.createIntent(this, 0));
- }
private void createNewMessage() {
startActivity(ComposeMessageActivity.createIntent(this, 0));
}
就是跳转到新建会话界面,这里对于该界面不做分析后面会专门分析该界面的一些功能。3.3 会话列表
3.3.1 数据的填充
首先来看看咱们listview是怎么填充数据的,这部分定义仍然在ConversationList,前面有提到过该类的重要性,后面会反复提到,希望大家重视。其相关代码如下所示
- ListView listView = getListView();
- listView.setOnCreateContextMenuListener(mConvListOnCreateContextMenuListener);
- listView.setOnKeyListener(mThreadListKeyListener);
- initListAdapter();
- <!---对应的方法--->
- private void initListAdapter() {
- mListAdapter = new ConversationListAdapter(this, null);
- mListAdapter.setOnContentChangedListener(mContentChangedListener);
- setListAdapter(mListAdapter);
- getListView().setRecyclerListener(mListAdapter);
- }
- <span style="font-family: Arial; background-color: rgb(255, 255, 255);"></span>
ListView listView = getListView();
listView.setOnCreateContextMenuListener(mConvListOnCreateContextMenuListener);
listView.setOnKeyListener(mThreadListKeyListener);
initListAdapter();
<!---对应的方法--->
private void initListAdapter() {
mListAdapter = new ConversationListAdapter(this, null);
mListAdapter.setOnContentChangedListener(mContentChangedListener);
setListAdapter(mListAdapter);
getListView().setRecyclerListener(mListAdapter);
}
大家可以从填充数据来看Mms自定义了一个adapter,这和传统大家在使用listview填充数据时有一些区别,一般我们都是使用系统自带的ArrayAdapter、CursorAdapter之类的来实现,但google的开发人员并不认为这是一种好的方式,自定义使其操作更方便,功能更丰富。废话少说我们来看看ConversationListAdapter的高明之处;下面了将几个重要的方法摘录下来:
- public class ConversationListAdapter extends CursorAdapter implements AbsListView.RecyclerListener {
- private final LayoutInflater mFactory;
- private OnContentChangedListener mOnContentChangedListener;
- public ConversationListAdapter(Context context, Cursor cursor) {
- super(context, cursor, false /* auto-requery */);
- mFactory = LayoutInflater.from(context);
- }
- public void bindView(View view, Context context, Cursor cursor) {
- if (!(view instanceof ConversationListItem)) {
- Log.e(TAG, "Unexpected bound view: " + view);
- return;
- }
- ConversationListItem headerView = (ConversationListItem) view;
- Conversation conv = Conversation.from(context, cursor);
- ConversationListItemData ch = new ConversationListItemData(context, conv);
- headerView.bind(context, ch);
- }
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- if (LOCAL_LOGV) Log.v(TAG, "inflating new view");
- return mFactory.inflate(R.layout.conversation_list_item, parent, false);
- }
- public interface OnContentChangedListener {
- void onContentChanged(ConversationListAdapter adapter);
- }
- public void setOnContentChangedListener(OnContentChangedListener l) {
- mOnContentChangedListener = l;
- } protected void onContentChanged() {
- if (mCursor != null && !mCursor.isClosed()) {
- if (mOnContentChangedListener != null) {
- mOnContentChangedListener.onContentChanged(this);
- }
- }
- }
- }
public class ConversationListAdapter extends CursorAdapter implements AbsListView.RecyclerListener {
private final LayoutInflater mFactory;
private OnContentChangedListener mOnContentChangedListener;
public ConversationListAdapter(Context context, Cursor cursor) {
super(context, cursor, false /* auto-requery */);
mFactory = LayoutInflater.from(context);
}
public void bindView(View view, Context context, Cursor cursor) {
if (!(view instanceof ConversationListItem)) {
Log.e(TAG, "Unexpected bound view: " + view);
return;
}
ConversationListItem headerView = (ConversationListItem) view;
Conversation conv = Conversation.from(context, cursor);
ConversationListItemData ch = new ConversationListItemData(context, conv);
headerView.bind(context, ch);
}
public View newView(Context context, Cursor cursor, ViewGroup parent) {
if (LOCAL_LOGV) Log.v(TAG, "inflating new view");
return mFactory.inflate(R.layout.conversation_list_item, parent, false);
}
public interface OnContentChangedListener {
void onContentChanged(ConversationListAdapter adapter);
}
public void setOnContentChangedListener(OnContentChangedListener l) {
mOnContentChangedListener = l;
} protected void onContentChanged() {
if (mCursor != null && !mCursor.isClosed()) {
if (mOnContentChangedListener != null) {
mOnContentChangedListener.onContentChanged(this);
}
}
}
}
上述newView在adapter第一次调用时执行,将对应的布局文件加载进来并创建子项view(ConversationListItem),而bindView将cursor中的数据绑定到ConversationListItem ;onContentChanged则是在数据发生变化时重新查询,以达到刷新界面的目的。数据的填充大致就是这样,下面我们来看一个会话具体有哪些数据,以及它的布局。
3.3.2 子项数据定义
从上面数据绑定来看用于显示一个会话的布局文件是conversation_list_item.xml,我们不妨探秘一下google开发者定义一个会话包含了那些数据和ui控件。为节约篇幅我就在代码上对每个属性的含义做解释
- <com.android.mms.ui.ConversationListItem xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:background="@drawable/conversation_item_background_unread"
- android:paddingRight="10dip" >
- <android.widget.QuickContactBadge
- android:id="@+id/avatar" 该属性为widget,也即是每个联系都可以有一个快捷方式之类的widget
- android:visibility="gone"
- android:layout_marginLeft="7dip"
- android:layout_centerVertical="true"
- style="?android:attr/quickContactBadgeStyleWindowSmall" />
- <ImageView
- android:id="@+id/presence"
- android:visibility="gone" 用于显示联系人的图标
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="5dip"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:paddingBottom="20dip"
- />
- <TextView android:id="@+id/from" 发送者姓名,如果联系人中没有该联系人将显示号码
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMediumInverse"
- android:singleLine="true"
- android:layout_marginTop="6dip"
- android:layout_marginRight="5dip"
- android:layout_marginLeft="7dip"
- android:layout_alignParentTop="true"
- android:layout_toRightOf="@id/avatar"
- android:layout_toLeftOf="@id/presence"
- android:layout_alignWithParentIfMissing="true"
- android:ellipsize="marquee" />
- <TextView android:id="@+id/date"接收到短信的日期
- android:layout_marginTop="2dip"
- android:layout_marginBottom="10dip"
- android:layout_marginLeft="5dip"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmallInverse"
- android:singleLine="true"
- android:layout_alignParentRight="true"
- android:layout_alignParentBottom="true" />
- <ImageView android:id="@+id/error" 如果发送失败会有一个红的感叹号提示,以及错误提示
- android:layout_marginLeft="3dip"
- android:visibility="invisible"
- android:layout_toLeftOf="@id/date"
- android:layout_alignBottom="@id/date"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_list_alert_sms_failed" />
- <ImageView android:id="@+id/attachment"如果是彩信,有附件的情况将会显示该图标
- android:layout_marginLeft="3dip"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:visibility="gone"
- android:layout_toLeftOf="@id/error"
- android:layout_alignBottom="@id/date"
- android:src="@drawable/ic_attachment_universal_small" />
- <TextView android:id="@+id/subject" 彩信主题
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmallInverse"
- android:singleLine="true"
- android:layout_marginBottom="10dip"
- android:layout_marginLeft="7dip"
- android:layout_alignParentBottom="true"
- android:layout_toRightOf="@id/avatar"
- android:layout_alignWithParentIfMissing="true"
- android:layout_toLeftOf="@id/date"
- android:ellipsize="end" />
- </com.android.mms.ui.ConversationListItem>
<com.android.mms.ui.ConversationListItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="@drawable/conversation_item_background_unread"
android:paddingRight="10dip" >
<android.widget.QuickContactBadge
android:id="@+id/avatar" 该属性为widget,也即是每个联系都可以有一个快捷方式之类的widget
android:visibility="gone"
android:layout_marginLeft="7dip"
android:layout_centerVertical="true"
style="?android:attr/quickContactBadgeStyleWindowSmall" />
<ImageView
android:id="@+id/presence"
android:visibility="gone" 用于显示联系人的图标
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dip"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:paddingBottom="20dip"
/>
<TextView android:id="@+id/from" 发送者姓名,如果联系人中没有该联系人将显示号码
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMediumInverse"
android:singleLine="true"
android:layout_marginTop="6dip"
android:layout_marginRight="5dip"
android:layout_marginLeft="7dip"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/avatar"
android:layout_toLeftOf="@id/presence"
android:layout_alignWithParentIfMissing="true"
android:ellipsize="marquee" />
<TextView android:id="@+id/date"接收到短信的日期
android:layout_marginTop="2dip"
android:layout_marginBottom="10dip"
android:layout_marginLeft="5dip"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmallInverse"
android:singleLine="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true" />
<ImageView android:id="@+id/error" 如果发送失败会有一个红的感叹号提示,以及错误提示
android:layout_marginLeft="3dip"
android:visibility="invisible"
android:layout_toLeftOf="@id/date"
android:layout_alignBottom="@id/date"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:src="@drawable/ic_list_alert_sms_failed" />
<ImageView android:id="@+id/attachment"如果是彩信,有附件的情况将会显示该图标
android:layout_marginLeft="3dip"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:visibility="gone"
android:layout_toLeftOf="@id/error"
android:layout_alignBottom="@id/date"
android:src="@drawable/ic_attachment_universal_small" />
<TextView android:id="@+id/subject" 彩信主题
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmallInverse"
android:singleLine="true"
android:layout_marginBottom="10dip"
android:layout_marginLeft="7dip"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@id/avatar"
android:layout_alignWithParentIfMissing="true"
android:layout_toLeftOf="@id/date"
android:ellipsize="end" />
</com.android.mms.ui.ConversationListItem>
那大致定义了上述的控件,这些控件对应的数据怎么绑定上的??这得从自定义的adapter的bindView方法说起,大家可以回过来头来看看bindView方法,这里再给大家复习一下:
- ConversationListItem headerView = (ConversationListItem) view;
- Conversation conv = Conversation.from(context, cursor);
- ConversationListItemData ch = new ConversationListItemData(context, conv);
- headerView.bind(context, ch);
ConversationListItem headerView = (ConversationListItem) view;
Conversation conv = Conversation.from(context, cursor);
ConversationListItemData ch = new ConversationListItemData(context, conv);
headerView.bind(context, ch);
将得到的数据cursor转换成Conversation对象传递到ConversationListItemData,这里了google工程师将对应的数据抽象之后专门使用类来保存,这里值得称道
那我们不妨从ConversationListItemData来看看一个会话包含哪些数据
- private long mThreadId;会话的id
- private String mSubject;主题
- private String mDate;时间,这里指的是接收
- private boolean mHasAttachment;是否有附件
- private boolean mIsRead;是否是读过了
- private boolean mHasError;是否有错
- private boolean mHasDraft;是否有草稿
- private int mMessageCount;短信数量
- // The recipients in this conversation
- private ContactList mRecipients;联系人
- private String mRecipientString;联系人
- // the presence icon resource id displayed for the conversation thread.
- private int mPresenceResId;图标的id
private long mThreadId;会话的id
private String mSubject;主题
private String mDate;时间,这里指的是接收
private boolean mHasAttachment;是否有附件
private boolean mIsRead;是否是读过了
private boolean mHasError;是否有错
private boolean mHasDraft;是否有草稿
private int mMessageCount;短信数量
// The recipients in this conversation
private ContactList mRecipients;联系人
private String mRecipientString;联系人
// the presence icon resource id displayed for the conversation thread.
private int mPresenceResId;图标的id
然后将对应的数据设置到对应的ui上,headerView.bind(context, ch);
- public final void bind(Context context, final ConversationListItemData ch) {
- //if (DEBUG) Log.v(TAG, "bind()");
- setConversationHeader(ch);
- Drawable background = ch.isRead()?
- mContext.getResources().getDrawable(R.drawable.conversation_item_background_read) :
- mContext.getResources().getDrawable(R.drawable.conversation_item_background_unread);
- setBackgroundDrawable(background);
- LayoutParams attachmentLayout = (LayoutParams)mAttachmentView.getLayoutParams();
- boolean hasError = ch.hasError();
- // When there's an error icon, the attachment icon is left of the error icon.
- // When there is not an error icon, the attachment icon is left of the date text.
- // As far as I know, there's no way to specify that relationship in xml.
- if (hasError) {
- attachmentLayout.addRule(RelativeLayout.LEFT_OF, R.id.error);
- } else {
- attachmentLayout.addRule(RelativeLayout.LEFT_OF, R.id.date);
- }
- boolean hasAttachment = ch.hasAttachment();
- mAttachmentView.setVisibility(hasAttachment ? VISIBLE : GONE);
- // Date
- mDateView.setText(ch.getDate());
- // From.
- mFromView.setText(formatMessage(ch));
- // Register for updates in changes of any of the contacts in this conversation.
- ContactList contacts = ch.getContacts();
- if (DEBUG) Log.v(TAG, "bind: contacts.addListeners " + this);
- Contact.addListener(this);
- setPresenceIcon(contacts.getPresenceResId());
- // Subject
- mSubjectView.setText(ch.getSubject());
- LayoutParams subjectLayout = (LayoutParams)mSubjectView.getLayoutParams();
- // We have to make the subject left of whatever optional items are shown on the right.
- subjectLayout.addRule(RelativeLayout.LEFT_OF, hasAttachment ? R.id.attachment :
- (hasError ? R.id.error : R.id.date));
- // Transmission error indicator.
- mErrorIndicator.setVisibility(hasError ? VISIBLE : GONE);
- updateAvatarView();
- }
public final void bind(Context context, final ConversationListItemData ch) {
//if (DEBUG) Log.v(TAG, "bind()");
setConversationHeader(ch);
Drawable background = ch.isRead()?
mContext.getResources().getDrawable(R.drawable.conversation_item_background_read) :
mContext.getResources().getDrawable(R.drawable.conversation_item_background_unread);
setBackgroundDrawable(background);
LayoutParams attachmentLayout = (LayoutParams)mAttachmentView.getLayoutParams();
boolean hasError = ch.hasError();
// When there's an error icon, the attachment icon is left of the error icon.
// When there is not an error icon, the attachment icon is left of the date text.
// As far as I know, there's no way to specify that relationship in xml.
if (hasError) {
attachmentLayout.addRule(RelativeLayout.LEFT_OF, R.id.error);
} else {
attachmentLayout.addRule(RelativeLayout.LEFT_OF, R.id.date);
}
boolean hasAttachment = ch.hasAttachment();
mAttachmentView.setVisibility(hasAttachment ? VISIBLE : GONE);
// Date
mDateView.setText(ch.getDate());
// From.
mFromView.setText(formatMessage(ch));
// Register for updates in changes of any of the contacts in this conversation.
ContactList contacts = ch.getContacts();
if (DEBUG) Log.v(TAG, "bind: contacts.addListeners " + this);
Contact.addListener(this);
setPresenceIcon(contacts.getPresenceResId());
// Subject
mSubjectView.setText(ch.getSubject());
LayoutParams subjectLayout = (LayoutParams)mSubjectView.getLayoutParams();
// We have to make the subject left of whatever optional items are shown on the right.
subjectLayout.addRule(RelativeLayout.LEFT_OF, hasAttachment ? R.id.attachment :
(hasError ? R.id.error : R.id.date));
// Transmission error indicator.
mErrorIndicator.setVisibility(hasError ? VISIBLE : GONE);
updateAvatarView();
}
3.3.3 数据的查询和更新
走到这一步,大家终于不用管那些界面了,咱们来关心一下短信会话数据时从那来的。在ConversationList类的onCreate方法里有一些重要提示。
- mQueryHandler = new ThreadListQueryHandler(getContentResolver());
- this.getContentResolver().registerContentObserver(Contacts.CONTENT_URI, true, observer);
mQueryHandler = new ThreadListQueryHandler(getContentResolver());
this.getContentResolver().registerContentObserver(Contacts.CONTENT_URI, true, observer);
当数据的内容发生变法就会调用observer的onContentChange方法
- private ContentObserver observer = new ContentObserver(new Handler()){
- public void onChange(boolean selfChange) {
- startAsyncQuery();
- if (!Conversation.loadingThreads()) {
- Contact.invalidateCache();
- }
- }
- };
private ContentObserver observer = new ContentObserver(new Handler()){
public void onChange(boolean selfChange) {
startAsyncQuery();
if (!Conversation.loadingThreads()) {
Contact.invalidateCache();
}
}
};
另外在初始化adapter时设置了一个内容的监听器;
- mListAdapter = new ConversationListAdapter(this, null);
- mListAdapter.setOnContentChangedListener(mContentChangedListener);
mListAdapter = new ConversationListAdapter(this, null);
mListAdapter.setOnContentChangedListener(mContentChangedListener);
看到这大家可能说这些在我开机起来没有一个触发条件啊,不要心急,紧接着走到ConversationList的onStart()方法来了,盖房调用了startAsyncQuery()方法,该方法我们虽然没看代码我们都应该猜测到它是干神马的,大家应该会欣喜若狂了吧。好吧请看下面答案揭晓答案:
- private void startAsyncQuery() {
- try {
- setTitle(getString(R.string.refreshing));
- setProgressBarIndeterminateVisibility(true);
- Conversation.startQueryForAll(mQueryHandler, THREAD_LIST_QUERY_TOKEN);
- } catch (SQLiteException e) {
- SqliteWrapper.checkSQLiteException(this, e);
- }
- }
private void startAsyncQuery() {
try {
setTitle(getString(R.string.refreshing));
setProgressBarIndeterminateVisibility(true);
Conversation.startQueryForAll(mQueryHandler, THREAD_LIST_QUERY_TOKEN);
} catch (SQLiteException e) {
SqliteWrapper.checkSQLiteException(this, e);
}
}
这里继续走下去
- public static void startQueryForAll(AsyncQueryHandler handler, int token) {
- handler.cancelOperation(token);
- handler.startQuery(token, null, sAllThreadsUri,
- ALL_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER);
- }
public static void startQueryForAll(AsyncQueryHandler handler, int token) {
handler.cancelOperation(token);
handler.startQuery(token, null, sAllThreadsUri,
ALL_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER);
}
最后会走到ThreadListQueryHandler的onQueryComplete方法中,至于怎么走到的稍微看一下就明白了,这里就不提及了,那就来看看onQueryComplete方法:
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- switch (token) {
- case THREAD_LIST_QUERY_TOKEN:
- mListAdapter.changeCursor(cursor);
- setTitle(mTitle);
- setProgressBarIndeterminateVisibility(false);
- if (mNeedToMarkAsSeen) {
- mNeedToMarkAsSeen = false;
- Conversation.markAllConversationsAsSeen(getApplicationContext());
- // Database will be update at this time in some conditions.
- // Wait 1s and ensure update complete.
- mQueryHandler.postDelayed(new Runnable() {
- public void run() {
- // Delete any obsolete threads. Obsolete threads are threads that aren't
- // referenced by at least one message in the pdu or sms tables.
- Conversation.asyncDeleteObsoleteThreads(mQueryHandler,
- DELETE_OBSOLETE_THREADS_TOKEN);
- }
- }, 1000);
- }
- break;
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case THREAD_LIST_QUERY_TOKEN:
mListAdapter.changeCursor(cursor);
setTitle(mTitle);
setProgressBarIndeterminateVisibility(false);
if (mNeedToMarkAsSeen) {
mNeedToMarkAsSeen = false;
Conversation.markAllConversationsAsSeen(getApplicationContext());
// Database will be update at this time in some conditions.
// Wait 1s and ensure update complete.
mQueryHandler.postDelayed(new Runnable() {
public void run() {
// Delete any obsolete threads. Obsolete threads are threads that aren't
// referenced by at least one message in the pdu or sms tables.
Conversation.asyncDeleteObsoleteThreads(mQueryHandler,
DELETE_OBSOLETE_THREADS_TOKEN);
}
}, 1000);
}
break;
这里大家可以看到mListAdapter.changeCursor(cursor);调用该方法,adapter重新绑定数据到ui完成界面刷新。那如果数据发生变化上面有提到的一个就是监听了数据库,一旦发生变化就会重新查询刷新ui。这也是为啥最早提及监听数据库和adapter添加内容监听器的原因。
3.3.4 listview item的单击事件处理
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- Cursor cursor = (Cursor) getListView().getItemAtPosition(position);
- Conversation conv = Conversation.from(this, cursor);
- long tid = conv.getThreadId();
- if (LogTag.VERBOSE) {
- Log.d(TAG, "onListItemClick: pos=" + position + ", view=" + v + ", tid=" + tid);
- }
- openThread(tid);
- }
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Cursor cursor = (Cursor) getListView().getItemAtPosition(position);
Conversation conv = Conversation.from(this, cursor);
long tid = conv.getThreadId();
if (LogTag.VERBOSE) {
Log.d(TAG, "onListItemClick: pos=" + position + ", view=" + v + ", tid=" + tid);
}
openThread(tid);
}
点击子项所做的工作就是打开这样的一个会话,继续看看openTread是干嘛的
- private void openThread(long threadId) {
- startActivity(ComposeMessageActivity.createIntent(this, threadId));
- }
private void openThread(long threadId) {
startActivity(ComposeMessageActivity.createIntent(this, threadId));
}
看到这大家应该清楚了,打开某个会话,仍然是在ComposeMessageActivity该类来实现的,这个类不但实现了会话的加载,新建会话也是在该类来实现的,所以了后面会专门来探讨该类是怎么实现这些功能的。
3.3.5 listview item的长按事件处理
对于item的长按事件处理,2.3与4.0有一些区别,4.0仅提供了删除该会话的功能,那2.3提供了查看会话、删除会话、如果联系人没有在通讯录中还有一个“添加到联系人”的功能、如果联系人在通讯录中存在提供了一个“查看联系”的功能。下面是2.3的处理代码:- private final OnCreateContextMenuListener mConvListOnCreateContextMenuListener =
- new OnCreateContextMenuListener() {
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- Cursor cursor = mListAdapter.getCursor();
- if (cursor == null || cursor.getPosition() < 0) {
- return;
- }
- Conversation conv = Conversation.from(ConversationList.this, cursor);
- ContactList recipients = conv.getRecipients();
- menu.setHeaderTitle(recipients.formatNames(","));
- menu.add(0, MENU_VIEW, 0, R.string.menu_view);
- // Only show if there's a single recipient
- if (recipients.size() == 1) {
- // do we have this recipient in contacts?
- if (recipients.get(0).existsInDatabase()) {
- menu.add(0, MENU_VIEW_CONTACT, 0, R.string.menu_view_contact);
- } else {
- menu.add(0, MENU_ADD_TO_CONTACTS, 0, R.string.menu_add_to_contacts);
- }
- }
- menu.add(0, MENU_DELETE, 0, R.string.menu_delete);
- }
- };
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- Cursor cursor = mListAdapter.getCursor();
- if (cursor != null && cursor.getPosition() >= 0) {
- Conversation conv = Conversation.from(ConversationList.this, cursor);
- long threadId = conv.getThreadId();
- switch (item.getItemId()) {
- case MENU_DELETE: {
- confirmDeleteThread(threadId, mQueryHandler);
- break;
- }
- case MENU_VIEW: {
- openThread(threadId);
- break;
- }
- case MENU_VIEW_CONTACT: {
- Contact contact = conv.getRecipients().get(0);
- Intent intent = new Intent(Intent.ACTION_VIEW, contact.getUri());
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
- startActivity(intent);
- break;
- }
- case MENU_ADD_TO_CONTACTS: {
- String address = conv.getRecipients().get(0).getNumber();
- startActivity(createAddContactIntent(address));
- break;
- }
- default:
- break;
- }
- }
- return super.onContextItemSelected(item);
- }
private final OnCreateContextMenuListener mConvListOnCreateContextMenuListener =
new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
Cursor cursor = mListAdapter.getCursor();
if (cursor == null || cursor.getPosition() < 0) {
return;
}
Conversation conv = Conversation.from(ConversationList.this, cursor);
ContactList recipients = conv.getRecipients();
menu.setHeaderTitle(recipients.formatNames(","));
menu.add(0, MENU_VIEW, 0, R.string.menu_view);
// Only show if there's a single recipient
if (recipients.size() == 1) {
// do we have this recipient in contacts?
if (recipients.get(0).existsInDatabase()) {
menu.add(0, MENU_VIEW_CONTACT, 0, R.string.menu_view_contact);
} else {
menu.add(0, MENU_ADD_TO_CONTACTS, 0, R.string.menu_add_to_contacts);
}
}
menu.add(0, MENU_DELETE, 0, R.string.menu_delete);
}
};
@Override
public boolean onContextItemSelected(MenuItem item) {
Cursor cursor = mListAdapter.getCursor();
if (cursor != null && cursor.getPosition() >= 0) {
Conversation conv = Conversation.from(ConversationList.this, cursor);
long threadId = conv.getThreadId();
switch (item.getItemId()) {
case MENU_DELETE: {
confirmDeleteThread(threadId, mQueryHandler);
break;
}
case MENU_VIEW: {
openThread(threadId);
break;
}
case MENU_VIEW_CONTACT: {
Contact contact = conv.getRecipients().get(0);
Intent intent = new Intent(Intent.ACTION_VIEW, contact.getUri());
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
startActivity(intent);
break;
}
case MENU_ADD_TO_CONTACTS: {
String address = conv.getRecipients().get(0).getNumber();
startActivity(createAddContactIntent(address));
break;
}
default:
break;
}
}
return super.onContextItemSelected(item);
}
上面这些代码没有太多技术上的难点这里了就不做太多的深入分析。
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- menu.clear();
- menu.add(0, MENU_COMPOSE_NEW, 0, R.string.menu_compose_new).setIcon(
- com.android.internal.R.drawable.ic_menu_compose);
- if (mListAdapter.getCount() > 0) {
- menu.add(0, MENU_DELETE_ALL, 0, R.string.menu_delete_all).setIcon(
- android.R.drawable.ic_menu_delete);
- }
- menu.add(0, MENU_SEARCH, 0, android.R.string.search_go).
- setIcon(android.R.drawable.ic_menu_search).
- setAlphabeticShortcut(android.app.SearchManager.MENU_KEY);
- menu.add(0, MENU_PREFERENCES, 0, R.string.menu_preferences).setIcon(
- android.R.drawable.ic_menu_preferences);
- return true;
- }
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
menu.add(0, MENU_COMPOSE_NEW, 0, R.string.menu_compose_new).setIcon(
com.android.internal.R.drawable.ic_menu_compose);
if (mListAdapter.getCount() > 0) {
menu.add(0, MENU_DELETE_ALL, 0, R.string.menu_delete_all).setIcon(
android.R.drawable.ic_menu_delete);
}
menu.add(0, MENU_SEARCH, 0, android.R.string.search_go).
setIcon(android.R.drawable.ic_menu_search).
setAlphabeticShortcut(android.app.SearchManager.MENU_KEY);
menu.add(0, MENU_PREFERENCES, 0, R.string.menu_preferences).setIcon(
android.R.drawable.ic_menu_preferences);
return true;
}
对应的点击事件处理
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch(item.getItemId()) {
- case MENU_COMPOSE_NEW:
- createNewMessage();
- break;
- case MENU_SEARCH:
- onSearchRequested();
- break;
- case MENU_DELETE_ALL:
- // The invalid threadId of -1 means all threads here.
- confirmDeleteThread(-1L, mQueryHandler);
- break;
- case MENU_PREFERENCES: {
- Intent intent = new Intent(this, MessagingPreferenceActivity.class);
- startActivityIfNeeded(intent, -1);
- break;
- }
- default:
- return true;
- }
- return false;
- }
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case MENU_COMPOSE_NEW:
createNewMessage();
break;
case MENU_SEARCH:
onSearchRequested();
break;
case MENU_DELETE_ALL:
// The invalid threadId of -1 means all threads here.
confirmDeleteThread(-1L, mQueryHandler);
break;
case MENU_PREFERENCES: {
Intent intent = new Intent(this, MessagingPreferenceActivity.class);
startActivityIfNeeded(intent, -1);
break;
}
default:
return true;
}
return false;
}
1)首先,这里的compose也即是新建会话的意思所以了和上面的新建会话功能一样,这里不重复赘叙;其次,这里的删除会话和上面长按menu里的删除会话不一样,在于这里是删除全部的会话,而上面是删除某一个会话,但实现方式差不多。首先调用confirmDeleteThread方法,去查询该会话相关的数据,弄到这大家可能蒙了我删就删呗,怎么还要去查询了,大家从传递的参数大致可以看到,实际上当前会话的id我们是得到了但对于该会对应的相关数据我们并没有直接拿到。
- public static void confirmDeleteThread(long threadId, AsyncQueryHandler handler) {
- Conversation.startQueryHaveLockedMessages(handler, threadId,
- HAVE_LOCKED_MESSAGES_TOKEN);
- }
public static void confirmDeleteThread(long threadId, AsyncQueryHandler handler) {
Conversation.startQueryHaveLockedMessages(handler, threadId,
HAVE_LOCKED_MESSAGES_TOKEN);
}
这里是一个异步查询的方式,查询完后会回调对应queryHandler的onQueryComplete方法的核心调用
- case HAVE_LOCKED_MESSAGES_TOKEN:
- long threadId = (Long)cookie;
- confirmDeleteThreadDialog(new DeleteThreadListener(threadId, mQueryHandler,
- ConversationList.this), threadId == -1,
- cursor != null && cursor.getCount() > 0,
- ConversationList.this);
- break;
case HAVE_LOCKED_MESSAGES_TOKEN:
long threadId = (Long)cookie;
confirmDeleteThreadDialog(new DeleteThreadListener(threadId, mQueryHandler,
ConversationList.this), threadId == -1,
cursor != null && cursor.getCount() > 0,
ConversationList.this);
break;
这里是弹出对话框,不难看出,那点击确认删除的动作就放在对应的listener中
- public void onClick(DialogInterface dialog, final int whichButton) {
- MessageUtils.handleReadReport(mContext, mThreadId,
- PduHeaders.READ_STATUS__DELETED_WITHOUT_BEING_READ, new Runnable() {
- public void run() {
- int token = DELETE_CONVERSATION_TOKEN;
- if (mThreadId == -1) {
- Conversation.startDeleteAll(mHandler, token, mDeleteLockedMessages);
- DraftCache.getInstance().refresh();
- } else {
- Conversation.startDelete(mHandler, token, mDeleteLockedMessages,
- mThreadId);
- DraftCache.getInstance().setDraftState(mThreadId, false);
- }
- }
- });
- dialog.dismiss();
- }
public void onClick(DialogInterface dialog, final int whichButton) {
MessageUtils.handleReadReport(mContext, mThreadId,
PduHeaders.READ_STATUS__DELETED_WITHOUT_BEING_READ, new Runnable() {
public void run() {
int token = DELETE_CONVERSATION_TOKEN;
if (mThreadId == -1) {
Conversation.startDeleteAll(mHandler, token, mDeleteLockedMessages);
DraftCache.getInstance().refresh();
} else {
Conversation.startDelete(mHandler, token, mDeleteLockedMessages,
mThreadId);
DraftCache.getInstance().setDraftState(mThreadId, false);
}
}
});
dialog.dismiss();
}
3.5、 搜索功能
- @Override
- public boolean onSearchRequested() {
- startSearch(null, false, null /*appData*/, false);
- return true;
- }
@Override
public boolean onSearchRequested() {
startSearch(null, false, null /*appData*/, false);
return true;
}
startSearch方法走到这就走不动了,下面了该方法就调用到google search去了。所以暂时就调到这了