前言
ListView與數(shù)據(jù)庫(kù)結(jié)合使用也是一種非常常見(jiàn)的手段氧急。例如歌單的ListView,在我們重新啟動(dòng)應(yīng)用的時(shí)候數(shù)據(jù)依舊存在,如下圖:具體實(shí)現(xiàn)
一麻掸、準(zhǔn)備Activity和ListView
- Activity要繼承ListActivity或AppCompatActivity(推薦后者)
- ListView不需要先準(zhǔn)備適配器Adapter,只需要初始化好ListView就行了
二、創(chuàng)建數(shù)據(jù)庫(kù)
- 新建一個(gè)類MySQLite繼承自SQLiteOpenHelper,然后去實(shí)現(xiàn)未實(shí)現(xiàn)的3個(gè)方法屯碴,分別是:
1、構(gòu)造方法
MySQLite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
//this.mContext = context; //自定義變量mContext
}
其中形式參數(shù)的意義分別是:
- context:上下文膊存,即當(dāng)前Activity的對(duì)象 this
- name:即將創(chuàng)建的數(shù)據(jù)庫(kù)的表的名字导而,例如:"user.db"、"music_msg.db"
- factory:游標(biāo)隔崎,一般都是 null
- version:數(shù)據(jù)庫(kù)版本號(hào)今艺,用于更新數(shù)據(jù)庫(kù)版本, 例如:1
所以也可以寫成
MySQLite(Context context) {
super(context, "music_msg.db", null, 1);
this.mContext = context;
}
2爵卒、onCreate方法洼滚,用于創(chuàng)建數(shù)據(jù)庫(kù)的表
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("create table music_msg(_id integer primary key autoincrement, singer varchar, songname varchar, url varchar)");
}
通過(guò)execSQL()方法執(zhí)行SQL語(yǔ)句創(chuàng)建數(shù)據(jù)庫(kù)的表
如果你不懂?dāng)?shù)據(jù)庫(kù),那你只需要改變下圖框出的部分技潘,第一個(gè)是表名,第二個(gè)是變量名和數(shù)據(jù)類型
3千康、onUpgrade方法享幽,用于升級(jí)軟件時(shí)更新數(shù)據(jù)庫(kù)表結(jié)構(gòu),此方法在數(shù)據(jù)庫(kù)的版本發(fā)生變化時(shí)會(huì)被調(diào)用
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
Toast.makeText(mContext, "onUpgrade", Toast.LENGTH_SHORT).show();
}
該方法這里不做演示分析拾弃,只是彈個(gè)Toast值桩,想具體了解的可自行谷歌百度
三、關(guān)聯(lián)ListView與數(shù)據(jù)庫(kù)
- 關(guān)聯(lián)的關(guān)鍵就是ListView的適配器SimpleCursorAdapter豪椿,構(gòu)造方法與SimpleAdapter有些類似
SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags)
其中形式參數(shù)的意義分別是:
context:上下文奔坟,即當(dāng)前Activity的對(duì)象 this
layout:ListView對(duì)應(yīng)的布局文件
c:游標(biāo)對(duì)象携栋,此處可以設(shè)為 null
from:想從數(shù)據(jù)庫(kù)表中拿的數(shù)據(jù)的列名,放在new出來(lái)的String數(shù)組中
to:將 layout 里面的控件的 id 寫進(jìn)new出來(lái)的 int 數(shù)組中咳秉,分別對(duì)應(yīng) from 里的數(shù)據(jù)
flags:標(biāo)志用于確定適配器的行為婉支,用常量CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER
代碼如下:
SimpleCursorAdapter mSimpleCursorAdapter = new SimpleCursorAdapter(MyListviewActivity.this, R.layout.listview_sql_item, null,
new String[]{"songname", "singer"}, new int[]{R.id.songname, R.id.singer}, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
mListView.setAdapter(mSimpleCursorAdapter); //給ListView設(shè)置適配器
refreshListview(); //自定義的方法,用于當(dāng)數(shù)據(jù)列表改變時(shí)刷新ListView
四澜建、刷新ListView(很關(guān)鍵)
- 當(dāng)我們對(duì)數(shù)據(jù)進(jìn)行增刪改的時(shí)候向挖,就需要將ListView刷新顯示
-
自定義一個(gè)方法refreshListview(),里面關(guān)鍵的方法是適配器對(duì)象mSimpleCursorAdapter的changeCursor(Cursor cursor)方法炕舵,因?yàn)樾枰獋鬟f一個(gè)參數(shù)cursor何之,所以我們需要得到這個(gè)Cursor對(duì)象,通過(guò)數(shù)據(jù)庫(kù)對(duì)象的查詢方法query就能返回一個(gè)Cursor對(duì)象咽筋,query方法如下圖:
這里我們暫時(shí)只需要設(shè)置table參數(shù)溶推,即數(shù)據(jù)庫(kù)的表名,先不管另外6個(gè)參數(shù)是什么(后面會(huì)再詳細(xì)說(shuō)明)奸攻,此處全部設(shè)置為null就行了蒜危,即:
Cursor mCursor = mDbWriter.query("music_msg", null, null, null, null, null, null);
上面說(shuō)到要用數(shù)據(jù)庫(kù)的對(duì)象去調(diào)用query方法,所以我們就得先拿到數(shù)據(jù)庫(kù)的對(duì)象舞箍。數(shù)據(jù)庫(kù)對(duì)象的獲取有兩個(gè)方法——getWritableDatabase() 和 getReadableDatabase()
- SQLiteDatabase getWritableDatabase() :以讀寫的方式打開(kāi)數(shù)據(jù)庫(kù)對(duì)應(yīng)的SQLiteDatabase對(duì)象舰褪,如果打開(kāi)的數(shù)據(jù)庫(kù)磁盤滿了,此時(shí)只能讀不能寫疏橄,此時(shí)調(diào)用了 getWritableDatabase 的實(shí)例占拍,那么將會(huì)發(fā)生錯(cuò)誤(異常)
- SQLiteDatabase getReadableDatabase():以讀寫的方式打開(kāi)數(shù)據(jù)庫(kù)對(duì)應(yīng)的SQLiteDatabase對(duì)象,如果數(shù)據(jù)庫(kù)的磁盤空間滿了捎迫,就會(huì)打開(kāi)失敗晃酒,當(dāng)打開(kāi)失敗后會(huì)繼續(xù)嘗試以只讀方式打開(kāi)數(shù)據(jù)庫(kù)。如果該問(wèn)題成功解決窄绒,則只讀數(shù)據(jù)庫(kù)對(duì)象就會(huì)關(guān)閉贝次,然后返回一個(gè)可讀寫的數(shù)據(jù)庫(kù)對(duì)象
要調(diào)用這兩個(gè)方法,先再Activity的onCreate()方法里實(shí)例化之前自定義的MySQLite類彰导,代碼如下:
MySQLite mMySQLite = new MySQLite(this);
SQLiteDatabase mDbWriter = mMySQLite.getWritableDatabase();
SQLiteDatabase mDbReader = mMySQLite.getReadableDatabase();
- 接著就可以寫出refreshListview()方法了蛔翅,代碼如下:
//刷新數(shù)據(jù)列表
public void refreshListview() {
Cursor mCursor = mDbWriter.query("music_msg", null, null, null, null, null, null);
mSimpleCursorAdapter.changeCursor(mCursor);
}
五、增
- 實(shí)現(xiàn)往數(shù)據(jù)庫(kù)中增加一條數(shù)據(jù)并在ListView中顯示
-
先簡(jiǎn)單寫一個(gè)UI界面:在有ListView的Activity的底部增加兩個(gè)文本輸入框和一個(gè)按鈕位谋,如圖:
這里的ListView沒(méi)有數(shù)據(jù)所以看不見(jiàn)山析,兩個(gè)EditText的對(duì)象分別為 mEt_songName 和 mEt_singer ,Button的對(duì)象為 btn_insert掏父,這三個(gè)對(duì)象后面要用到
- 數(shù)據(jù)的存儲(chǔ)是通過(guò)ContentValues 類實(shí)現(xiàn)的笋轨,ContentValues 類跟Hashtable 類很相似,也是通過(guò)put(key, value) 方法暫存的,其中鍵值對(duì)的鍵key只能是String類型爵政,值value只能是基本類型仅讽。于是我們可以new出ContentValues 類的對(duì)象,然后將EditText上的數(shù)據(jù)作為值value存起來(lái)钾挟,再通過(guò)數(shù)據(jù)庫(kù)對(duì)象的 insert() 方法將數(shù)據(jù)插入數(shù)據(jù)庫(kù)表中洁灵,最后調(diào)用refreshListview()方法刷新數(shù)據(jù)列表,將數(shù)據(jù)在ListView中顯示出來(lái)等龙,代碼如下:
//增
public void insertData() {
ContentValues mContentValues = new ContentValues();
mContentValues.put("songname", mEt_songName.getText().toString().trim());
mContentValues.put("singer", mEt_singer.getText().toString().trim());
mDbWriter.insert("music_msg", null, mContentValues);
refreshListview();
}
其中的insert()方法的3個(gè)參數(shù)
table:想插入的數(shù)據(jù)庫(kù)的表名
nullColunmHack:代表強(qiáng)行插入null值的數(shù)據(jù)列的列名处渣,這里設(shè)置為 null 就行
values:存放了數(shù)據(jù)的ContentValues 對(duì)象
這里將其封裝成一個(gè)insertData()方法,然后在添加按鈕mBtn_insert的點(diǎn)擊事件中調(diào)用蛛砰,代碼如下:
mBtn_insert.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
insertData();
mEt_songName.setText(""); //數(shù)據(jù)添加完成后罐栈,把文本輸入框清空,方便下次輸入
mEt_singer.setText("");
}
});
-
效果如圖:
六泥畅、刪
- 實(shí)現(xiàn)刪除ListView的一條數(shù)據(jù)并將數(shù)據(jù)庫(kù)對(duì)應(yīng)的數(shù)據(jù)也刪除
- 這里我們實(shí)現(xiàn)長(zhǎng)按ListView的某一項(xiàng)item荠诬,然后彈出一個(gè)對(duì)話框,確認(rèn)是否刪除位仁,確定的話就刪除該數(shù)據(jù)柑贞,取消的話就不做任何處理
-
要想刪除數(shù)據(jù)庫(kù)的某一條數(shù)據(jù),我們就得先知道是ListView的哪一項(xiàng)聂抢,所以我們先寫ListView的長(zhǎng)按事件監(jiān)聽(tīng)钧嘶,并且添加一個(gè)AlertDialog,代碼如圖:
可以注意到上面有3個(gè)紅色的框框琳疏,第一個(gè)框框是 final關(guān)鍵字有决,因?yàn)槠湫揎椀膒osition變量在匿名內(nèi)部類中需要被使用到,所以需要在前面添加 final 關(guān)鍵字空盼。第二個(gè)框框是一個(gè)自定義的方法书幕,其作用是刪除LIstView的第 position 項(xiàng)數(shù)據(jù),后面會(huì)具體介紹揽趾。第三個(gè)框框是布爾變量台汇,返回true代表響應(yīng)該次監(jiān)聽(tīng)事件
- 從長(zhǎng)按監(jiān)聽(tīng)中我們可以知道 position 就是我們要?jiǎng)h除項(xiàng)的位置,所以我們現(xiàn)在可以開(kāi)始寫刪除功能篱瞎。我們自定義一個(gè) deleteData(int positon) 方法苟呐,參數(shù)就是剛才那個(gè) position 。思路是這樣的俐筋,拿到數(shù)據(jù)庫(kù)表的游標(biāo)掠抬,讓它移動(dòng)到 position 的位置,然后拿到對(duì)應(yīng)的 _id 列的 id 校哎,最后用數(shù)據(jù)庫(kù)對(duì)象的 delete方法刪除該id的這一行數(shù)據(jù),代碼如下:
//刪
public void deleteData(int positon) {
Cursor mCursor = mSimpleCursorAdapter.getCursor();
mCursor.moveToPosition(positon);
int itemId = mCursor.getInt(mCursor.getColumnIndex("_id"));
mDbWriter.delete("music_msg", "_id=?", new String[]{itemId + ""});
refreshListview();
}
這里主要說(shuō)一下數(shù)據(jù)庫(kù)對(duì)象的 delete方法的3個(gè)參數(shù)
- table:數(shù)據(jù)庫(kù)的表名
- whereClause:代表要?jiǎng)h除哪一列上的哪一行,這里出于安全性考慮闷哆,只告訴了“_id”列腰奋,然后用“=?”代替具體哪一行
- whereArgs:該參數(shù)給出具體哪一行
-
效果如圖:
七、改
- 學(xué)會(huì)了增和刪抱怔,其實(shí)就會(huì)了改劣坊,所謂的改就是移動(dòng)游標(biāo)到需要更改數(shù)據(jù)的位置,然后用ContentValues 存新的數(shù)據(jù)屈留,最后用update方法進(jìn)行數(shù)據(jù)替換更改局冰,這里希望讀者自己試著靠自己去理解,代碼如下:
//改
public void updateData(int positon) {
Cursor mCursor = mSimpleCursorAdapter.getCursor();
mCursor.moveToPosition(positon);
int itemId = mCursor.getInt(mCursor.getColumnIndex("_id"));
ContentValues mContentValues = new ContentValues();
mContentValues.put("songname", mEt_dialog_songName.getText().toString().trim());
mContentValues.put("singer", mEt_dialog_singer.getText().toString().trim());
mDbWriter.update("music_msg", mContentValues, "_id=?", new String[]{itemId + ""});
refreshListview();
}
- 具體我實(shí)現(xiàn)單擊ListView的某一項(xiàng)灌危,然后彈出一個(gè)AlertDialog進(jìn)行更改康二,代碼如下:
//單擊修改item
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, final View view, final int position, long l) {
View mView = View.inflate(MyListviewActivity.this, R.layout.dialog_et_layout, null); //將放置了兩個(gè)EditText的布局dialog_et_layout渲染成view對(duì)象
mEt_dialog_songName = (EditText) mView.findViewById(R.id.et_dialog_songname); //要用對(duì)應(yīng)布局的view對(duì)象去findViewById獲取控件對(duì)象
mEt_dialog_singer = (EditText) mView.findViewById(R.id.et_dialog_singer);
mEt_dialog_songName.setText(((TextView) view.findViewById(R.id.songname)).getText()); //獲取并顯示原來(lái)的歌曲
mEt_dialog_singer.setText(((TextView) view.findViewById(R.id.singer)).getText()); //獲取并顯示原來(lái)的歌手
new AlertDialog.Builder(MyListviewActivity.this)
.setTitle("提示")
.setMessage("是否修改數(shù)據(jù)")
.setView(mView)
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
updateData(position);
}
})
.setNegativeButton("取消", null)
.show();
}
});
-
效果如圖:
八、查
- 實(shí)現(xiàn)輸入歌曲或歌手進(jìn)行查詢
-
首先我們需要一個(gè)輸入框和一個(gè)查詢按鈕勇蝙,對(duì)象分別為 mEt_input 和 btn_query沫勿,像這樣的就行
- 跟增刪改一樣,我們還是自定義一個(gè)方法 queryData()味混,然后首先獲取輸入框 mEt_input 的值产雹,代碼如下:
String mInput = mEt_input.getText().toString().trim();
- 我們的思路是這樣的:由于不知道輸入進(jìn)來(lái)的 mInput 是歌曲還是歌手,我們需要進(jìn)行判斷翁锡,如果是歌曲蔓挖,那么就去數(shù)據(jù)庫(kù)中找該歌曲名字對(duì)應(yīng)的所有歌手,放在List<Map>里馆衔,最后用 AlertDialog 進(jìn)行對(duì)話框列表顯示瘟判;如果是歌手,也是用同樣的方法哈踱。
-
那么如何判斷呢荒适?還記得我們?cè)俳榻B第四部分“刷新ListView”的時(shí)候用到的數(shù)據(jù)庫(kù)對(duì)象的 query() 方法嗎?當(dāng)時(shí)沒(méi)作具體參數(shù)分析开镣,現(xiàn)在就來(lái)介紹一下
- table:代表想要查詢的數(shù)據(jù)庫(kù)表名
- columns:代表想要查詢的結(jié)果的列(相當(dāng)于select語(yǔ)句的select關(guān)鍵字后面的部分)
- selection:代表想要通過(guò)這些列作為查詢的條件(相當(dāng)于select語(yǔ)句的where關(guān)鍵字后面的部分刀诬,允許使用占位符“?”)
- selectionArgs:與上面的 selection 參數(shù)有關(guān)邪财,用于為 selection 子句中的占位符傳入?yún)?shù)值陕壹,代表是這些列的具體的數(shù)據(jù),也就知道是哪些行了
- groupBy:用于控制分組(相當(dāng)于select語(yǔ)句的group by關(guān)鍵字后面的部分)
- having:用于對(duì)分組進(jìn)行過(guò)濾(相當(dāng)于select語(yǔ)句的having關(guān)鍵字后面的部分)
- orderBy:用于對(duì)記錄進(jìn)行排序(相當(dāng)于select語(yǔ)句的order by關(guān)鍵字后面的部分)
- 正如上面所說(shuō)的树埠,因?yàn)椴恢垒斎氲氖歉枨€是歌手糠馆,所以要查詢兩次
Cursor mCursor1 = mDbReader.query("music_msg", new String[]{"singer"}, "songname=?", new String[]{mInput}, null, null, null);
Cursor mCursor2 = mDbReader.query("music_msg", new String[]{"songname"}, "singer=?", new String[]{mInput}, null, null, null);
若mCursor1.getCount() > 0,表示輸入的是歌曲怎憋,并且有查詢到結(jié)果又碌,這時(shí)定義一個(gè)全局變量List<Map<String, String>> mStringList = new ArrayList<>();然后通過(guò)游標(biāo)的向下滑動(dòng)九昧,把歌曲和歌名都存進(jìn)List里面,最后別忘了關(guān)閉游標(biāo)毕匀。具體看代碼去理解:
//查
public void queryData() {
String mInput = mEt_input.getText().toString().trim();
//第二個(gè)參數(shù)是你需要查找的列
//第三和第四個(gè)參數(shù)確定是從哪些行去查找第二個(gè)參數(shù)的列
Cursor mCursor1 = mDbReader.query("music_msg", new String[]{"singer"}, "songname=?", new String[]{mInput}, null, null, null);
Cursor mCursor2 = mDbReader.query("music_msg", new String[]{"songname"}, "singer=?", new String[]{mInput}, null, null, null);
if (mCursor1.getCount() > 0) {
mStringList.clear(); //清空List
while (mCursor1.moveToNext()) { //游標(biāo)總是在查詢到的上一行
Map<String, String> mMap = new HashMap<>();
String output_singer = mCursor1.getString(mCursor1.getColumnIndex("singer"));
mMap.put("tv1", mInput);
mMap.put("tv2", output_singer);
mStringList.add(mMap);
}
mCursor1.close();
} else if (mCursor2.getCount() > 0) {
mStringList.clear(); //清空List
while (mCursor2.moveToNext()) { //游標(biāo)總是在查詢到的上一行
Map<String, String> mMap = new HashMap<>();
String output_songname = mCursor2.getString(mCursor2.getColumnIndex("songname"));
mMap.put("tv1", output_songname);
mMap.put("tv2", mInput);
mStringList.add(mMap);
}
mCursor2.close();
} else {
mStringList.clear(); //清空List
Map<String, String> mMap = new HashMap<>();
mMap.put("tv1", "未能查詢到結(jié)果");
mStringList.add(mMap);
}
}
- 最后通過(guò)查詢按鈕 btn_query 的點(diǎn)擊監(jiān)聽(tīng)事件去調(diào)用上面的 queryData()方法铸鹰,然后用AlertDialog 進(jìn)行對(duì)話框列表顯示郎仆,代碼如下:
mBtn_query.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
queryData();
mEt_input.setText(""); //清空輸入框
new AlertDialog.Builder(MyListviewActivity.this)
.setTitle("查詢結(jié)果")
.setAdapter(new SimpleAdapter(MyListviewActivity.this, mStringList, R.layout.dialog_tv_layout, new String[]{"tv1", "tv2"}, new int[]{R.id.tv1_dialog, R.id.tv2_dialog}), null)
.setPositiveButton("確定", null)
.show();
}
});
其中AlertDialog的setAdapter方法跟ListView設(shè)置SimpleAdapter基本一樣喊括,只是最后那個(gè)點(diǎn)擊監(jiān)聽(tīng)設(shè)置為null咬腕。
-
效果如圖:
后話
- 完整代碼已上傳Github舆声,如有疑問(wèn)或表述有誤的地方钻趋,歡迎在評(píng)論區(qū)交流张惹。
- Github:https://github.com/TheCoffeeNoSugar/ListviewAndDatabase