前言
ListView與數據庫結合使用也是一種非常常見的手段。例如歌單的ListView,在我們重新啟動應用的時候數據依舊存在,如下圖:具體實現
一、準備Activity和ListView
- Activity要繼承ListActivity或AppCompatActivity(推薦后者)
- ListView不需要先準備適配器Adapter,只需要初始化好ListView就行了
二、創建數據庫
- 新建一個類MySQLite繼承自SQLiteOpenHelper,然后去實現未實現的3個方法,分別是:
1、構造方法
MySQLite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
//this.mContext = context; //自定義變量mContext
}
其中形式參數的意義分別是:
- context:上下文,即當前Activity的對象 this
- name:即將創建的數據庫的表的名字,例如:"user.db"、"music_msg.db"
- factory:游標,一般都是 null
- version:數據庫版本號,用于更新數據庫版本, 例如:1
所以也可以寫成
MySQLite(Context context) {
super(context, "music_msg.db", null, 1);
this.mContext = context;
}
2、onCreate方法,用于創建數據庫的表
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("create table music_msg(_id integer primary key autoincrement, singer varchar, songname varchar, url varchar)");
}
通過execSQL()方法執行SQL語句創建數據庫的表
如果你不懂數據庫,那你只需要改變下圖框出的部分,第一個是表名,第二個是變量名和數據類型
3、onUpgrade方法,用于升級軟件時更新數據庫表結構,此方法在數據庫的版本發生變化時會被調用
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
Toast.makeText(mContext, "onUpgrade", Toast.LENGTH_SHORT).show();
}
該方法這里不做演示分析,只是彈個Toast,想具體了解的可自行谷歌百度
三、關聯ListView與數據庫
- 關聯的關鍵就是ListView的適配器SimpleCursorAdapter,構造方法與SimpleAdapter有些類似
SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags)
其中形式參數的意義分別是:
context:上下文,即當前Activity的對象 this
layout:ListView對應的布局文件
c:游標對象,此處可以設為 null
from:想從數據庫表中拿的數據的列名,放在new出來的String數組中
to:將 layout 里面的控件的 id 寫進new出來的 int 數組中,分別對應 from 里的數據
flags:標志用于確定適配器的行為,用常量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設置適配器
refreshListview(); //自定義的方法,用于當數據列表改變時刷新ListView
四、刷新ListView(很關鍵)
- 當我們對數據進行增刪改的時候,就需要將ListView刷新顯示
-
自定義一個方法refreshListview(),里面關鍵的方法是適配器對象mSimpleCursorAdapter的changeCursor(Cursor cursor)方法,因為需要傳遞一個參數cursor,所以我們需要得到這個Cursor對象,通過數據庫對象的查詢方法query就能返回一個Cursor對象,query方法如下圖:
這里我們暫時只需要設置table參數,即數據庫的表名,先不管另外6個參數是什么(后面會再詳細說明),此處全部設置為null就行了,即:
Cursor mCursor = mDbWriter.query("music_msg", null, null, null, null, null, null);
上面說到要用數據庫的對象去調用query方法,所以我們就得先拿到數據庫的對象。數據庫對象的獲取有兩個方法——getWritableDatabase() 和 getReadableDatabase()
- SQLiteDatabase getWritableDatabase() :以讀寫的方式打開數據庫對應的SQLiteDatabase對象,如果打開的數據庫磁盤滿了,此時只能讀不能寫,此時調用了 getWritableDatabase 的實例,那么將會發生錯誤(異常)
- SQLiteDatabase getReadableDatabase():以讀寫的方式打開數據庫對應的SQLiteDatabase對象,如果數據庫的磁盤空間滿了,就會打開失敗,當打開失敗后會繼續嘗試以只讀方式打開數據庫。如果該問題成功解決,則只讀數據庫對象就會關閉,然后返回一個可讀寫的數據庫對象
要調用這兩個方法,先再Activity的onCreate()方法里實例化之前自定義的MySQLite類,代碼如下:
MySQLite mMySQLite = new MySQLite(this);
SQLiteDatabase mDbWriter = mMySQLite.getWritableDatabase();
SQLiteDatabase mDbReader = mMySQLite.getReadableDatabase();
- 接著就可以寫出refreshListview()方法了,代碼如下:
//刷新數據列表
public void refreshListview() {
Cursor mCursor = mDbWriter.query("music_msg", null, null, null, null, null, null);
mSimpleCursorAdapter.changeCursor(mCursor);
}
五、增
- 實現往數據庫中增加一條數據并在ListView中顯示
-
先簡單寫一個UI界面:在有ListView的Activity的底部增加兩個文本輸入框和一個按鈕,如圖:
這里的ListView沒有數據所以看不見,兩個EditText的對象分別為 mEt_songName 和 mEt_singer ,Button的對象為 btn_insert,這三個對象后面要用到
- 數據的存儲是通過ContentValues 類實現的,ContentValues 類跟Hashtable 類很相似,也是通過put(key, value) 方法暫存的,其中鍵值對的鍵key只能是String類型,值value只能是基本類型。于是我們可以new出ContentValues 類的對象,然后將EditText上的數據作為值value存起來,再通過數據庫對象的 insert() 方法將數據插入數據庫表中,最后調用refreshListview()方法刷新數據列表,將數據在ListView中顯示出來,代碼如下:
//增
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個參數
table:想插入的數據庫的表名
nullColunmHack:代表強行插入null值的數據列的列名,這里設置為 null 就行
values:存放了數據的ContentValues 對象
這里將其封裝成一個insertData()方法,然后在添加按鈕mBtn_insert的點擊事件中調用,代碼如下:
mBtn_insert.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
insertData();
mEt_songName.setText(""); //數據添加完成后,把文本輸入框清空,方便下次輸入
mEt_singer.setText("");
}
});
-
效果如圖:
六、刪
- 實現刪除ListView的一條數據并將數據庫對應的數據也刪除
- 這里我們實現長按ListView的某一項item,然后彈出一個對話框,確認是否刪除,確定的話就刪除該數據,取消的話就不做任何處理
-
要想刪除數據庫的某一條數據,我們就得先知道是ListView的哪一項,所以我們先寫ListView的長按事件監聽,并且添加一個AlertDialog,代碼如圖:
可以注意到上面有3個紅色的框框,第一個框框是 final關鍵字,因為其修飾的position變量在匿名內部類中需要被使用到,所以需要在前面添加 final 關鍵字。第二個框框是一個自定義的方法,其作用是刪除LIstView的第 position 項數據,后面會具體介紹。第三個框框是布爾變量,返回true代表響應該次監聽事件
- 從長按監聽中我們可以知道 position 就是我們要刪除項的位置,所以我們現在可以開始寫刪除功能。我們自定義一個 deleteData(int positon) 方法,參數就是剛才那個 position 。思路是這樣的,拿到數據庫表的游標,讓它移動到 position 的位置,然后拿到對應的 _id 列的 id ,最后用數據庫對象的 delete方法刪除該id的這一行數據,代碼如下:
//刪
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();
}
這里主要說一下數據庫對象的 delete方法的3個參數
- table:數據庫的表名
- whereClause:代表要刪除哪一列上的哪一行,這里出于安全性考慮,只告訴了“_id”列,然后用“=?”代替具體哪一行
- whereArgs:該參數給出具體哪一行
-
效果如圖:
七、改
- 學會了增和刪,其實就會了改,所謂的改就是移動游標到需要更改數據的位置,然后用ContentValues 存新的數據,最后用update方法進行數據替換更改,這里希望讀者自己試著靠自己去理解,代碼如下:
//改
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();
}
- 具體我實現單擊ListView的某一項,然后彈出一個AlertDialog進行更改,代碼如下:
//單擊修改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); //將放置了兩個EditText的布局dialog_et_layout渲染成view對象
mEt_dialog_songName = (EditText) mView.findViewById(R.id.et_dialog_songname); //要用對應布局的view對象去findViewById獲取控件對象
mEt_dialog_singer = (EditText) mView.findViewById(R.id.et_dialog_singer);
mEt_dialog_songName.setText(((TextView) view.findViewById(R.id.songname)).getText()); //獲取并顯示原來的歌曲
mEt_dialog_singer.setText(((TextView) view.findViewById(R.id.singer)).getText()); //獲取并顯示原來的歌手
new AlertDialog.Builder(MyListviewActivity.this)
.setTitle("提示")
.setMessage("是否修改數據")
.setView(mView)
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
updateData(position);
}
})
.setNegativeButton("取消", null)
.show();
}
});
-
效果如圖:
八、查
- 實現輸入歌曲或歌手進行查詢
-
首先我們需要一個輸入框和一個查詢按鈕,對象分別為 mEt_input 和 btn_query,像這樣的就行
- 跟增刪改一樣,我們還是自定義一個方法 queryData(),然后首先獲取輸入框 mEt_input 的值,代碼如下:
String mInput = mEt_input.getText().toString().trim();
- 我們的思路是這樣的:由于不知道輸入進來的 mInput 是歌曲還是歌手,我們需要進行判斷,如果是歌曲,那么就去數據庫中找該歌曲名字對應的所有歌手,放在List<Map>里,最后用 AlertDialog 進行對話框列表顯示;如果是歌手,也是用同樣的方法。
-
那么如何判斷呢?還記得我們再介紹第四部分“刷新ListView”的時候用到的數據庫對象的 query() 方法嗎?當時沒作具體參數分析,現在就來介紹一下
- table:代表想要查詢的數據庫表名
- columns:代表想要查詢的結果的列(相當于select語句的select關鍵字后面的部分)
- selection:代表想要通過這些列作為查詢的條件(相當于select語句的where關鍵字后面的部分,允許使用占位符“?”)
- selectionArgs:與上面的 selection 參數有關,用于為 selection 子句中的占位符傳入參數值,代表是這些列的具體的數據,也就知道是哪些行了
- groupBy:用于控制分組(相當于select語句的group by關鍵字后面的部分)
- having:用于對分組進行過濾(相當于select語句的having關鍵字后面的部分)
- orderBy:用于對記錄進行排序(相當于select語句的order by關鍵字后面的部分)
- 正如上面所說的,因為不知道輸入的是歌曲還是歌手,所以要查詢兩次
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,表示輸入的是歌曲,并且有查詢到結果,這時定義一個全局變量List<Map<String, String>> mStringList = new ArrayList<>();然后通過游標的向下滑動,把歌曲和歌名都存進List里面,最后別忘了關閉游標。具體看代碼去理解:
//查
public void queryData() {
String mInput = mEt_input.getText().toString().trim();
//第二個參數是你需要查找的列
//第三和第四個參數確定是從哪些行去查找第二個參數的列
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()) { //游標總是在查詢到的上一行
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()) { //游標總是在查詢到的上一行
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", "未能查詢到結果");
mStringList.add(mMap);
}
}
- 最后通過查詢按鈕 btn_query 的點擊監聽事件去調用上面的 queryData()方法,然后用AlertDialog 進行對話框列表顯示,代碼如下:
mBtn_query.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
queryData();
mEt_input.setText(""); //清空輸入框
new AlertDialog.Builder(MyListviewActivity.this)
.setTitle("查詢結果")
.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設置SimpleAdapter基本一樣,只是最后那個點擊監聽設置為null。
-
效果如圖:
后話
- 完整代碼已上傳Github,如有疑問或表述有誤的地方,歡迎在評論區交流。
- Github:https://github.com/TheCoffeeNoSugar/ListviewAndDatabase