Android——ListView與數據庫的結合

前言

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。

  • 效果如圖:


后話

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容