短信ui分析--会话列表
来源:互联网 发布:安卓淘宝客户端下载 编辑:程序博客网 时间:2024/06/12 22:03
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
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>
3、UI及功能实现
- @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>
3.2 新建会话
- 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();
- }
- });
createNewMessage()方法大家可能已经猜到
- 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>
大家可以从填充数据来看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);
- }
- }
- }
- }
上述newView在adapter第一次调用时执行,将对应的布局文件加载进来并创建子项view(ConversationListItem),而bindView将cursor中的数据绑定到ConversationListItem ;onContentChanged则是在数据发生变化时重新查询,以达到刷新界面的目的。数据的填充大致就是这样,下面我们来看一个会话具体有哪些数据,以及它的布局。
3.3.2 子项数据定义
从上面数据绑定来看用于显示一个会话的布局文件是conversation_list_item.xml,我们不妨探秘一下google开发者定义一个会话包含了那些数据和ui控件。为节约篇幅我就在代码上对每个属性的含义做解释
- <com.android.mms.ui.ConversationListItemxmlns: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"
- />
- <TextViewandroid: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" />
- <TextViewandroid: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"/>
- <ImageViewandroid: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"/>
- <ImageViewandroid: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"/>
- <TextViewandroid: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);
将得到的数据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
然后将对应的数据设置到对应的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();
- }
3.3.3 数据的查询和更新
走到这一步,大家终于不用管那些界面了,咱们来关心一下短信会话数据时从那来的。在ConversationList类的onCreate方法里有一些重要提示。
- 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();
- }
- }
- };
另外在初始化adapter时设置了一个内容的监听器;
- 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);
- }
- }
这里继续走下去
- 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;
这里大家可以看到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);
- }
- private void openThread(long threadId) {
- startActivity(ComposeMessageActivity.createIntent(this, threadId));
- }
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);
- }
3.4、menu
- @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;
- }
- public static void confirmDeleteThread(long threadId, AsyncQueryHandler handler) {
- Conversation.startQueryHaveLockedMessages(handler, threadId,
- HAVE_LOCKED_MESSAGES_TOKEN);
- }
- 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;
- 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;
- }
4、总结
- 短信ui分析--会话列表
- 短信ui分析--会话列表
- 源码分析Mms--ConversationList短信主界面会话列表
- Android 获取短信会话列表
- Android 获取短信会话列表
- Android 获取短信会话列表
- android发送短信、会话列表、短信详情
- 短信ui-会话编辑界面(二)接收者UI
- 短信ui分析--短信界面更新
- 短信ui分析--设置界面
- Android短信列表源码分析
- 短信ui-会话编辑界面(一) 初识
- 短信ui-会话编辑界面(一) 初识
- 短信ui--会话编辑界面(三)历史记录
- 短信ui--会话编辑界面(四)BottomPanel
- 短信ui--会话编辑界面(五)彩信附件
- 短信ui--会话编辑界面之彩信附件
- 短信(会话)
- eclipse导入class文件
- 数组大折腾
- java经典算法_021利用递归方法求5!
- Eclipse设置:背景与字体大小和xml文件中字体大小调整
- Linux下G++怎么编译使用Boost库的程序
- 短信ui分析--会话列表
- fibnacci序列
- Struct2.0第一个例子
- 开源 Portal 型 CMS
- android 端生成随机验证码 实现
- http://www.open-open.com/lib/view/open1328063267889.html
- 关闭输入输出错误输出
- sed 格式化文件
- 二次开发