android - 使用 ListView 在下一个活动中显示数据库详细信息
问题描述
我是 Android Studio 的新手,
我已经有 3 列的数据库文件(ID、标题、详细信息)
我想从数据库创建一个包含“标题”的 ListView,当我单击其中一个标题时,它将前往下一个活动并显示我之前单击的标题中的“详细信息”。
数据库文件位于 assets 文件夹中。
我正在使用 Android Studio 最新版本,请帮助我获取 xml、java 和清单代码。谢谢你。
这是我的代码,我只是成功地在 ListView 中显示了“标题”列,其余的我不知道该怎么做。
MainActivity.java
public class MainActivity extends AppCompatActivity {
static final String DBNAME = "story.db";
static final String DBASSETPATH = "databases/" + DBNAME;
static final String DBTABLE = "table";
static final String DBTITLE = "title";
static final String IDCOLUMN = "_id";
ListView mTableList;
SQLiteDatabase mDB;
SimpleCursorAdapter mSCA;
Cursor mCsr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTableList = (ListView) this.findViewById(R.id.storylist);
mDB = openStoryDB();
if (mDB != null) {
mCsr = mDB.query(DBTABLE,
new String[]{IDCOLUMN + " AS _id",
DBTITLE
},
null,null,null,null,null);
mSCA = new SimpleCursorAdapter(this,android.R.layout.simple_list_item_1,mCsr,
new String[]{DBTITLE},
new int[]{android.R.id.text1},0);
mTableList.setAdapter(mSCA);
} else {
Toast.makeText(this,"Unable to open Database.",Toast.LENGTH_LONG);
}
}
private SQLiteDatabase openStoryDB() {
String dbpath = this.getDatabasePath(DBNAME).getPath();
if (this.getDatabasePath(DBNAME).exists()) {
Log.d("OPENSTORYDB","Opening already existing Database");
return SQLiteDatabase.openDatabase(dbpath,null,SQLiteDatabase.OPEN_READWRITE);
}
InputStream is;
byte[] buffer;
FileOutputStream db;
try {
is = this.getAssets().open(DBASSETPATH);
buffer = new byte[is.available()];
is.read(buffer);
is.close();
} catch (Exception e) {
e.printStackTrace();
Log.d("OPENSTORYDB","Unable to locate or buffer input from assets " + DBASSETPATH);
return null;
}
// Just in case the databases directory doesn't exist create it.
File dbmkdir = (this.getDatabasePath(DBNAME)).getParentFile();
dbmkdir.mkdirs();
try {
db = new FileOutputStream(this.getDatabasePath(DBNAME).getPath());
} catch (Exception e) {
e.printStackTrace();
Log.d("OPENSTORYDB","Unable to create outputstream for DB at path " + dbpath);
try {
is.close();
} catch (Exception e2) {
}
return null;
}
try {
db.write(buffer);
db.flush();
db.close();
is.close();
} catch (Exception e) {
Log.d("OPENSTORYDB","Failed to copy asset to DB");
e.printStackTrace();
return null;
}
return SQLiteDatabase.openDatabase(dbpath,null,SQLiteDatabase.OPEN_READWRITE);
}
}
MainActivity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.book.story.MainActivity">
<ListView
android:id="@+id/storylist"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
解决方案
你需要
- 将 ListView 添加到初始活动的适当布局中。
- 创建一个数据库助手类,它将:
- 检查数据库是否已经存在
- 如果数据库不存在,则从 assets 文件夹中复制数据库
- 根据需要提供访问数据的方法(在填充 Listview 的情况下,将需要一个方法来检索要列出的数据)
- 在初始活动中获取数据库助手类的实例
- 通过它的 id 获取 ListView。
- 为 ListView 实例化一个合适的适配器。
- 将 ListView 设置为使用适配器
- 将 onItemClick 侦听器添加到 ListView,它将从所选项目中提取足够的详细信息(id),使用详细信息设置 Intent extra(s),然后启动其他活动(然后可以从 Intent 中提取详细信息)。
例子
以下作为基于您的问题的示例。
数据库
名为mydb的数据库,因此是资产文件夹中名为mydb的文件,其中包含表mytable:-
布局
一个简单的布局,包括一个已被赋予 ID 的 Listview,已用于名为MainActivity的活动,ListView 的代码是:-
<ListView
android:id="@+id/myListView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
数据库助手
数据库助手已创建为DBHelper.java。实例化时,它会检查数据库是否作为文件存在,如果不存在,则尝试从资产文件夹中复制数据库(如果失败,例如资产不存在,则会引发运行时异常)。然后打开数据库。有一个公共方法getAllFromMytable返回一个 Cursor ,其中包含 mytable 表中的所有行:-
public class DBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "mydb"; //<<<<<<<<<< Database file name including extension
public static final int DBVERSION = 101;
public static final String TBL_MYTABLE = "mytable"; //<<<<<<<<<< The table name
public static final String COL_MYTABLE_ID = "id"; //<<<<<<<<<< The id column name
public static final String COl_MYTABLE_TITLE = "title"; //<<<<<<<<<< The title column name
public static final String COL_MYTABLE_DETAILS = "details"; //<<<<<<<<<< The details column name
Context mContext;
SQLiteDatabase mDB;
public DBHelper(Context context) {
super(context, DBNAME, null, DBVERSION);
mContext = context;
if(!ifDBExists()) {
copyDatabaseFromAssetsFolder();
}
mDB = this.getWritableDatabase();
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
super.onDowngrade(db, oldVersion, newVersion);
}
public Cursor getAllFromMytable() {
String[] columns = new String[]{"*, id AS " + BaseColumns._ID}; //<<<<<<<<<< need column named _id so generate it
return mDB.query(TBL_MYTABLE,columns,null,null,null,null,null);
}
private void copyDatabaseFromAssetsFolder() {
int stage = 0, buffer_size = 4096, blocks_copied = 0, bytes_copied = 0;
try {
InputStream is = mContext.getAssets().open(DBNAME);
stage++;
OutputStream os = new FileOutputStream(mContext.getDatabasePath(DBNAME));
stage++;
byte[] buffer = new byte[buffer_size];
int length;
while ((length = is.read(buffer))>0) {
blocks_copied++;
os.write(buffer, 0, length);
bytes_copied += length;
}
os.flush();
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
String exception = "";
switch (stage) {
case 0:
exception = "Unable to open asset file " + DBNAME;
break;
case 1:
exception = "Unable to open the Database file " + DBNAME + " for output.";
break;
case 2:
exception = "Error whilst copying " + DBNAME +
" from the assets folder to " + mContext.getDatabasePath(DBNAME).toString() +
" - successfully copied " + String.valueOf(blocks_copied) + " blocks."
;
}
throw new RuntimeException(exception + " (see stack-trace above)");
}
}
// Check if the Database exists
private boolean ifDBExists() {
File db = mContext.getDatabasePath(DBNAME);
if (db.exists()) return true;
if (!db.getParentFile().exists()) {
db.getParentFile().mkdirs();
}
return false;
}
}
主要活动
这进行了 4-7,除了不是开始另一个活动,而是通过 Toast 显示 ListView 中单击项目的详细信息:-
public class MainActivity extends AppCompatActivity {
ListView mMyListView;
DBHelper mDBHlpr;
Cursor mCsr;
SimpleCursorAdapter mSCA;
Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
mDBHlpr = new DBHelper(this); //<<<<<<<<<< Instantiate the DBHelper
mMyListView = this.findViewById(R.id.myListView);
}
private void manageListView() {
mCsr = mDBHlpr.getAllFromMytable(); //<<<<<<<<<< get the latest data from the database
if (mSCA == null) {
mSCA = new SimpleCursorAdapter(
this,
android.R.layout.simple_list_item_1,mCsr,
new String[]{DBHelper.COl_MYTABLE_TITLE},
new int[]{android.R.id.text1},
0
);
mMyListView.setAdapter(mSCA);
mMyListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Start the other activity here passing the id (sufficient to find the specififc row)via an intent extra
// Toast used as an example of extracting the respective data from the cursor
Toast.makeText(
mContext,
"You clicked on the row with an id of " + String.valueOf(id) +
" the Title is " + mCsr.getString(mCsr.getColumnIndex(DBHelper.COl_MYTABLE_TITLE)) +
" the Details are " + mCsr.getString(mCsr.getColumnIndex(DBHelper.COL_MYTABLE_DETAILS)) +
" the id column is " + String.valueOf(mCsr.getLong(mCsr.getColumnIndex(DBHelper.COL_MYTABLE_ID))),
Toast.LENGTH_SHORT
).show();
}
});
} else {
mSCA.swapCursor(mCsr);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mCsr.close(); //<<<<<<<<<< ensure that the cursor is closed when done with
}
@Override
protected void onResume() {
super.onResume();
manageListView(); //<<<<<<<<<< refresh the listview
}
}
笔记
- 请参考可以通过搜索轻松获得的评论和/或适当的文档
- ManageListView不会直接调用,因为onResume方法在活动启动时运行。
结果
Toast 的屏幕截图:-
额外的
将 id 传递给另一个活动,然后提取该活动中的详细信息列。
首先,在数据库助手类中增加一个新方法,允许详细信息列作为字符串返回,并根据id获取
例如,上面的 DBHelper.java 将添加以下内容:-
public String getDetailsFromId(long id) {
String rv = "";
String whereclause = COL_MYTABLE_ID + "=?";
String[] whereargs = new String[]{String.valueOf(id)};
Cursor csr = mDB.query(TBL_MYTABLE,null,whereclause,whereargs,null,null,null);
if (csr.moveToFirst()) {
rv = csr.getString(csr.getColumnIndex(COL_MYTABLE_DETAILS));
}
csr.close();
return rv;
}
创建其他活动,确保它在清单中定义(使用 File/New/Activity 相应地修改清单)。
此活动可能类似于(这将根据用于启动活动的额外意图传递的 id 显示详细信息):-
public class OtherActivity extends AppCompatActivity {
public static final String INTENTKEY_MYTABLEIDCOLUMNS = "ikey_mytableidcolumn";
TextView mDetails;
DBHelper mDBHlpr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);
mDetails = this.findViewById(R.id.mydetails);
mDBHlpr = new DBHelper(this);
long id = this.getIntent().getLongExtra(INTENTKEY_MYTABLEIDCOLUMNS,-1);
mDetails.setText(mDBHlpr.getDetailsFromId(id));
}
}
最后修改初始活动以实例化可用于启动其他活动的 Intent,然后将 id 作为附加项并最后启动其他活动,例如以下可以在上述活动中使用而不是(以及如果你想要)吐司:-
mMyListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Start the other activity here passing the id (sufficient to find the specififc row)via an intent extra
// Toast used as an example of extracting the respective data from the cursor
/*
Toast.makeText(
mContext,
"You clicked on the row with an id of " + String.valueOf(id) +
" the Title is " + mCsr.getString(mCsr.getColumnIndex(DBHelper.COl_MYTABLE_TITLE)) +
" the Details are " + mCsr.getString(mCsr.getColumnIndex(DBHelper.COL_MYTABLE_DETAILS)) +
" the id column is " + String.valueOf(mCsr.getLong(mCsr.getColumnIndex(DBHelper.COL_MYTABLE_ID))),
Toast.LENGTH_SHORT
).show();
*/
Intent i = new Intent(mContext,OtherActivity.class);
i.putExtra(OtherActivity.INTENTKEY_MYTABLEIDCOLUMNS,id);
startActivity(i);
}
});
结果(根据单击项目时的其他活动(在本例中为第一个)):-
推荐阅读
- android - 为什么我不能为 textView (android) 应用边距?
- python - Python中具有单个等约束的最小二乘优化
- kubernetes - 将 Azure AD 与 AWS EKS 本机 OIDC 集成
- android - 在 android studio 中使用 SQLite 显示上次登录的用户数据
- react-native - 反应导航如何深度链接
- flutter - 当我导航回上一个屏幕时,为什么我的数据会重置?
- physics-engine - rotate(angle) 和 Matter.Body.rotate(body, angle) 有什么区别?使用 matter.min.js 库。何时以及为什么要一起使用它们?
- android - api30 之间的 api21 是否支持 avif?
- java - 在 AWS Kinesis 上上传媒体时获取“ErrorCode”:“KMS_KEY_ACCESS_DENIED”、“ErrorId”:4500
- javascript - Slick JS 和自定义动画