MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

定义Android SQLite应用的用户界面

2023-01-104.8k 阅读

1. 理解 Android SQLite 应用的用户界面需求

在 Android 应用开发中,SQLite 数据库常被用于本地数据存储。用户界面(UI)则是连接用户与 SQLite 数据的桥梁,优秀的 UI 设计能极大提升用户体验。

首先要明确数据的展示与交互需求。例如,若应用是一个笔记类工具,用户需要查看已有的笔记列表,这就要求 UI 有一个列表视图来展示笔记的关键信息,如标题、创建时间等。同时,用户还需能点击列表项进入详细笔记界面进行查看、编辑和删除操作。

从数据录入角度看,应用应提供友好的输入界面。对于笔记应用,可能需要文本编辑框让用户输入笔记标题和内容,也许还会有日期选择器来设定笔记的特定日期等。

2. 使用布局文件构建基础 UI 结构

2.1 线性布局(LinearLayout)

线性布局是 Android 中最基本的布局之一,它可以让子视图在水平或垂直方向上依次排列。假设我们要创建一个简单的登录界面,用于连接 SQLite 数据库(虽然登录不一定直接依赖本地 SQLite,但以此为例说明布局应用)。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="用户名"/>

    <EditText
        android:id="@+id/password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="密码"
        android:inputType="textPassword"/>

    <Button
        android:id="@+id/login_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="登录"/>

</LinearLayout>

在上述代码中,LinearLayout 设置为垂直方向排列。其中有两个 EditText 用于输入用户名和密码,一个 Button 用于触发登录操作。这种布局方式简单明了,适用于许多基础界面的构建。

2.2 相对布局(RelativeLayout)

相对布局允许子视图通过相对位置来排列。比如我们要创建一个包含图片和文字描述的界面,用于展示 SQLite 数据库中存储的商品信息。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/product_image"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:src="@drawable/product_default"/>

    <TextView
        android:id="@+id/product_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="商品名称"
        android:layout_toRightOf="@id/product_image"
        android:layout_marginLeft="16dp"
        android:textSize="20sp"/>

    <TextView
        android:id="@+id/product_price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="价格"
        android:layout_below="@id/product_name"
        android:layout_toRightOf="@id/product_image"
        android:layout_marginLeft="16dp"
        android:textSize="18sp"/>

</RelativeLayout>

这里,ImageView 显示商品图片,TextView 显示商品名称和价格,通过相对布局的方式,让文字信息围绕在图片右侧,合理利用空间。

3. 列表视图(ListView)用于数据展示

在 Android SQLite 应用中,列表视图常用于展示数据库中的多条记录。例如,展示联系人列表、任务列表等。

3.1 创建 ListView

首先在布局文件中添加 ListView

<ListView
    android:id="@+id/contacts_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

3.2 自定义列表项布局

为了让列表项显示我们期望的样式,需要创建一个自定义的列表项布局文件。假设我们要展示联系人的姓名和电话号码。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:padding="16dp">

    <TextView
        android:id="@+id/contact_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"/>

    <TextView
        android:id="@+id/contact_phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:layout_marginLeft="16dp"/>

</LinearLayout>

3.3 使用适配器填充 ListView

适配器是连接数据和 ListView 的桥梁。我们可以创建一个自定义适配器来适配联系人数据。

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.List;

public class ContactAdapter extends BaseAdapter {

    private Context context;
    private List<Contact> contacts;

    public ContactAdapter(Context context, List<Contact> contacts) {
        this.context = context;
        this.contacts = contacts;
    }

    @Override
    public int getCount() {
        return contacts.size();
    }

    @Override
    public Object getItem(int position) {
        return contacts.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.contact_item, parent, false);
        }

        TextView nameTextView = convertView.findViewById(R.id.contact_name);
        TextView phoneTextView = convertView.findViewById(R.id.contact_phone);

        Contact contact = contacts.get(position);
        nameTextView.setText(contact.getName());
        phoneTextView.setText(contact.getPhone());

        return convertView;
    }
}

这里的 Contact 类是一个自定义的数据类,包含姓名和电话号码等字段。

public class Contact {
    private String name;
    private String phone;

    public Contact(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }

    public String getName() {
        return name;
    }

    public String getPhone() {
        return phone;
    }
}

在 Activity 中,我们从 SQLite 数据库获取数据,并设置适配器。

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.widget.ListView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ListView contactsListView;
    private ContactAdapter contactAdapter;
    private List<Contact> contacts = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        contactsListView = findViewById(R.id.contacts_list);

        // 从 SQLite 数据库获取数据
        SQLiteDatabase db = openOrCreateDatabase("contacts.db", MODE_PRIVATE, null);
        Cursor cursor = db.rawQuery("SELECT * FROM contacts", null);
        if (cursor.moveToFirst()) {
            do {
                String name = cursor.getString(cursor.getColumnIndex("name"));
                String phone = cursor.getString(cursor.getColumnIndex("phone"));
                contacts.add(new Contact(name, phone));
            } while (cursor.moveToNext());
        }
        cursor.close();
        db.close();

        contactAdapter = new ContactAdapter(this, contacts);
        contactsListView.setAdapter(contactAdapter);
    }
}

4. 详情界面设计与数据交互

当用户点击列表项进入详情界面时,我们需要展示该条记录的详细信息,并提供编辑和删除等操作功能。

4.1 创建详情界面布局

假设我们的详情界面用于展示书籍信息,布局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/book_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:textStyle="bold"/>

    <TextView
        android:id="@+id/book_author"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"/>

    <TextView
        android:id="@+id/book_description"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:layout_marginTop="16dp"/>

    <Button
        android:id="@+id/edit_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="编辑"
        android:layout_marginTop="16dp"/>

    <Button
        android:id="@+id/delete_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="删除"
        android:layout_marginTop="16dp"/>

</LinearLayout>

4.2 传递数据到详情界面

在列表项点击事件中,我们通过 Intent 传递数据。

contactsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Contact contact = contacts.get(position);
        Intent intent = new Intent(MainActivity.this, ContactDetailActivity.class);
        intent.putExtra("name", contact.getName());
        intent.putExtra("phone", contact.getPhone());
        startActivity(intent);
    }
});

4.3 在详情界面展示和操作数据

在详情界面的 Activity 中接收数据并展示。

import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class ContactDetailActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contact_detail);

        TextView nameTextView = findViewById(R.id.contact_name);
        TextView phoneTextView = findViewById(R.id.contact_phone);

        Intent intent = getIntent();
        if (intent != null) {
            String name = intent.getStringExtra("name");
            String phone = intent.getStringExtra("phone");
            nameTextView.setText(name);
            phoneTextView.setText(phone);
        }
    }
}

对于编辑和删除操作,编辑可以通过启动一个新的包含输入框的界面,让用户修改数据后保存;删除则直接在数据库中删除对应记录。

Button deleteButton = findViewById(R.id.delete_button);
deleteButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        SQLiteDatabase db = openOrCreateDatabase("contacts.db", MODE_PRIVATE, null);
        db.delete("contacts", "name =? AND phone =?", new String[]{name, phone});
        db.close();
        finish();
    }
});

5. 数据输入界面设计

数据输入界面是用户向 SQLite 数据库添加新记录的入口。设计一个良好的数据输入界面,需要考虑用户输入的便捷性和准确性。

5.1 创建输入界面布局

以添加任务为例,输入界面布局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/task_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="任务标题"/>

    <EditText
        android:id="@+id/task_description"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="任务描述"
        android:inputType="textMultiLine"
        android:minLines="3"/>

    <Button
        android:id="@+id/save_task_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存任务"/>

</LinearLayout>

5.2 处理输入数据并保存到数据库

Activity 中获取输入数据并保存到 SQLite 数据库。

import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import androidx.appcompat.app.AppCompatActivity;

public class AddTaskActivity extends AppCompatActivity {

    private EditText taskTitleEditText;
    private EditText taskDescriptionEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_task);

        taskTitleEditText = findViewById(R.id.task_title);
        taskDescriptionEditText = findViewById(R.id.task_description);

        Button saveButton = findViewById(R.id.save_task_button);
        saveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String title = taskTitleEditText.getText().toString();
                String description = taskDescriptionEditText.getText().toString();

                SQLiteDatabase db = openOrCreateDatabase("tasks.db", MODE_PRIVATE, null);
                db.execSQL("INSERT INTO tasks (title, description) VALUES (?,?)", new String[]{title, description});
                db.close();

                finish();
            }
        });
    }
}

6. UI 与 SQLite 交互的最佳实践

  • 数据验证:在数据输入界面,对用户输入的数据进行验证。比如,对于邮箱地址输入框,验证输入是否符合邮箱格式;对于密码输入,检查长度是否符合要求等。这可以防止非法数据进入数据库,保证数据的完整性和一致性。
  • 事务处理:当进行多个数据库操作时,如同时插入多条记录或先插入后更新等,使用事务处理。这样可以确保要么所有操作都成功,要么所有操作都失败,避免数据处于不一致状态。
SQLiteDatabase db = openOrCreateDatabase("example.db", MODE_PRIVATE, null);
db.beginTransaction();
try {
    db.execSQL("INSERT INTO table1 (column1, column2) VALUES (?,?)", new String[]{"value1", "value2"});
    db.execSQL("UPDATE table2 SET column3 =? WHERE id =?", new String[]{"new_value", "1"});
    db.setTransactionSuccessful();
} finally {
    db.endTransaction();
}
  • 优化查询:在从 SQLite 数据库获取数据用于 UI 展示时,尽量优化查询语句。例如,只选择需要展示的列,而不是使用 SELECT *。同时,合理使用索引可以大大提高查询效率。
-- 优化前
SELECT * FROM users;

-- 优化后
SELECT username, email FROM users;
  • 缓存机制:对于不经常变化的数据,可以在本地内存或文件中进行缓存。这样在 UI 需要展示数据时,可以先从缓存中获取,减少数据库查询次数,提高应用响应速度。例如,对于一些配置信息、静态数据等。

7. 处理 UI 与 SQLite 交互中的异常

在 UI 与 SQLite 交互过程中,可能会出现各种异常,如数据库打开失败、SQL 语句执行错误等。

7.1 数据库打开异常

当使用 openOrCreateDatabase 方法打开或创建数据库时,可能会因为权限问题、存储空间不足等原因失败。

try {
    SQLiteDatabase db = openOrCreateDatabase("example.db", MODE_PRIVATE, null);
    // 数据库操作
} catch (Exception e) {
    e.printStackTrace();
    // 提示用户数据库打开失败,可能是权限问题或存储空间不足
    Toast.makeText(this, "数据库打开失败,请检查权限或存储空间", Toast.LENGTH_SHORT).show();
}

7.2 SQL 语句执行异常

SQL 语句可能存在语法错误、数据类型不匹配等问题导致执行失败。

try {
    SQLiteDatabase db = openOrCreateDatabase("example.db", MODE_PRIVATE, null);
    db.execSQL("INSERT INTO users (username, password) VALUES (?,?)", new Object[]{"test_user", 12345}); // 这里密码数据类型错误
} catch (SQLException e) {
    e.printStackTrace();
    // 提示用户 SQL 语句执行错误
    Toast.makeText(this, "SQL 语句执行错误,请检查输入或联系开发者", Toast.LENGTH_SHORT).show();
}

通过合理处理这些异常,可以提高应用的稳定性和用户体验,避免应用因异常而崩溃。

8. 适配不同屏幕尺寸和方向

Android 设备具有多种屏幕尺寸和方向,良好的 UI 设计需要适配这些变化。

8.1 使用布局限定符

通过创建不同的布局文件,利用布局限定符来适配不同屏幕尺寸。例如,在 res/layout 目录下存放普通布局,在 res/layout-large 目录下存放适用于大屏幕设备的布局。

<!-- res/layout/main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- 普通布局内容 -->
</LinearLayout>

<!-- res/layout-large/main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <!-- 大屏幕布局内容,可根据需要调整布局方向和控件大小等 -->
</LinearLayout>

8.2 处理屏幕方向变化

当屏幕方向发生变化时,Activity 会默认重新创建。如果不想让 Activity 重新创建,可以在 AndroidManifest.xml 中指定 configChanges 属性。

<activity android:name=".MainActivity"
    android:configChanges="orientation|screenSize">
    <!-- 其他配置 -->
</activity>

然后在 Activity 中重写 onConfigurationChanged 方法来处理屏幕方向变化。

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        // 横屏处理
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
        // 竖屏处理
    }
}

通过以上方式,可以让应用在不同屏幕尺寸和方向下都能提供良好的用户体验,使得 UI 与 SQLite 数据交互在各种设备上都能稳定、高效地进行。