這幾天在學習NotePad的源碼,對其中的ListView,CursorAdapter,ContextMenu數據之間的映射關係仔細研究了一下,在這記錄一下。
在這先說一下SQLiteDatabase中的insert()這個方法的返回值爲插入數據行的ID,具體在ContentProvider中的insert()方法中有引用:
@Override
public Uri insert(Uri uri, ContentValues initValues) {
// TODO Auto-generated method stub
if (uriMatcher.match(uri) != NOTES) {
throw new IllegalArgumentException("Invalid uri" + uri);
}
ContentValues values;
if (initValues != null) {
values = new ContentValues(initValues);
} else {
values = new ContentValues();
}
long now = Long.valueOf(System.currentTimeMillis());
if (values.containsKey(Notes.CREATED_DATE) == false) {
values.put(Notes.CREATED_DATE, now);
}
if (values.containsKey(Notes.MODIFIED_DATE) == false) {
values.put(Notes.MODIFIED_DATE, now);
}
if (values.containsKey(Notes.NOTE) == false) {
values.put(Notes.NOTE, "");
}
if (values.containsKey(Notes.TITLE) == false) {
values.put(Notes.TITLE,
getContext().getResources().getString(R.string.untitle));
}
SQLiteDatabase db = myDBHelper.getWritableDatabase();
long rowId = db.insert(NOTES_TABLE_NAME, Notes.NOTE, values);
Log.e(TAG, "from insert=>rowId:" + rowId);
if (rowId > 0) {
Uri noteUri = ContentUris.withAppendedId(Notes.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);// 這個noteUri改成uri行嗎,就是不帶Id的那種,寫完和實驗一下
return noteUri;
}
throw new SQLiteException("Insert Note Fail! uri=>" + uri);
}
有上面的代碼可以看到,在插入值的時候並沒有指定Notes._ID的值,但是當你插入的時候會自動分配一個ID給屬性Notes._ID列,默認的情況下,這個ID是自增的。
也就是會比當前最大的那個id+1(可能由於刪除操作,刪除前面的行,比如說一共有18行,1~18,刪掉第一行,還剩2~18,再插入一行數據之後,數據的ID列在數據庫中的
值是19);首先對這個ID的來源有所瞭解了,你會發現以後在查詢的時候凡是用到映射的地方都會在映射中加入ID,因爲在ListView中顯示的Item的ID就是在數據庫中ID列
的值,而不是在SimpleCursorAdapter的位置,SimpleCursorAdapter的位置對應的是ListView的從頭到尾顯示的順序,現在說好像有點抽象,上圖。
每個Item都會有一個ID,該ID對應的值也就是在數據庫中Notes._ID對應的值,知道這個Notes._ID和ListView之間的關係了吧,
也就是ListView中的Item的ID對應的就是每行數據的ID列的值,這也就是爲什麼一般使用ContentProvider的時候都會用到ID這一列
的原因,因爲這樣設計才能根據你的點擊映射到數據庫的數據上面。
那麼Item的顯示順序是由什麼來決定的呢?
答案是有CusorAdapter中的位置決定。由於CursorAdapter是對Cursor的適配,而Cursor又是根據一定條件按照一定順序從數據庫中
篩選出來的,所以Cursor中數據的順序也就是顯示的順序。這跟數據行的ID沒有什麼關係。上圖:
在NotePad中使用的是根據modified這一列的降序排列,也就是根據修改時間排列的,誰最後修改的排在最前面,但是可能這個data0的ID由於是在insert創建決定的,(
比如是在data1,data2之後,那麼他的ID = 3而在CursorAdapter中的position是2(起始點是0,1,2))。
好了說了這麼多好像把基本的脈絡差不多裏清楚了,那麼再來研究一下這個ContextMenu();所謂的ContextMenu簡單來說就是你長按一個VIew之後彈出來的菜單,
不過前提是你要給這個組件註冊一下listener,View.setOnCreateContextMenuListener(Listener);對應在Notepad中的代碼是:
getListView().setOnCreateContextMenuListener(this);
當完成註冊之後呢,如果每次長按ListView中的一個Item就會觸發onCreateContextMenu()這個回調函數,Notepad中的代碼:
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo info) {
AdapterContextMenuInfo menuinfo;
try {
menuinfo = (AdapterContextMenuInfo) info;
} catch (ClassCastException e) {
Log.e(TAG, "from onCreateContextMenu()");
return;
}
Cursor cursor = (Cursor) getListAdapter().getItem(menuinfo.position);
if (cursor == null) {
Log.e(TAG, "cursor == null");
return;
}
menu.setHeaderTitle(cursor.getString(TITLE_COLUME_INDEX));
menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_delete);
}
關鍵是這幾個參數的理解,上官方文檔~
Called when the context menu for this view is being built. It is not safe to hold onto the menu after this method returns.
Parameters
<span style="color:#CC0000;">menu </span> The context menu that is being built
<span style="color:#CC0000;">v</span> The view for which the context menu is being built
<span style="color:#CC0000;">menuInfo</span> Extra information about the item for which the context menu should be shown. This information will vary depending on the class of v.
menu也就是即將要創建的這個ContextMenu了;
v也就是你setOnCreateContextMenuListener()的View;
menuInfo就好呢有講究了,詳細說一下啊。
menuInfo的類型是ContextMenuInfo該類中有三個變量,上官方文檔~
<span style="color:#CC0000;">public long id</span>
Added in API level 1
The row id of the item for which the context menu is being displayed.
<span style="color:#CC0000;"> public int position</span>
Added in API level 1
The position in the adapter for which the context menu is being displayed.
<span style="color:#CC0000;"> public View targetView</span>
Added in API level 1
The child view for which the context menu is being displayed. This will be one of the children of this AdapterView.
id也就是Item的ID也就是數據行的ID;
position也就是在CursorAdapter中的位置,也就是Item的顯示順序對應的位置;
targetView也不知道幹啥的。我getId一直都是-1,看官方文檔的意思就是AdapterView的child,所謂的AdapterView就是該View的Child是由Adapter來決定的,常見的
就是ListView,GridView,Spinner和Gallery;
上面在onCreateContextMenu()中用到了menuinfo.position也就是CursorAdapter中的位置,通過Adapter.getItem(menuinfo.position)或的該行的數據行保留在Cursor
中,然後在通過Cursor獲取每列的值。
好像寒不夠充分,在說一下,點擊彈出來的ContextMenu的Item的處理函數,onContextItemSelected():
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo menuinfo;
try {
menuinfo = (AdapterContextMenuInfo) item.getMenuInfo();
} catch (ClassCastException e) {
Log.e(TAG, "from onContextItemSelected()");
return false;
}
switch (item.getItemId()) {
case MENU_ITEM_DELETE:
Uri uri = ContentUris.withAppendedId(getIntent().getData(),
menuinfo.id);
getContentResolver().delete(uri, null, null);
return true;
}
return false;
}
這個函數中通過MenuInfo的id獲得數據的Id,然後根據id刪除相應的數據行。
說到這,相信也基本把ListView,SimpleCursorAdapter,ContextMenu三者的映射關係