文本备忘录(安卓应用开发)

更新:20230408

修复BUG:新建备注录时,多次点击“保存”会创建多个副本。使用默认的rowid管理数据。APK安装文件和源代码已更新。

原文

本文将构建一个文本备忘录,应用命名为InfoBox,功能包括备忘录的添加、修改、删除和查询,数据使用SQLite数据库保存,基础知识可以参考《Java与Android移动应用开发:技术、方法与实践》一书,下面将主要讨论实现代码。

首先来看应用的两个界面,如图1。

图1

左侧为主界面,包括备忘录查询功能和添加按钮。右侧为编辑界面,可以编写新的备忘录信息,也可以修改或删除已存在的备忘录信息。下面先来看主界面的创建。

主界面

主界面命名为MainActivity,其布局如下(activity_main.xml)。

XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <androidx.appcompat.widget.SearchView
        android:id="@+id/searchView"
        android:textSize="22sp"
        android:layout_weight="9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"></androidx.appcompat.widget.SearchView>
    <Button android:id="@+id/btnAdd"
        android:text="+"
        android:textSize="22sp"
        android:layout_weight="1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"></Button>
</LinearLayout>

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"></ListView>
</LinearLayout>

其中,使用线性布局创建了三个组件,分别是搜索组件(searchView)、添加按钮(btnAdd)和查询结果列表(listView)。

主界面的Java代码部分如下(MainActivity.java)。

Java
package com.caohuayu.infobox;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;

import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;

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

public class MainActivity extends AppCompatActivity
implements View.OnClickListener, SearchView.OnQueryTextListener,
        AdapterView.OnItemClickListener{

    SearchView searchView = null;
    Button btnAdd = null;
    ListView listView=null;
    //
    List<String> dataId = new ArrayList<>();
    List<String> dataInfo = new ArrayList<>();
    private ArrayAdapter<String> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化数据库
        SQLiteDatabase db = openOrCreateDatabase(
                "infobox.db",MODE_PRIVATE,null);
        String sql = "create table if not exists "+
                "infobox(recid integer not null primary key,info text);";
        db.execSQL(sql);
        db.close();
        // 初始化控件
        searchView = (SearchView)findViewById(R.id.searchView);
        btnAdd = (Button)findViewById(R.id.btnAdd);
        listView = (ListView)findViewById(R.id.listView);
        //
        btnAdd.setOnClickListener(this);
        listView.setOnItemClickListener(this);
        //
        searchView.setSubmitButtonEnabled(true);
        searchView.setIconified(false);
        searchView.setOnQueryTextListener(this);
        // 绑定数据
        adapter = new ArrayAdapter<>(
                this,
                android.R.layout.simple_list_item_1,
                dataInfo);
        listView.setAdapter(adapter);
    }

    @Override
    protected void onStart() {
        super.onStart();
        //
        search(searchView.getQuery().toString());
    }

    /* 添加按钮 */
    @Override
    public void onClick(View v){
        // 显示
        Intent intent = new Intent(MainActivity.this,EditActivity.class);
        intent.putExtra("recid","0");
        startActivity(intent);
    }

    /* 搜索 */
    @Override
    public boolean onQueryTextSubmit(String query){
        search(query);
        return false;
    }

    @Override
    public boolean onQueryTextChange(String newText){
        search(newText);
        return false;
    }

    // 根据关键字搜索内容
    protected void search(String key){
        dataId.clear();
        dataInfo.clear();
        //
        SQLiteDatabase db = openOrCreateDatabase(
                "infobox.db",MODE_PRIVATE,null);
        String sql = "select recid,substr(info,1,15) as info "+
                "from infobox where info like '%"+key+
                "%' order by recid desc limit 100;";
        Cursor cur = db.rawQuery(sql,null);
        if(cur.getCount()>0){
            while(cur.moveToNext()){
                dataId.add(cur.getString(0));
                dataInfo.add(cur.getString(1)+"...");
            }
        }
        else{
            Toast.makeText(MainActivity.this,
                    "没有找到相关的信息",Toast.LENGTH_SHORT).show();
        }
        cur.close();
        db.close();
        //
        adapter.notifyDataSetChanged();
    }

    /* 响应列表 */
    @Override
    public void onItemClick(AdapterView<?> parent,View view,int position,long id){
        Intent intent = new Intent(MainActivity.this,EditActivity.class);
        intent.putExtra("recid",dataId.get((int)id));
        startActivity(intent);
    }
}

MainActivity类中自定义的对象包括:

  • searchView定义为SearchView类型,表示搜索组件。
  • btnAdd定义为Button类型,表示添加按钮。
  • listView定义为ListView类型,表示搜索结果列表组件。
  • dataId定义为List<String>类型,保存搜索结果中的记录ID。
  • dataInfo定义为 List<String>类型,保存搜索结果中的文本内容。
  • adapter定义为ArrayAdapter<String>类型,绑定搜索结果列表的适配器对象。

onCreate()方法中对这些对象进行初始化,并会在第一次运行时创建SQLite数据表(infobox),定义的字段包括:

  • recid,记录ID,定义为自增长整数。
  • info,备忘录信息,定义为文本类型。

onStart()方法中,当主界面回成为活动界面时,会根据searchView组件中的内容进行查询;点击搜索按钮或修改查询内容可以完成相同的操作。

搜索功能使用search()方法完成。方法中通过关键字查询info字段内容,返回最新的100条的备忘录信息,由dataId对象保存recid字段数据,由dataInfo对象保存info字段数据,并将dataInfo对象的数据绑定到listView组件中。

listView组件通过onItemClick()方法响应列表项的点击操作,并打开编辑界面进行修改或删除,同时向编辑界面传递记录ID数据(recid)。

onClick()方法中响应了添加按钮的操作。点击添加按钮同样打开编辑界面,并传递记录ID数据(recid)为0。

编辑界面

无论是添加新的备忘录,还是对已存在的备忘录进行编辑,都使用了编辑界面(EditActivity),其布局如下(activity_edit.xml)。

XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button android:id="@+id/btnReturn"
            android:text="返回"
            android:textSize="22sp"
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"></Button>
        <Button android:id="@+id/btnDelete"
            android:text="删除"
            android:textSize="22sp"
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"></Button>
        <Button android:id="@+id/btnSave"
            android:text="保存"
            android:textSize="22sp"
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"></Button>
    </LinearLayout>

    <EditText android:id="@+id/txtInfo"
        android:gravity="top"
        android:textSize="25sp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        android:scrollbarStyle="outsideOverlay"></EditText>
</LinearLayout>

其中定义了三个按钮,分别是返回(btnReturn)、删除(btnDelete)和保存(btnSave)按钮,此外,文本编辑使用了EditView组件(txtInfo)。

编辑界面的Java代码部分如下(EditActivity.java)。

Java
package com.caohuayu.infobox;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class EditActivity extends AppCompatActivity
implements  View.OnClickListener{
    // 当前记录ID,为0时表示新记录
    private long recid = 0;
    //
    Button btnDelete,btnSave,btnReturn;
    EditText txtInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_edit);
        // 确认记录ID
        Intent intent = getIntent();
        String s = intent.getStringExtra("recid");
        if(s==null || s.length()==0) recid=0;
        else recid = Long.valueOf(s);
        //Toast.makeText(this,String.valueOf(recid),Toast.LENGTH_LONG).show();
        // 控件初始化
        btnDelete = (Button)findViewById(R.id.btnDelete);
        btnSave = (Button)findViewById(R.id.btnSave);
        btnReturn = (Button)findViewById(R.id.btnReturn);
        txtInfo=(EditText)findViewById(R.id.txtInfo);
        btnDelete.setOnClickListener(this);
        btnSave.setOnClickListener(this);
        btnReturn.setOnClickListener(this);
        // 如果记录ID大于0则显示信息内容
        if(recid>0){
            SQLiteDatabase db = openOrCreateDatabase(
                    "infobox.db",MODE_PRIVATE,null);
            String sql = "select info from infobox where recid="+recid+";";
            Cursor cur = db.rawQuery(sql,null);
            if(cur.getCount()>0){
                cur.moveToFirst();
                txtInfo.setText(cur.getString(0));
            }
            cur.close();
            db.close();
        }
    }

    @Override
    public void onClick(View v){
        int vid = v.getId();
        if(vid==R.id.btnDelete){
            // 删除记录
            delete();
        }else if(vid==R.id.btnSave){
            // 保存记录
            save();
        }else if (vid==R.id.btnReturn){
            // 返回
            finish();
        }
    }

    // 删除当前记录
    private void delete(){
        AlertDialog.Builder dlg = new AlertDialog.Builder(this);
        dlg.setTitle("删除信息");
        dlg.setMessage("真的要删除当前信息吗?");
        dlg.setCancelable(false);
        // 确认操作
        dlg.setPositiveButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                SQLiteDatabase db = openOrCreateDatabase(
                        "infobox.db",MODE_PRIVATE,null);
                String sql = "delete from infobox where recid="+recid+";";
                db.execSQL(sql);
                db.close();
                txtInfo.setText("");
                Toast.makeText(EditActivity.this,
                        "信息已删除",Toast.LENGTH_SHORT).show();
                finish();
            }
        });
        // 取消操作
        dlg.setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                return;
            }
        });
        //
        dlg.show();
    }

    // 保存记录
    private void save(){
        String s = txtInfo.getText().toString().trim();
        if(s.length()==0){
            Toast.makeText(this,
                    "请输入内容",Toast.LENGTH_SHORT).show();
            return;
        }
        SQLiteDatabase db = openOrCreateDatabase(
                "infobox.db",MODE_PRIVATE,null);
        if(recid<=0) {
            // 添加记录
            ContentValues values = new ContentValues();
            values.put("info", s);
            if (db.insert("infobox", null, values) > 0)
                Toast.makeText(this,
                        "信息已保存", Toast.LENGTH_SHORT).show();
            else
                Toast.makeText(this,
                        "信息保存失败", Toast.LENGTH_SHORT).show();
        }else{
            // 更新记录
            ContentValues values = new ContentValues();
            values.put("info", s);
            if(db.update("infobox",values,
                    "recid="+recid,null)>0)
                Toast.makeText(this, "信息已保存",
                        Toast.LENGTH_SHORT).show();
            else
                Toast.makeText(this, "信息保存失败",
                        Toast.LENGTH_SHORT).show();
        }
        db.close();
    }

}

EditActivity中定义的变量和对象包括:

  • recid,保存当前操作的备忘录ID,为0时表示编辑新的备忘录信息,大于0时为编辑已存在的备忘录信息。
  • btnReturn、btnDelete、btnSave分别表示返回按钮、删除按钮、保存按钮。
  • txtInfo表示文本编辑组件。

onCreate()方法,会读取由主界面传递的recid数据,当recid大于0时,会从数据库中读取信息并显示到txtInfo组件。

返回操作会直接调用Activity的finish()方法回到主界面。

删除操作时,会通过一个消息对话框(AlertDialog)确认,然后通过recid数据删除数据库的备忘录信息。

保存操作时,如果recid为0,会将txtInfo中的文本添加到数据库;如果recid大于0,则更新相应的备忘录数据。

附件