安卓开发学习笔记

基础部分

创建项目

创建项目的时候先选择模板,不同的模板会有不同的预设。
这是google官方写的几个模板。

image-20250303100208984

其中特别要说的是,如果选的是这个empty activity,就只能使用kotlin语言

image-20250303100625435

在学校推荐使用如下模板,因为可以切换语言
image-20250303100934809

安卓项目目录结构

.gradle和.idea是编译后生成的文件,编译后自动生成,一般情况下不需要去动。

image-20250303105058053

gradle文件夹 主要就是配置gradle的版本。
首先他会寻找本地是否有该gradle版本,如果没有的话就回去网络上下载。

image-20250303200829668

app是我们的主要战场,基本上的开发都是在这个目录下面进行的。

上面其实说的非常不清晰,但是下面我们切换到安卓模式重新认识一次
image-20250304082514386

直接来看这个列表,首先最上面的manifest是整个项目的一些配置文件,java下面第一个文件夹是我们写的所有java文件
下面两个test文件夹都是用于编写测试用例的
再往下是自动生成的java文件夹 ,估计是jvm虚拟机的编译产物,所以也先不用管。再往下layout存储的是页面资源,类似前端页面。然后其他的都是一些配置文件,之后在使用的过程中用到再慢慢了解。
gradle整体是一个对软件编译打包的套件,不太需要去了解具体的运行原理。

实战项目开发

对自己的java水平自视甚高
直接实战,就是傲慢

记事本

实现一个带提醒功能的记事本

功能

  • 笔记列表
  • 提醒模式(定时提醒的笔记)
  • 黑夜模式
  • 时间倒序
  • 按钮外观颜色自定义
  • 笔记格式部分自定义
  • 搜索功能

适合范围

了解安卓四大组件:Activity,Service,Content Provider,BroadcastReceiver广播接收器 至少了解其中三个
熟悉java ,了解OOP
至少使用过Android Studio新手教程,创建过Hello World项目
想要开发一款安卓应用

实操

关于创建应用和架构以及组件的部分不赘述

编辑笔记功能

修改主页面页面

首先作为记事本,最重要的就是编辑笔记的功能。实现这个功能,首先我们需要一个编辑笔记的页面,然后在java中完成交互。

添加mainifest文件
所有要显示出来的activity文件都需要加入manifest
image-20250304202507851

加入如图所示24行代码

1
2
<activity android:name=".EditActivity">
</activity>

然后我们要实现浮动布局,加入浮动的小圆球实现跳转
如果要实现这个浮动小圆球。就一定不能使用容器布局,因为浮动圆球不能随着页面移动,必须浮在页面上方。
这里我们要用到一个相对布局 relativeLayout
修改完了在主界面搜索FloatingActionButton 然后加入一个浮标
然后再主界面xml里面加入图标的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:layout_centerInParent="true" />

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_margin="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="9dp"
android:layout_marginBottom="43dp"
android:clickable="true"
android:contentDescription="@string/fab_description"
tools:srcCompat="@drawable/baseline_add_24" />
</RelativeLayout>

然后还需要给他加一个icon
在下图的位置

image-20250304210415779

然后在上面的xml中引入(代码中已经引入)
这个设置是看不到的,仅仅只是定位用(我猜的)

这里还是有几个警告其中第一个是namespace未使用 这个之后会用到,先不管他,第二个是说你没有照顾到盲人,难道我开发一个记事本备忘录应用我还要去照顾到盲人,盲人难道不用盲文,不用语音备忘录来用盲文吗?到时候销量排行榜一结算,盲人得了MVP!躺赢狗!你健全的人就是躺赢狗!image-20250304211007815

接下来基本的设置已经完成,我们可以看一下效果。
image-20250304210904392

效果一切正常,点击也有样式,然后我们就需要为这个按钮添加点击事件,那么就来到了java的部分

初始化点击事件

EditActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.selfstudy001;

import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
/*和v4 v7版本一样 但是v4 v7等版本比较杂 x版本集成度比较高
* 所以选择了x*/

public class EditActivity extends AppCompatActivity {

EditText et;

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

MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.example.selfstudy001;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

public class MainActivity extends AppCompatActivity/*这个是安卓系统提供的一个基类*/ {

FloatingActionButton btn1;/*先把我们定义的浮标引入*/
final String TAG = "test";

@Override
protected void onCreate(Bundle savedInstanceState) {

/*这个onCreate的function是所有activity必须的*/
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);


btn1 = (FloatingActionButton)findViewById(R.id.fab);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/* Log.d(TAG, "onClick: click");
通过这一行代码可以在logcat里面查看
点击事件是否正常触发 */
// 创建跳转到 EditActivity 的 Intent
Intent intent = new Intent(MainActivity.this, EditActivity.class);

// 使用 startActivities 方法传递 Intent 数组
startActivities(new Intent[]{intent});
/*上面两行代码主要实现跳转功能
* 这里intent相当于一个事件或者信号 后面的参数是从这个页面跳转到edit页面
* 然后下面是触发这个intent事件*/

}
});
return insets;

});
}
}

现在上面这两个部分已经编辑好了
首先edit页面主要就是引入浮标。其他目前为止还没有什么功能。
mainactivict界面 首先是把btn引入 然后写了点击事件,实现跳转。

插入文字组件

edit_layout

在这里我们要编辑输入文本页面。相当于是加入一个可以编辑文字的组件,这个组件将作为记事本的核心。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">


<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top">

</EditText>


</LinearLayout>

重设返回键

先防结果 在编辑界面编辑完之后按下返回键返回主页面 ,应用程序自动吧你输入的文字返回

下面是日志文件的截图
image-20250305091335961

要达到这个效果 首先我们要修改主界面,具体的解析我都写在注释里面了
MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.example.selfstudy001;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

public class MainActivity extends AppCompatActivity/*这个是安卓系统提供的一个基类*/ {

FloatingActionButton btn1;/*先把我们定义的浮标引入*/
final String TAG = "test";

@Override
protected void onCreate(Bundle savedInstanceState) {

/*这个onCreate的function是所有activity必须的*/
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);


btn1 = (FloatingActionButton)findViewById(R.id.fab);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/* Log.d(TAG, "onClick: click");
通过这一行代码可以在logcat里面查看
点击事件是否正常触发 */
// 创建跳转到 EditActivity 的 Intent
Intent intent = new Intent(MainActivity.this, EditActivity.class);
// 使用 startActivities 方法传递 Intent 数组
// startActivities(new Intent[]{intent});
/*上面两行代码主要实现跳转功能
* 这里intent相当于一个事件或者信号 后面的参数是从这个页面跳转到edit页面
* 然后下面是触发这个intent事件*/

startActivityForResult(intent,0);
/*这个是启动活动获取结果 下面有一个警告是说这个未来会被弃用,但是我看了一下代替的东西一点也不比这个简单
* 所以我这里看看 能不能先凑合用了。
* 这个主要的作用就是传回返回值*/
}
});
return insets;
});
}
/*这里相当于写了一个function来专门接收上面说的传回的数据*/
@Override
protected void onActivityResult(int requestCode,int resultCode,Intent data){
super.onActivityResult(requestCode,resultCode,data);
String edit = data.getStringExtra("input");
/*这样如果我们传回的信息中有一个intent的信息的话我们就可以传到这个input里面*/
/*获取edit的内容之后打印*/
Log.d(TAG,edit);

}

}

然后我们要写EditActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.example.selfstudy001;

import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.widget.EditText;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
/*和v4 v7版本一样 但是v4 v7等版本比较杂 x版本集成度比较高
* 所以选择了x*/

public class EditActivity extends AppCompatActivity {

EditText et;

/*创建点击事件*/
/*为该活动初始化界面并设置布局。*/
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_layout);
et = findViewById(R.id.et);
}

/*给返回键也创建一个结束事件*/
public boolean onKeyDown(int keyCode,KeyEvent event){
if(keyCode == KeyEvent.KEYCODE_HOME){
return true;
}
else if (keyCode == KeyEvent.KEYCODE_BACK)
{
/*这里的逻辑是如果使用返回键返回应用程序之外
* 然后通过我们定义在main里面的函数接受信息*/
Intent intent = new Intent();
intent.putExtra("input",et.getText().toString());
setResult(RESULT_OK,intent);
finish();
return true;

}
return super.onKeyDown(keyCode,event);
/*处理了按下返回键时的逻辑,获取用户输入的文本并传回主 Activity */

}
}

这样通过java文件就实现了上面的功能,虽然看起来好像很简单但是修改bug 的过程很漫长。
总之到这一步我们实际上已经实现了点击悬浮按钮进入编辑页面和点击返回按钮记录编辑页面记录的东西两个部分的功能。

数据库操作和笔记显示

返回编辑结束的文本

首先稍微修改了一下代码 达到了我现在在编辑页面编辑之后再返回到主页面,程序可以通过onActivityResult接受startActivityForResult的结果,然后替换helloworld的功能

效果:
首先在编辑页面输入123321 然后返回
image-20250305123733035

主页面的helloworld被替换
image-20250305123748246

实现代码

在mainactivity中修改

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onActivityResult(int requestCode,int resultCode,Intent data){
super.onActivityResult(requestCode,resultCode,data);
String edit = data.getStringExtra("input");
/*这样如果我们传回的信息中有一个intent的信息的话我们就可以传到这个input里面*/

/*获取edit的内容之后打印*/
//Log.d(TAG,edit); 测试时用 现在不用就先注释
/*通过下面的代码可以实现把你在编辑页面的值*/
tv.setText(edit);

}

在mainxml里面修改

1
2
3
4
5
6
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:layout_centerInParent="true" />

现在我们想要实现的功能是在手机屏幕的左上角按一条一条来区分,目前的代码只能实现显示一条,而且是显示在屏幕中央,不过显示位置可以通过在mainxml里面修改来实现。
现在的主要逻辑是我们要不断的生成笔记并且输入笔记内容,然后我们需要按某种顺序把输入的笔记排列在屏幕上。
然后屏幕肯定会被填满,我们需要实现一个屏幕长度无线 可以下拉滚动的效果,并且曾经输入过的笔记必须是可以随时重新编辑和修改的。

​ 为了实现上述的效果 我们就需要引入一个记事本功能的核心

ListView

相关的介绍可以自行百度,这里截一张图凑合一下

image-20250305124753321

这张图就是非常只管的演示,这个lv的功能就是把你传入的信息一条一条排列(并且包括分割线)
总之通过这个就可以实现上面说的所有功能
然后他是如何实现的
列表的显示需要三个元素:
1、listview用来展示列表的view
2、适配器 用来吧数据映射到ListView上的中介
3、数据 具体的将被映射的字符串,图片,或者基本组件
所以我们现在首先要创建一个数据结构(一个类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.example.selfstudy001;

public class Note {
/*说一下下面四个属性
* id是用来定位的,第一次传入的时候自动生成
* 后面如果要更改数据,就要通过id来定位到之前的数据
* 然后content是内容 就是备忘录笔记的正文部分
* time就是时间 分成创建时间和最后编辑的时间
* 如果只是获取创建时间的话很简单
* 但是如果要改成最后编辑时间的话很复杂 后面会不会做出来我们且看且说
* tag相当于给笔记分类 比如有的笔记可能是学习笔记 有的可能是账本*/
private long id;
private String content;
private String time;
private int tag;

/*下面写一下构造函数还有封装的getset方法 直接右键 启动!
* */
public Note(){
}
public Note(String content, String time, int tag){
this.content = content;
this.time = time;
this.tag = tag;
}

public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public int getTag() {
return tag;
}
public void setTag(int tag) {
this.tag = tag;
}

/*下面重写了toString方法 时间用了切片的格式 主要是为了debug
* 下面的toString获取的就是最后要显示的信息 内容(标题) +时间+文章id*/
@Override
public String toString() {
return content + "\n" + time.substring(5,16) + " " + id;
}
}

基本上的解析我都通过注释写在代码里了 所以现在也不赘述了。

接下来就到了我最讨厌的数据库环节了。

首先我们需要通过定义语句创建一个数据库(在这个安卓里面创建 不用连接mysql这些)

初始化数据库

NoteDatabase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.example.selfstudy001;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class NoteDatabase extends SQLiteOpenHelper {
/*创建完之后发现一定要实现一个oncreat不然就是抽象的 就是上学期C++虚函数那块*/

/*下面先定义一些全局常量,唯一的作用就是方便后续命名和修改
* 没有什么别的作用*/
/*也可以换一种方式理解成我们定义数据库中的列名*/
public static final String TABLE_NAME = "notes";
public static final String CONTENT = "content";
public static final String ID = "_id";
public static final String TIME = "time";
public static final String MODE = "mode";

/*SQLiteOpenHelper 类,并设置数据库的相关参数*/
public NoteDatabase(Context context){
super(context, TABLE_NAME, null, 1);
}/*具体不了解 但是看到文档里是这样的格式我就写了具体原理不清楚*/

@Override
public void onCreate(SQLiteDatabase db) {
/*这个下面的db就是大家心心念念非常熟悉的sql语句了*/
/*下面就是大家熟悉的创表语句了 不必多说
* 哦 还是要多说一嘴 要把id定义成主键 想想id的作用
* 然后时间和内容不为空这个肯定的
* 然后最后那个mode就是对应tag*/
db.execSQL("CREATE TABLE " + TABLE_NAME
+ "("
+ ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ CONTENT + " TEXT NOT NULL,"
+ TIME + " TEXT NOT NULL,"
+ MODE + " INTEGER DEFAULT 1)"
);
}


/*这个function会检测你已有的版本 相当于会做版本匹配 内核其实是一个for循环 里面嵌套了一个switch
* 如果版本不一致就会调用这个upgrade*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

}

上面的代码主要是要负责管理数据库的创建和版本升级,这段代码说实话有很多细节其实不是很了解,都是去搜出来的,但是只要会用就算赢,老师问起来我直接放文档就行了应该。
简单来说onCreate 方法负责创建表,并定义了表的结构
onUpgrade 方法用于处理数据库版本升级时的操作

数据库操作

那么现在数据库初始化也有了,可以开始写数据库操作需要的代码了

CRUD.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package com.example.selfstudy001;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

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

public class CRUD {
/*下面的从名字就可以知道是帮助你操作数据库的
* 说白了就是数据操作*/
SQLiteOpenHelper dbHandler;
/*下面的SQLiteDatabase是用来初始化和定义数据库*/
SQLiteDatabase db;
/*在这两个定义完了之后正常导包就行*/

private static final String[] columns = {
NoteDatabase.ID,
NoteDatabase.CONTENT,
NoteDatabase.TIME,
NoteDatabase.MODE
};

/*孩子们我写完定义语句回来了 现在再来一个构造函数
* 下面其实就是初始化一个刚才写的创建语句 相当于创建了数据库*/
public CRUD(Context context) {
dbHandler = new NoteDatabase(context);
}
/*然后下面通过open和close来控制数据库写入模式的开关*/
public void open(){
db = dbHandler.getWritableDatabase();
}

public void close(){
dbHandler.close();
}

/*孩子们,接下来就是本次项目的核心了 添加笔记功能,终于要来了。*/
public Note addNote(Note note){
//这个contentvalues就是安卓四大组件其中之一
/*这个cv就是一个专门处理数据的一个类 然后对它进行初始化。*/
ContentValues contentValues = new ContentValues();
contentValues.put(NoteDatabase.CONTENT, note.getContent());
contentValues.put(NoteDatabase.TIME, note.getTime());
contentValues.put(NoteDatabase.MODE, note.getTag());
long insertId = db.insert(NoteDatabase.TABLE_NAME, null, contentValues);
note.setId(insertId);
return note;
/*解析一下这行代码 我们刚才写了一个note类 然后这里调用这个构造方法的时候需要传入这个note类
* 然后在传入的note类中获取上面的数据,然后insert那一行是一个内置的方式
* 插入一行信息返回一个long值 然后我们把这个long值再拿来当做id 最后再把完整的note返回*/
}

/*下面的代码直接长话短说 上面我们已经有了id的基本概念,然后下面我们就可以通过这个id来获得note的信息
* 简单来说下面的代码就是这个功能*/
public Note getNote(long id){
//孩子们 java也有自己的指针了
/*这部分完全死记硬背 */
Cursor cursor = db.query(NoteDatabase.TABLE_NAME, columns, NoteDatabase.ID + "=?",
new String[] {String.valueOf(id)}, null, null, null, null);
/*如果找到这个note就用新值去覆盖
* 相关知识点:潜拷贝*/
if (cursor != null) cursor.moveToFirst();
Note e = new Note(cursor.getString(1), cursor.getString(2), cursor.getInt(3));
return e;

}

/*下面代码意思是再调用上面添加方法的时候我们其实在不断往list里面加数据
然后我们返回所有的list 相当于查全部的功能或者说遍历*/
public List<Note> getAllNotes() {
Cursor cursor = db.query(NoteDatabase.TABLE_NAME, columns, null, null, null, null, null);
/*这里原代码报错 我自己修改了一下代码*/
List<Note> notes = new ArrayList<>();
if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
Note note = new Note();
// 获取各个列的索引
int idIndex = cursor.getColumnIndex(NoteDatabase.ID);
int contentIndex = cursor.getColumnIndex(NoteDatabase.CONTENT);
int timeIndex = cursor.getColumnIndex(NoteDatabase.TIME);
int modeIndex = cursor.getColumnIndex(NoteDatabase.MODE);
// 确保列索引有效
if (idIndex != -1) {note.setId(cursor.getLong(idIndex));}
if (contentIndex != -1) {note.setContent(cursor.getString(contentIndex));}
if (timeIndex != -1) {note.setTime(cursor.getString(timeIndex));}
if (modeIndex != -1) {note.setTag(cursor.getInt(modeIndex));}
notes.add(note);
}
}
return notes;
}

/*后面是更新和删除的代码 没有太大的区别*/
public int updateNote(Note note) {

ContentValues values = new ContentValues();
values.put(NoteDatabase.CONTENT, note.getContent());
values.put(NoteDatabase.TIME, note.getTime());
values.put(NoteDatabase.MODE, note.getTag());

return db.update(NoteDatabase.TABLE_NAME, values,
NoteDatabase.ID + "=?", new String[] { String.valueOf(note.getId())});
}

public void removeNote(Note note){
db.delete(NoteDatabase.TABLE_NAME, NoteDatabase.ID + "=" + note.getId(), null);
}

}

这里我们把数据库操作和初始化的语句也写好了,然后我们就可以开始写适配器了。

mainactivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package com.example.selfstudy001;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

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

public class MainActivity extends AppCompatActivity/*这个是安卓系统提供的一个基类*/ {

/*基本组件列表
* 在这里初始化所有组件*/
private NoteDatabase dbHelper;
final String TAG = "test";
FloatingActionButton btn1;/*先把我们定义的浮标引入*/
TextView tv;
private ListView lv;
private NoteAdapter adapter;
private List<Note> noteList = new ArrayList<Note>();
private Context context = this;
/*基本上的元素创建好了之后都需要定位*/



@Override
protected void onCreate(Bundle savedInstanceState) {
/*这个onCreate的function是所有activity必须的*/
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
/*初始化按钮*/
btn1 = (FloatingActionButton)findViewById(R.id.fab);
/*把下面的元素都初始化定位*/
tv = findViewById(R.id.tv);
lv = findViewById(R.id.lv);
/*下面就是把适配器搞起来*/
/*下面代码 初始化的同时调用了一个获取context的内容*/
adapter = new NoteAdapter(getApplicationContext(), noteList);

/*每次调用的时候刷新内容 调用一个刷新功能,在后面实现*/
refreshListView();

lv.setAdapter(adapter);


btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/* Log.d(TAG, "onClick: click");
通过这一行代码可以在logcat里面查看
点击事件是否正常触发 */
// 创建跳转到 EditActivity 的 Intent
Intent intent = new Intent(MainActivity.this, EditActivity.class);

// 使用 startActivities 方法传递 Intent 数组
// startActivities(new Intent[]{intent});
/*上面两行代码主要实现跳转功能
* 这里intent相当于一个事件或者信号 后面的参数是从这个页面跳转到edit页面
* 然后下面是触发这个intent事件*/

startActivityForResult(intent,0);
/*这个是启动活动获取结果 下面有一个警告是说这个未来会被弃用,但是我看了一下代替的东西一点也不比这个简单
* 所以我这里看看 能不能先凑合用了。
* 这个主要的作用就是传回返回值*/

}
});
return insets;
});
}
/*这里相当于写了一个function来专门接收上面说的传回的数据
* 也就是接受startActivityForResult的结果*/
@Override
protected void onActivityResult(int requestCode,int resultCode,Intent data){
super.onActivityResult(requestCode,resultCode,data);
String content = data.getStringExtra("content");
/*这样如果我们传回的信息中有一个intent的信息的话我们就可以传到这个input里面*/
String time = data.getStringExtra("time");
Note note = new Note(content,time,1);
CRUD op = new CRUD(context);
op.open();
op.addNote(note);
op.close();
/*获取edit的内容之后打印*/
//Log.d(TAG,edit); 测试时用 现在不用就先注释
/*通过下面的代码可以实现把你在编辑页面的值传回去*/
// tv.setText(edit); 测试完之后也注释掉 具体功能实现的时候不需要这个

}


public void refreshListView(){

CRUD op = new CRUD(context);
op.open();
// set adapter
if (noteList.size() > 0) noteList.clear();
noteList.addAll(op.getAllNotes());
op.close();
adapter.notifyDataSetChanged();
}

}












总体代码

CRUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package com.example.selfstudy001;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

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

public class CRUD {
/*下面的从名字就可以知道是帮助你操作数据库的
* 说白了就是数据操作*/
SQLiteOpenHelper dbHandler;
/*下面的SQLiteDatabase是用来初始化和定义数据库*/
SQLiteDatabase db;
/*在这两个定义完了之后正常导包就行*/

private static final String[] columns = {
NoteDatabase.ID,
NoteDatabase.CONTENT,
NoteDatabase.TIME,
NoteDatabase.MODE
};

/*孩子们我写完定义语句回来了 现在再来一个构造函数
* 下面其实就是初始化一个刚才写的创建语句 相当于创建了数据库*/
public CRUD(Context context) {
dbHandler = new NoteDatabase(context);
}
/*然后下面通过open和close来控制数据库写入模式的开关*/
public void open(){
db = dbHandler.getWritableDatabase();
}

public void close(){
dbHandler.close();
}

/*孩子们,接下来就是本次项目的核心了 添加笔记功能,终于要来了。*/
public Note addNote(Note note){
//这个contentvalues就是安卓四大组件其中之一
/*这个cv就是一个专门处理数据的一个类 然后对它进行初始化。*/
ContentValues contentValues = new ContentValues();
contentValues.put(NoteDatabase.CONTENT, note.getContent());
contentValues.put(NoteDatabase.TIME, note.getTime());
contentValues.put(NoteDatabase.MODE, note.getTag());
long insertId = db.insert(NoteDatabase.TABLE_NAME, null, contentValues);
note.setId(insertId);
return note;
/*解析一下这行代码 我们刚才写了一个note类 然后这里调用这个构造方法的时候需要传入这个note类
* 然后在传入的note类中获取上面的数据,然后insert那一行是一个内置的方式
* 插入一行信息返回一个long值 然后我们把这个long值再拿来当做id 最后再把完整的note返回*/
}

/*下面的代码直接长话短说 上面我们已经有了id的基本概念,然后下面我们就可以通过这个id来获得note的信息
* 简单来说下面的代码就是这个功能*/
public Note getNote(long id){
//孩子们 java也有自己的指针了
/*这部分完全死记硬背 */
Cursor cursor = db.query(NoteDatabase.TABLE_NAME, columns, NoteDatabase.ID + "=?",
new String[] {String.valueOf(id)}, null, null, null, null);
/*如果找到这个note就用新值去覆盖
* 相关知识点:潜拷贝*/
if (cursor != null) cursor.moveToFirst();
Note e = new Note(cursor.getString(1), cursor.getString(2), cursor.getInt(3));
return e;

}

/*下面代码意思是再调用上面添加方法的时候我们其实在不断往list里面加数据
然后我们返回所有的list 相当于查全部的功能或者说遍历*/
public List<Note> getAllNotes() {
Cursor cursor = db.query(NoteDatabase.TABLE_NAME, columns, null, null, null, null, null);
/*这里原代码报错 我自己修改了一下代码*/
List<Note> notes = new ArrayList<>();
if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
Note note = new Note();
// 获取各个列的索引
int idIndex = cursor.getColumnIndex(NoteDatabase.ID);
int contentIndex = cursor.getColumnIndex(NoteDatabase.CONTENT);
int timeIndex = cursor.getColumnIndex(NoteDatabase.TIME);
int modeIndex = cursor.getColumnIndex(NoteDatabase.MODE);
// 确保列索引有效
if (idIndex != -1) {note.setId(cursor.getLong(idIndex));}
if (contentIndex != -1) {note.setContent(cursor.getString(contentIndex));}
if (timeIndex != -1) {note.setTime(cursor.getString(timeIndex));}
if (modeIndex != -1) {note.setTag(cursor.getInt(modeIndex));}
notes.add(note);
}
}
return notes;
}

/*后面是更新和删除的代码 没有太大的区别*/
public int updateNote(Note note) {

ContentValues values = new ContentValues();
values.put(NoteDatabase.CONTENT, note.getContent());
values.put(NoteDatabase.TIME, note.getTime());
values.put(NoteDatabase.MODE, note.getTag());

return db.update(NoteDatabase.TABLE_NAME, values,
NoteDatabase.ID + "=?", new String[] { String.valueOf(note.getId())});
}

public void removeNote(Note note){

db.delete(NoteDatabase.TABLE_NAME, NoteDatabase.ID + "=" + note.getId(), null);
}
}

EditActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.example.selfstudy001;

import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.widget.EditText;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

import java.text.SimpleDateFormat;
import java.util.Date;
/*和v4 v7版本一样 但是v4 v7等版本比较杂 x版本集成度比较高
* 所以选择了x*/

public class EditActivity extends AppCompatActivity {

EditText et;
/*现在我们可以把content和time引入了
* 是时候升级一下我们的编辑页面逻辑了*/
private String content;
private String time;


/*创建点击事件*/
/*为该活动初始化界面并设置布局。*/
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_layout);
et = findViewById(R.id.et);
}

/*给返回键也创建一个结束事件*/
public boolean onKeyDown(int keyCode,KeyEvent event){
if(keyCode == KeyEvent.KEYCODE_HOME){
return true;
}
else if (keyCode == KeyEvent.KEYCODE_BACK)
{
/*这里的逻辑是如果使用返回键返回应用程序之外
* 然后通过我们定义在main里面的函数接受信息*/
Intent intent = new Intent();
/*首先我们把获取的内容改成content*/
intent.putExtra("content",et.getText().toString());
/*然后我们再传回去一个time*/
intent.putExtra("time", dateToStr());
setResult(RESULT_OK,intent);
finish();
return true;

}
return super.onKeyDown(keyCode,event);
/*处理了按下返回键时的逻辑,获取用户输入的文本并传回主 Activity */

}

/*下面我们再写一个方法,作用是把时间戳改变成我们想要的格式*/
public String dateToStr(){
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(date);
}

}

MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package com.example.selfstudy001;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

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

public class MainActivity extends AppCompatActivity/*这个是安卓系统提供的一个基类*/ {

/*基本组件列表
* 在这里初始化所有组件*/
private NoteDatabase dbHelper;
final String TAG = "test";
FloatingActionButton btn1;/*先把我们定义的浮标引入*/
TextView tv;
private ListView lv;
private NoteAdapter adapter;
private List<Note> noteList = new ArrayList<Note>();
private Context context = this;
/*基本上的元素创建好了之后都需要定位*/

// 新增的 ActivityResultLauncher 用于代替 startActivityForResult
ActivityResultLauncher<Intent> startActivityForResultLauncher;

@Override
protected void onCreate(Bundle savedInstanceState) {
/*这个onCreate的function是所有activity必须的*/
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);

// 初始化 ActivityResultLauncher
startActivityForResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
// 获取返回的数据
String content = result.getData().getStringExtra("content");
String time = result.getData().getStringExtra("time");

// 创建新的 Note 对象
Note note = new Note(content, time, 1);

// 存储到数据库
CRUD op = new CRUD(context);
op.open();
op.addNote(note);
op.close();

// 刷新 ListView
refreshListView();
}
});

ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);

/*初始化按钮*/
btn1 = (FloatingActionButton)findViewById(R.id.fab);
/*把下面的元素都初始化定位*/
tv = findViewById(R.id.tv);
lv = findViewById(R.id.lv);
/*下面就是把适配器搞起来*/
adapter = new NoteAdapter(getApplicationContext(), noteList);

/*每次调用的时候刷新内容 调用一个刷新功能,在后面实现*/
refreshListView();

lv.setAdapter(adapter);

btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/* Log.d(TAG, "onClick: click");
通过这一行代码可以在logcat里面查看
点击事件是否正常触发 */
// 创建跳转到 EditActivity 的 Intent
Intent intent = new Intent(MainActivity.this, EditActivity.class);

// 使用新的 ActivityResultLauncher 启动 Activity
startActivityForResultLauncher.launch(intent);
/*这个是启动活动获取结果 下面有一个警告是说这个未来会被弃用,但是我看了一下代替的东西一点也不比这个简单
* 所以我这里看看 能不能先凑合用了。
* 这个主要的作用就是传回返回值*/
}
});
return insets;
});
}

/*这里相当于写了一个function来专门接收上面说的传回的数据
* 也就是接受startActivityForResult的结果*/
// 此方法已经不再需要,因为我们使用了新的 ActivityResultLauncher API

public void refreshListView(){
CRUD op = new CRUD(context);
op.open();
// set adapter
if (noteList.size() > 0) noteList.clear();
noteList.addAll(op.getAllNotes());
op.close();
adapter.notifyDataSetChanged();
}
}

Note

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.example.selfstudy001;

public class Note {
/*说一下下面四个属性
* id是用来定位的,第一次传入的时候自动生成
* 后面如果要更改数据,就要通过id来定位到之前的数据
* 然后content是内容 就是备忘录笔记的正文部分
* time就是时间 分成创建时间和最后编辑的时间
* 如果只是获取创建时间的话很简单
* 但是如果要改成最后编辑时间的话很复杂 后面会不会做出来我们且看且说
* tag相当于给笔记分类 比如有的笔记可能是学习笔记 有的可能是账本*/
private long id;
private String content;
private String time;
private int tag;

/*下面写一下构造函数还有封装的getset方法 直接右键 启动!
* */
public Note(){
}
public Note(String content, String time, int tag){
this.content = content;
this.time = time;
this.tag = tag;
}

public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public int getTag() {
return tag;
}
public void setTag(int tag) {
this.tag = tag;
}

/*下面重写了toString方法 时间用了切片的格式 主要是为了debug
* 下面的toString获取的就是最后要显示的信息 内容(标题) +时间+文章id*/
@Override
public String toString() {
return content + "\n" + time.substring(5,16) + " " + id;
}
}

NoteAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package com.example.selfstudy001;

import android.content.Context;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;

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

/*主要是用于展示笔记内容的自定义适配器,主要功能是显示笔记内容,并实现了搜索过滤功能
* 用一句话来说就是三要素中的适配器*/
public class NoteAdapter extends BaseAdapter implements Filterable {

private Context mContext;
private List<Note> backList; //用来备份原始数据
private List<Note> noteList; //这个数据是会改变的,所以要有个变量来备份一下原始数据
private MyFilter mFilter;

public NoteAdapter(Context mContext, List<Note> noteList) {
this.mContext = mContext;
this.noteList = noteList;
this.backList = new ArrayList<>(noteList); // 创建一个备份列表
}

/*这个Filter实现了一个按时间正序和倒叙的排列 其实可以算一个进阶功能了
* 然后这个东西是一个类中类。*/
class MyFilter extends Filter {
//我们在performFiltering(CharSequence charSequence)这个方法中定义过滤规则
@Override
protected FilterResults performFiltering(CharSequence charSequence) {
FilterResults result = new FilterResults();
List<Note> list;
if (TextUtils.isEmpty(charSequence)) { //当过滤的关键字为空的时候,我们则显示所有的数据
list = backList;
} else { //否则把符合条件的数据对象添加到集合中
list = new ArrayList<>();
for (Note note : backList) {
if (note.getContent().contains(charSequence)) {
list.add(note);
}
}
}
result.values = list; //将得到的集合保存到FilterResults的value变量中
result.count = list.size(); //将集合的大小保存到FilterResults的count变量中

return result;
}

//在publishResults方法中告诉适配器更新界面
@Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
noteList = (List<Note>) filterResults.values;
if (filterResults.count > 0) {
notifyDataSetChanged(); //通知数据发生了改变
} else {
notifyDataSetInvalidated(); //通知数据失效
}
}
}


/*下面是几个get方法 可以获得需要的东西 */
@Override
public int getCount() {
return noteList.size();
}

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

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

/*根据指定位置返回一个视图,展示笔记内容。*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = View.inflate(mContext, R.layout.note_layout, null);
TextView tv_content = v.findViewById(R.id.tv_content);
TextView tv_time = v.findViewById(R.id.tv_time);

// Set text for TextView

String allText = noteList.get(position).getContent();
tv_content.setText(allText);
tv_time.setText(noteList.get(position).getTime());

// Save note id to long ,man!
v.setTag(noteList.get(position).getId());

return v;
}

// 实现Filterable接口中的getFilter方法
/*这里我不知道为什么 示例的代码和视频里都没有这一段 但是我没有这一段就报错。
* 显示不实现这个功能,这个类永远是一个抽象类,所以没有办法先把这个加上再说。后续如果要调整再说。
* 然后这里我即使加上了之后还是警告,然后下面的警告太多了,我也没办法一个个处理了,只能暂时先这样了。
*
* 最后发现这里是我的误会 这个确实是有的 只是我自己少了 但是注释我也先留下来纪念一下。*/
@Override
public Filter getFilter() {
if (mFilter ==null){
mFilter = new MyFilter();
}
return mFilter;
}
}

NoteDatabase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.example.selfstudy001;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class NoteDatabase extends SQLiteOpenHelper {
/*创建完之后发现一定要实现一个oncreat不然就是抽象的 就是上学期C++虚函数那块*/

/*下面先定义一些全局常量,唯一的作用就是方便后续命名和修改
* 没有什么别的作用*/
/*也可以换一种方式理解成我们定义数据库中的列名*/
public static final String TABLE_NAME = "notes";
public static final String CONTENT = "content";
public static final String ID = "_id";
public static final String TIME = "time";
public static final String MODE = "mode";

/*SQLiteOpenHelper 类,并设置数据库的相关参数*/
public NoteDatabase(Context context){
super(context, TABLE_NAME, null, 1);
}/*具体不了解 但是看到文档里是这样的格式我就写了具体原理不清楚*/

@Override
public void onCreate(SQLiteDatabase db) {
/*这个下面的db就是大家心心念念非常熟悉的sql语句了*/
/*下面就是大家熟悉的创表语句了 不必多说
* 哦 还是要多说一嘴 要把id定义成主键 想想id的作用
* 然后时间和内容不为空这个肯定的
* 然后最后那个mode就是对应tag*/
db.execSQL("CREATE TABLE " + TABLE_NAME
+ "("
+ ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ CONTENT + " TEXT NOT NULL,"
+ TIME + " TEXT NOT NULL,"
+ MODE + " INTEGER DEFAULT 1)"
);
}
/*这个function会检测你已有的版本 相当于会做版本匹配 内核其实是一个for循环 里面嵌套了一个switch
* 如果版本不一致就会调用这个upgrade*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

}

layout部分
activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:layout_centerInParent="true" >

</TextView>

<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="8dp"
android:divider="@android:color/darker_gray"
android:dividerHeight="8dp"
>
</ListView>


<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_margin="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="9dp"
android:layout_marginBottom="43dp"
android:clickable="true"
android:contentDescription="@string/fab_description"
tools:srcCompat="@drawable/baseline_add_24" />
<!--上网搜了一下说如果直接在一个括号内添加注释会造成闭合混乱,所以只能在括号外注释
那么主要说一下
tools:srcCompat="@drawable/baseline_add_24""
这个部分是一个定位-->
</RelativeLayout>

edit_layout.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">


<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top">

</EditText>


</LinearLayout>

new_layout

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<!--推荐使用这种布局,因为还有一种ConstrintLayout不知道为什么会报错-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

</LinearLayout>

note_layout.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="5dp"
android:paddingBottom="12dp"
android:background="@drawable/note_shape"
android:outlineAmbientShadowColor="@color/black">

<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Content"
android:textSize="20dp"
android:textColor="@color/tv_main_color"
android:singleLine="true"/>

<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Time"
android:textSize="16dp"
android:textColor="@color/greyC"/>

</LinearLayout>

自定义活动栏和笔记可点击

首先在mainactivity里面加入工具栏

1
2
3
4
5
6
7
8
9
10
<!--工具栏-->
<androidx.appcompat.widget.Toolbar
android:id="@+id/myToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
app:titleTextColor="?attr/titleColor"
android:theme="?attr/toolbarTheme"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

然后在mainactivity里面加入代码

1
2
3
4
5
6
7
8
9
10
   private Toolbar myToolbar;
myToolbar = findViewById(R.id.myToolbar);
/*加入工具栏*/
setSupportActionBar(myToolbar);
/*下面两句的意思就是设置一个活动栏代替原本的工具栏*/
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); //设置toolbar取代actionBar
initPopUpView();
myToolbar.setNavigationIcon(R.drawable.ic_menu_black_24dp);
myToolbar.setNavigationOnClickListener(new View.OnClickListener() {

这样改完之后工具栏就会出现在主页面,然后笔记的位置也会下移。(要加个css below样式)

在mainactivity中加入以下内容,

实现可实现点击已创建过的笔记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*这个mode是3的意思就是我们定义了一个点击时间,点击了之前已经存在的笔记之后
* 就打开一个新的界面,其实这个笔记和原本的笔记没有关系 是一个全新的笔记
* 只是读取了之前笔记的值*/
if (openMode == 3) {//打开已存在的note
/*下面几行都是获取接受的信息 非常直白*/
id = getIntent.getLongExtra("id", 0);
old_content = getIntent.getStringExtra("content");
old_time = getIntent.getStringExtra("time");
old_Tag = getIntent.getIntExtra("tag", 1);
/*下面一行主要说用信息设置内容 填充编辑栏*/
et.setText(old_content);
/*然后这个就是设置光标的位置*/
et.setSelection(old_content.length());

}
}
intent.putExtra("mode", 4);

在editactivity设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    public Intent intent = new Intent();
autoSetMessage();
public void autoSetMessage(){
if(openMode == 4){
if(et.getText().toString().length() == 0){
intent.putExtra("mode", -1); //nothing new happens.
}
else{
intent.putExtra("mode", 0); // new one note;
intent.putExtra("content", et.getText().toString());
intent.putExtra("time", dateToStr());
intent.putExtra("tag", tag);
}
}
else {
if (et.getText().toString().equals(old_content) && !tagChange)
intent.putExtra("mode", -1); // edit nothing
else {
intent.putExtra("mode", 1); //edit the content
intent.putExtra("content", et.getText().toString());
intent.putExtra("time", dateToStr());
intent.putExtra("id", id);
intent.putExtra("tag", tag);
}

}
}

总结就是先在页面里面加入了活动栏的布局,在前端方面就有了活动栏。

然后再mainactivicy里面加入活动栏初始化的代码。
然后编写function,添加点击事件,给list里面的每一条笔记添加一个点击事件,点击笔记的时候新建一个页面,并且读取之前的笔记内容,然后生成,然后监听对原本的笔记内容是否有改变,有的话就用新的笔记页面取代旧的。
简单来说就是这样 然后还引入了mode设置 不同mode不一样。

菜单栏

首先前面已经添加了 活动栏,但是活动栏上面还没有元素

标签需要在活动栏中修改,所以我们先搞定活动蓝(菜单栏)是完成后续标签功能的前置条件

对于每一个活动栏需要添加的功能都需要在res文件夹下面新建一个menu类的资源文件夹

image-20250309203221726

然后在里面创建菜单文件
接着在drawable里面创建图标文件

image-20250309203438372

然后在menu的xml文件里面引入图标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="?attr/menu_search"
app:showAsAction="always"
app:actionViewClass="androidx.appcompat.widget.SearchView"
android:title="Search"
/>
<item
android:id="@+id/menu_clear"
android:icon="?attr/menu_delete_all"
android:title="Clear"
app:showAsAction="always"
/>
</menu>

edit_menu.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/delete"
android:icon="?attr/menu_delete"
android:title="Delete"
app:showAsAction="always"
/>
</menu>

引入的图标默认靠右,如果要设置位置的话就要设置一些属性往中间靠。

修改mainactivity.java

1
2
3
4
5
6
7
8
9
/*替换主页面菜单栏返回的图标*/
myToolbar.setNavigationIcon(R.drawable.ic_menu_black_24dp);
myToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "onClick: shit");
showPopUpView();
}
});

editactivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*创建设置初始化编辑页面的菜单栏*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.edit_menu, menu);
return super.onCreateOptionsMenu(menu);
}
/*为该活动初始化界面并设置布局。
* 主要就是点击之后生成的编辑栏页面*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_layout);

myToolbar = findViewById(R.id.my_Toolbar);
setSupportActionBar(myToolbar);
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); //设置toolbar取代actionbar

myToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
autoSetMessage();
setResult(RESULT_OK, intent);
finish();
}
});
et = findViewById(R.id.et);
/*获取其他avtivity传入的值*/
Intent getIntent = getIntent();
/*获取信息*/
openMode = getIntent.getIntExtra("mode", 0);

删除功能的实现

上一部分加入了状态栏的图标,但是具体的功能还没有实现,这一章节过来实现一下

先从编辑页面开始,因为编辑页面的删除功能只需要删除当前页面,会比较简单。

editactivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*选择元素并点击,监听状态栏上任一元素*/
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
/*对item进行定位*/
switch (item.getItemId()){
/*这里是先定位到之前的delete元素*/
case R.id.delete:
/*下面是引入并且初始化了一个系统自带的东西
* 就是一个对话框
* 然后会弹出问你要不要删除
* 代码运行基本上就是监听整个导航栏
* 然后如果*/
new AlertDialog.Builder(EditActivity.this)
.setMessage("删除吗?")
/*然后在下面创建了一个监听器 监听点击*/
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
/*然后下面编辑点击事件,也是判断打开文件是新文件还是旧文件
* 如果是新笔记就什么都不管 不创建新笔记就行 直接返回
* 如果是旧笔记 就定位id 然后删除*/
@Override
public void onClick(DialogInterface dialog, int which) {
if (openMode == 4){ // new note
intent.putExtra("mode", -1);
setResult(RESULT_OK, intent);
}
else { // existing note
intent.putExtra("mode", 2);
intent.putExtra("id", id);
setResult(RESULT_OK, intent);
}
finish();
}
}).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override/*下面是取消的方法 只是为了语法完整性*/
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).create().show();
/*上面两个方法分别是创建和显示的方法*/
break;
}
/*不知道为什么,但是文档里是这么写的,所以就这样了*/
return super.onOptionsItemSelected(item);
}

mainactivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.menu_clear:
new AlertDialog.Builder(MainActivity.this)
.setMessage("删除全部吗?")
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dbHelper = new NoteDatabase(context);
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("notes", null, null);
db.execSQL("update sqlite_sequence set seq=0 where name='notes'");
refreshListView();
}
}).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).create().show();
break;

}
return super.onOptionsItemSelected(item);
}

笔记的主要功能其实到这里已经结束了,因为增删改都已经实现了。
接下来就是对其他功能的优化迭代了。还有实现查找功能。

搜索笔记

有什么用?
一方面增删改查就差个查找了
另一方面编辑很多的时候确实有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!--主页面的搜索功能-->
<item
android:id="@+id/action_search"
android:icon="?attr/menu_search"
app:showAsAction="always"
app:actionViewClass="androidx.appcompat.widget.SearchView"
android:title="Search"
/>
<!--主页面的删除功能-->
<item
android:id="@+id/menu_clear"
android:icon="?attr/menu_delete_all"
android:title="Clear"
app:showAsAction="always"
/>
</menu>

mainacticity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

/*引入menu菜单栏*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
/*渲染出menu这个对象*/
getMenuInflater().inflate(R.menu.main_menu, menu);

/*渲染并初始化搜索功能*/
MenuItem mSearch = menu.findItem(R.id.action_search);
SearchView mSearchView = (SearchView) mSearch.getActionView();

/*创建监听事件*/
mSearchView.setQueryHint("Search");
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
/*上方法 提交输入文字的时候的具体实现
* 这里不用写 因为我们根本没有提交键*/
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
/*下方法 文字改变的时候的实现
* 每次修改文本之后自动传入值 然后搜索传入的值*/
@Override
public boolean onQueryTextChange(String newText) {

adapter.getFilter().filter(newText);
return false;
}
});

return super.onCreateOptionsMenu(menu);
}

因为这个代码具体的实现在适配器里面已经写了过滤器了,所以直接把按钮装上之后把方法调用过来就行,所以写起来特别方便。

弹出菜单

想要实现功能,首先必须要有这个页面,所以我们先加一个设置菜单。

setting_layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:title="弹出菜单"
app:titleTextColor="?attr/titleColor"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:clickable="true"
android:orientation="vertical">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/setting_settings_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="?attr/settingIcon"
android:layout_gravity="center"
/>
<TextView
android:id="@+id/setting_settings_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设置"
android:textColor="?attr/tvMainColor"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:layout_gravity="center"
android:textSize="24dp"/>
</LinearLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black"
android:alpha="0.2"/>
<ListView
android:id="@+id/lv_tag"
android:layout_width="match_parent"
android:layout_height="wrap_content">

</ListView>
</LinearLayout>
</LinearLayout>

再引入设置图标

image-20250309225623196

mainactivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

//弹出菜单
private PopupWindow popupWindow;
private PopupWindow popupCover;
/*这里设置了蒙版 就是弹出菜单的透明层*/
private ViewGroup customView;
private ViewGroup coverView;
initPopUpView();
public void initPopUpView(){
layoutInfater = (LayoutInflater)MainActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
customView = (ViewGroup) layoutInfater.inflate(R.layout.setting_layout, null);
coverView = (ViewGroup) layoutInfater.inflate(R.layout.setting_cover, null);
main = findViewById(R.id.main_layout);
wm = getWindowManager();
metrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics);
}

public void showPopUpView(){
int width = metrics.widthPixels;
int height = metrics.heightPixels;

popupCover = new PopupWindow(coverView, width, height, false);
popupWindow = new PopupWindow(customView, (int)(width*0.7), height, true);
popupWindow.setBackgroundDrawable(new ColorDrawable(Color.WHITE));

//在主界面加载成功之后 显示弹出
findViewById(R.id.main_layout).post(new Runnable() {
@Override
public void run() {
popupCover.showAtLocation(main, Gravity.NO_GRAVITY, 0, 0);
popupWindow.showAtLocation(main, Gravity.NO_GRAVITY, 0, 0);

setting_image = customView.findViewById(R.id.setting_settings_image);
setting_text = customView.findViewById(R.id.setting_settings_text);

setting_image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, UserSettingsActivity.class));
}
});

setting_text.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, UserSettingsActivity.class));
}
});

coverView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
popupWindow.dismiss();
return true;
}
});

popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
popupCover.dismiss();
Log.d(TAG, "onDismiss: test");
}
});
}


});
}

这里想实现的一个效果是弹出窗口,然后弹出窗口加载之后要让背景变暗,然后弹出窗口保持原本的亮度。
但是查阅了文档和我上网搜了之后没有发现任何一个方法可以实现这个效果,所以选择曲线救国。在上面再铺设一层透明的层。

setting_cover.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/cover">

</View>

</LinearLayout>

黑夜模式

首先黑夜模式需要在设置里面切换,那么我们首先要写一个基础的BaseActivity

作用主要是减少重复的代码,只有用通用的功能 也是写在baseActiviy里面 类似上学期的java

BaseActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.example.biji;

import androidx.appcompat.app.AppCompatActivity;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;

public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setNightMode();
}

public boolean isNightMode(){
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
return sharedPreferences.getBoolean("nightMode", false);
}
public void setNightMode(){
setTheme(R.style.DayTheme);

}
}

UserSettingActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.example.biji;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.CompoundButton;
import android.widget.Switch;

import androidx.appcompat.widget.Toolbar;

public class UserSettingsActivity extends BaseActivity {

private Switch nightMode;
private SharedPreferences sharedPreferences;
private Boolean night_change;

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

sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
Intent intent = getIntent();
/*
if(intent.getExtras() != null) night_change = intent.getBooleanExtra("night_change", false);
else night_change = false;
*/
initView();

Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
setSupportActionBar(myToolbar);
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if(isNightMode()) myToolbar.setNavigationIcon(getDrawable(R.drawable.ic_settings_white_24dp));
else myToolbar.setNavigationIcon(getDrawable(R.drawable.ic_settings_black_24dp));
}

public void initView(){
nightMode = findViewById(R.id.nightMode);
nightMode.setChecked(sharedPreferences.getBoolean("nightMode", false));
nightMode.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setNightModePref(isChecked);
setSelfNightMode();

}
});
}

private void setNightModePref(boolean night){
//通过nightMode switch修改pref中的nightMode
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("nightMode", night);
editor.commit();
}

private void setSelfNightMode(){
//重新赋值并重启本activity

super.setNightMode();
Intent intent = new Intent(this, UserSettingsActivity.class);
//intent.putExtra("night_change", !night_change); //重启一次,正负颠倒。最终为正值时重启MainActivity。

startActivity(intent);
finish();
}
}

preference_layout.xml

然后要实现这个设置页面 肯定需要有设置页面的前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/lvBackground">
<androidx.appcompat.widget.Toolbar
android:id="@+id/my_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
app:titleTextColor="?attr/titleColor"
app:title="设置"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="20dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="黑夜模式"
android:textColor="?attr/tvMainColor"
android:textSize="20dp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_night_summary"
android:textColor="?attr/tvSubColor"
android:textSize="16dp" />
</LinearLayout>
<Switch
android:id="@+id/nightMode"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:clickable="true"
android:gravity="center"
android:switchMinWidth="50dp"
/>
</LinearLayout>

<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:foreground="@color/greyC"
/>

</LinearLayout>

mainactivity

修改设置代码 点击设置字符跳转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

/*设置的文字和图标 下面是这两个组件的监听器和跳转事件*/
setting_image = customView.findViewById(R.id.setting_settings_image);
setting_text = customView.findViewById(R.id.setting_settings_text);

setting_image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, UserSettingsActivity.class));
}
});

setting_text.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, UserSettingsActivity.class));
}
});

设置黑夜模式主题颜色

image-20250310004311534

image-20250310004334356

注意安卓开发过程中有一些部分的颜色会有一些专用名词来指代

重新添加图标并且添加黑夜主体的xml配置文件

theme.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->

<style name="DayTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="toolbarTheme">@style/ThemeOverlay.AppCompat.ActionBar</item>
<item name="colorPrimary">@color/white</item>
<item name="android:textColorPrimary">@color/black</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="tvBackground">@color/white</item>
<item name="lvBackground">@color/near_white</item>
<item name="settingIcon">@drawable/ic_settings_black_24dp</item>

<item name="menu_delete_all">@drawable/ic_delete_forever_black_24dp</item>
<item name="menu_delete">@drawable/ic_delete_black_24dp</item>
<item name="menu_search">@drawable/ic_search_black_24dp</item>
<item name="titleColor">@color/black</item>
<item name="tvMainColor">@color/black</item>
<item name="tvSubColor">@color/grey9</item>
<item name="colorControlActivated">@color/black</item>
<item name="colorSwitchThumbNormal">@color/black</item>
</style>
<style name="NightTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="toolbarTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
<item name="colorPrimary">@color/black</item>
<item name="android:textColorPrimary">@color/black</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="tvBackground">@color/black</item>
<item name="lvBackground">@color/near_black</item>
<item name="settingIcon">@drawable/ic_settings_white_24dp</item>

<item name="menu_delete_all">@drawable/ic_delete_forever_white_24dp</item>
<item name="menu_delete">@drawable/ic_delete_white_24dp</item>
<item name="menu_search">@drawable/ic_search_white_24dp</item>
<item name="titleColor">@color/white</item>
<item name="tvMainColor">@color/near_white</item>
<item name="tvSubColor">@color/greyC</item>
<item name="colorControlActivated">@color/white</item>
<item name="colorSwitchThumbNormal">@color/white</item>
</style>

</resources>

然后去base里面设置一下 让所有页面创建的时候默认就通过黑夜模式创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example.biji;

import androidx.appcompat.app.AppCompatActivity;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;

/*base本身不需要实例化 所以可以当作抽象类
* 不过这里目前还没有当抽象类 只是加了更加规范*/
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setNightMode();
}

public boolean isNightMode(){
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
return sharedPreferences.getBoolean("nightMode", false);
}
public void setNightMode(){
setTheme(R.style.NightTheme);

}
}

然后因为弹窗不属于任何一个页面 ,我们需要单独给他设置一个样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/lvBackground"> 在这里加
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:title="弹出菜单"
app:titleTextColor="?attr/titleColor"
/>
<!--在下面定义一个正向布局-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:clickable="true"
android:orientation="vertical">
<!--再定义一个横向布局放设置-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/setting_settings_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="?attr/settingIcon"
android:layout_gravity="center"
/>
<TextView
android:id="@+id/setting_settings_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设置"
android:textColor="?attr/tvMainColor"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:layout_gravity="center"
android:textSize="24dp"/>
</LinearLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black"
android:alpha="0.2"/>
<ListView
android:id="@+id/lv_tag"
android:layout_width="match_parent"
android:layout_height="wrap_content">

</ListView>
</LinearLayout>
</LinearLayout>

baseactivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.example.biji;

import androidx.appcompat.app.AppCompatActivity;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;

/*base本身不需要实例化 所以可以当作抽象类
* 不过这里目前还没有当抽象类 只是加了更加规范*/
public class BaseActivity extends AppCompatActivity {
private static final String TAG = "BaseActivity";
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setNightMode();
}

public boolean isNightMode(){
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
return sharedPreferences.getBoolean("nightMode", false);
}
// 根据夜间模式的设置选择主题
public void setNightMode() {
// 判断是否为夜间模式
if (isNightMode()) {
Log.d(TAG, "Night mode enabled");
setTheme(R.style.NightTheme); // 设置夜间主题
} else {
Log.d(TAG, "Day mode enabled");
setTheme(R.style.DayTheme); // 设置日间主题
}
}
}

做好了 结课

课外专题

根据老师的要求,加上单词本和答题功能。

英语词汇助手功能

功能概述

英语词汇助手是一个帮助用户学习和记忆英语单词的功能模块,主要包含以下功能:

  • 单词本:存储和管理用户添加的英语单词

  • 单词查询:通过网络API获取单词释义、例句和音标

  • 单词测试:针对已添加的单词进行记忆测试

  • 学习计划:设置每日学习单词数量和提醒

单词数据模型设计

首先我们需要创建一个单词的数据模型,用于存储单词相关信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.example.biji.vocabulary;

public class Word {
private long id;
private String word; // 单词
private String phonetic; // 音标
private String definition; // 释义
private String example; // 例句
private int familiarity; // 熟悉度(0-5)
private String addTime; // 添加时间

// 构造方法
public Word() { }

public Word(String word, String phonetic, String definition, String example, int familiarity, String addTime) {
this.word = word;
this.phonetic = phonetic;
this.definition = definition;
this.example = example;
this.familiarity = familiarity;
this.addTime = addTime;
}

// getter和setter方法
public long getId() { return id; }
public void setId(long id) { this.id = id; }

public String getWord() { return word; }
public void setWord(String word) { this.word = word; }

public String getPhonetic() { return phonetic; }
public void setPhonetic(String phonetic) { this.phonetic = phonetic; }

public String getDefinition() { return definition; }
public void setDefinition(String definition) { this.definition = definition; }

public String getExample() { return example; }
public void setExample(String example) { this.example = example; }

public int getFamiliarity() { return familiarity; }
public void setFamiliarity(int familiarity) { this.familiarity = familiarity; }

public String getAddTime() { return addTime; }
public void setAddTime(String addTime) { this.addTime = addTime; }
}

数据库设计

接下来创建单词的数据库表结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.example.biji.vocabulary;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class WordDatabase extends SQLiteOpenHelper {
// 数据库相关常量
public static final String TABLE_NAME = "words";
public static final String ID = "_id";
public static final String WORD = "word";
public static final String PHONETIC = "phonetic";
public static final String DEFINITION = "definition";
public static final String EXAMPLE = "example";
public static final String FAMILIARITY = "familiarity";
public static final String ADD_TIME = "add_time";

// 构造方法
public WordDatabase(Context context) {
super(context, "vocabulary.db", null, 1);
}

@Override
public void onCreate(SQLiteDatabase db) {
// 创建单词表
db.execSQL("CREATE TABLE " + TABLE_NAME
+ "("
+ ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ WORD + " TEXT UNIQUE NOT NULL,"
+ PHONETIC + " TEXT,"
+ DEFINITION + " TEXT NOT NULL,"
+ EXAMPLE + " TEXT,"
+ FAMILIARITY + " INTEGER DEFAULT 0,"
+ ADD_TIME + " TEXT NOT NULL)"
);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 升级数据库时的操作
if (oldVersion < newVersion) {
// 可以在这里添加新的列或表
}
}
}

单词数据操作类

创建一个类用于处理单词的CRUD操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package com.example.biji.vocabulary;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

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

public class WordCRUD {
private WordDatabase dbHandler;
private SQLiteDatabase db;

// 列名数组
private static final String[] columns = {
WordDatabase.ID,
WordDatabase.WORD,
WordDatabase.PHONETIC,
WordDatabase.DEFINITION,
WordDatabase.EXAMPLE,
WordDatabase.FAMILIARITY,
WordDatabase.ADD_TIME
};

// 构造方法
public WordCRUD(Context context) {
dbHandler = new WordDatabase(context);
}

// 打开数据库连接
public void open() {
db = dbHandler.getWritableDatabase();
}

// 关闭数据库连接
public void close() {
dbHandler.close();
}

// 添加单词
public Word addWord(Word word) {
ContentValues values = new ContentValues();
values.put(WordDatabase.WORD, word.getWord());
values.put(WordDatabase.PHONETIC, word.getPhonetic());
values.put(WordDatabase.DEFINITION, word.getDefinition());
values.put(WordDatabase.EXAMPLE, word.getExample());
values.put(WordDatabase.FAMILIARITY, word.getFamiliarity());
values.put(WordDatabase.ADD_TIME, word.getAddTime());

long insertId = db.insert(WordDatabase.TABLE_NAME, null, values);
word.setId(insertId);

return word;
}

// 获取单个单词
public Word getWord(long id) {
Cursor cursor = db.query(WordDatabase.TABLE_NAME, columns,
WordDatabase.ID + "=?",
new String[] {String.valueOf(id)},
null, null, null, null);

if (cursor != null) cursor.moveToFirst();

Word word = new Word();
word.setId(cursor.getLong(0));
word.setWord(cursor.getString(1));
word.setPhonetic(cursor.getString(2));
word.setDefinition(cursor.getString(3));
word.setExample(cursor.getString(4));
word.setFamiliarity(cursor.getInt(5));
word.setAddTime(cursor.getString(6));

return word;
}

// 获取所有单词
public List<Word> getAllWords() {
List<Word> wordList = new ArrayList<>();
Cursor cursor = db.query(WordDatabase.TABLE_NAME, columns,
null, null, null, null,
WordDatabase.FAMILIARITY + " ASC");

if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
Word word = new Word();
word.setId(cursor.getLong(0));
word.setWord(cursor.getString(1));
word.setPhonetic(cursor.getString(2));
word.setDefinition(cursor.getString(3));
word.setExample(cursor.getString(4));
word.setFamiliarity(cursor.getInt(5));
word.setAddTime(cursor.getString(6));

wordList.add(word);
}
}

return wordList;
}

// 更新单词
public int updateWord(Word word) {
ContentValues values = new ContentValues();
values.put(WordDatabase.WORD, word.getWord());
values.put(WordDatabase.PHONETIC, word.getPhonetic());
values.put(WordDatabase.DEFINITION, word.getDefinition());
values.put(WordDatabase.EXAMPLE, word.getExample());
values.put(WordDatabase.FAMILIARITY, word.getFamiliarity());
values.put(WordDatabase.ADD_TIME, word.getAddTime());

return db.update(WordDatabase.TABLE_NAME, values,
WordDatabase.ID + "=?",
new String[] {String.valueOf(word.getId())});
}

// 删除单词
public void removeWord(Word word) {
db.delete(WordDatabase.TABLE_NAME,
WordDatabase.ID + "=?",
new String[] {String.valueOf(word.getId())});
}

// 根据单词文本搜索
public Word findWordByText(String wordText) {
Cursor cursor = db.query(WordDatabase.TABLE_NAME, columns,
WordDatabase.WORD + "=?",
new String[] {wordText},
null, null, null, null);

if (cursor != null && cursor.moveToFirst()) {
Word word = new Word();
word.setId(cursor.getLong(0));
word.setWord(cursor.getString(1));
word.setPhonetic(cursor.getString(2));
word.setDefinition(cursor.getString(3));
word.setExample(cursor.getString(4));
word.setFamiliarity(cursor.getInt(5));
word.setAddTime(cursor.getString(6));
return word;
}

return null;
}
}

单词列表界面

创建一个展示所有单词的列表界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!-- word_list_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/lvBackground">

<androidx.appcompat.widget.Toolbar
android:id="@+id/word_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
app:title="词汇助手"
app:titleTextColor="?attr/titleColor"
android:theme="?attr/toolbarTheme"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

<ListView
android:id="@+id/word_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/word_toolbar"
android:divider="@android:color/darker_gray"
android:dividerHeight="1dp"
android:padding="8dp" />

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_word_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_margin="16dp"
android:clickable="true"
android:contentDescription="添加单词"
app:srcCompat="@drawable/baseline_add_24" />

</RelativeLayout>

单词项布局

为ListView中的每个单词项创建布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<!-- word_item_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp"
android:background="@drawable/word_item_bg">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<TextView
android:id="@+id/tv_word"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Word"
android:textColor="?attr/tvMainColor"
android:textSize="18sp"
android:textStyle="bold" />

<TextView
android:id="@+id/tv_phonetic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="[fəˈnetɪk]"
android:textColor="?attr/tvSubColor"
android:textSize="16sp" />
</LinearLayout>

<TextView
android:id="@+id/tv_definition"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Definition"
android:textColor="?attr/tvMainColor"
android:textSize="14sp"
android:maxLines="2"
android:ellipsize="end" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">

<TextView
android:id="@+id/tv_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="2025-03-15"
android:textColor="?attr/tvSubColor"
android:textSize="12sp" />

<RatingBar
android:id="@+id/rb_familiarity"
style="?android:attr/ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:numStars="5"
android:stepSize="1"
android:rating="3" />
</LinearLayout>
</LinearLayout>

单词适配器

创建一个适配器用于将单词数据显示在ListView中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.example.biji.vocabulary;

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

import com.example.biji.R;

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

public class WordAdapter extends BaseAdapter implements Filterable {
private Context context;
private List<Word> wordList;
private List<Word> originalWordList;
private WordFilter wordFilter;

public WordAdapter(Context context, List<Word> wordList) {
this.context = context;
this.wordList = wordList;
this.originalWordList = new ArrayList<>(wordList);
}

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

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

@Override
public long getItemId(int position) {
return wordList.get(position).getId();
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;

if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.word_item_layout, parent, false);

holder = new ViewHolder();
holder.tvWord = convertView.findViewById(R.id.tv_word);
holder.tvPhonetic = convertView.findViewById(R.id.tv_phonetic);
holder.tvDefinition = convertView.findViewById(R.id.tv_definition);
holder.tvTime = convertView.findViewById(R.id.tv_time);
holder.rbFamiliarity = convertView.findViewById(R.id.rb_familiarity);

convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

Word word = wordList.get(position);
holder.tvWord.setText(word.getWord());
holder.tvPhonetic.setText(word.getPhonetic());
holder.tvDefinition.setText(word.getDefinition());
holder.tvTime.setText(word.getAddTime());
holder.rbFamiliarity.setRating(word.getFamiliarity());

return convertView;
}

static class ViewHolder {
TextView tvWord;
TextView tvPhonetic;
TextView tvDefinition;
TextView tvTime;
RatingBar rbFamiliarity;
}

@Override
public Filter getFilter() {
if (wordFilter == null) {
wordFilter = new WordFilter();
}
return wordFilter;
}

private class WordFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();

if (constraint == null || constraint.length() == 0) {
results.values = new ArrayList<>(originalWordList);
results.count = originalWordList.size();
} else {
String filterString = constraint.toString().toLowerCase();
List<Word> filteredList = new ArrayList<>();

for (Word word : originalWordList) {
if (word.getWord().toLowerCase().contains(filterString) ||
word.getDefinition().toLowerCase().contains(filterString)) {
filteredList.add(word);
}
}

results.values = filteredList;
results.count = filteredList.size();
}

return results;
}

@Override
@SuppressWarnings("unchecked")
protected void publishResults(CharSequence constraint, FilterResults results) {
wordList = (List<Word>) results.values;
notifyDataSetChanged();
}
}
}

单词列表Activity

实现单词列表的Activity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package com.example.biji.vocabulary;

import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;

import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;

import com.example.biji.BaseActivity;
import com.example.biji.R;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

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

public class WordListActivity extends BaseActivity {
private ListView wordListView;
private WordAdapter adapter;
private List<Word> wordList;
private WordCRUD wordCRUD;
private FloatingActionButton addWordFab;
private Toolbar toolbar;

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

// 初始化工具栏
toolbar = findViewById(R.id.word_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);

// 初始化控件
wordListView = findViewById(R.id.word_list_view);
addWordFab = findViewById(R.id.add_word_fab);

// 初始化数据
wordList = new ArrayList<>();
adapter = new WordAdapter(this, wordList);
wordListView.setAdapter(adapter);

// 初始化数据库操作
wordCRUD = new WordCRUD(this);

// 加载数据
loadWords();

// 设置监听器
addWordFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 跳转到添加单词界面
Intent intent = new Intent(WordListActivity.this, AddWordActivity.class);
startActivityForResult(intent, 0);
}
});

wordListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 跳转到单词详情界面
Word word = wordList.get(position);
Intent intent = new Intent(WordListActivity.this, WordDetailActivity.class);
intent.putExtra("word_id", word.getId());
startActivityForResult(intent, 1);
}
});
}

private void loadWords() {
wordCRUD.open();
wordList.clear();
wordList.addAll(wordCRUD.getAllWords());
wordCRUD.close();
adapter.notifyDataSetChanged();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
loadWords(); // 刷新列表
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.word_list_menu, menu);

// 配置搜索功能
MenuItem searchItem = menu.findItem(R.id.action_search_word);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setQueryHint("搜索单词或释义");

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}

@Override
public boolean onQueryTextChange(String newText) {
adapter.getFilter().filter(newText);
return true;
}
});

return true;
}

@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.action_test:
// 跳转到单词测试界面
startActivity(new Intent(this, WordTestActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}

单词详情页面

创建一个用于显示单词详细信息的界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<!-- word_detail_layout.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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="?attr/lvBackground">

<androidx.appcompat.widget.Toolbar
android:id="@+id/word_detail_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
app:titleTextColor="?attr/titleColor"
android:theme="?attr/toolbarTheme"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">

<TextView
android:id="@+id/tv_detail_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Word"
android:textColor="?attr/tvMainColor"
android:textSize="28sp"
android:textStyle="bold" />

<TextView
android:id="@+id/tv_detail_phonetic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="[fəˈnetɪk]"
android:textColor="?attr/tvSubColor"
android:textSize="18sp" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="释义"
android:textColor="?attr/tvMainColor"
android:textSize="16sp"
android:textStyle="bold" />

<TextView
android:id="@+id/tv_detail_definition"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Definition"
android:textColor="?attr/tvMainColor"
android:textSize="16sp" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="例句"
android:textColor="?attr/tvMainColor"
android:textSize="16sp"
android:textStyle="bold" />

<TextView
android:id="@+id/tv_detail_example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Example sentence"
android:textColor="?attr/tvMainColor"
android:textSize="16sp" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="熟悉度"
android:textColor="?attr/tvMainColor"
android:textSize="16sp"
android:textStyle="bold" />

<RatingBar
android:id="@+id/rb_detail_familiarity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:numStars="5"
android:stepSize="1"
android:rating="3" />

<Button
android:id="@+id/btn_update_familiarity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="更新熟悉度" />

<Button
android:id="@+id/btn_delete_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:backgroundTint="#FF5252"
android:text="删除单词" />

</LinearLayout>
</ScrollView>
</LinearLayout>

添加单词功能

创建一个用于添加新单词的界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<!-- add_word_layout.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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="?attr/lvBackground">

<androidx.appcompat.widget.Toolbar
android:id="@+id/add_word_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
app:title="添加单词"
app:titleTextColor="?attr/titleColor"
android:theme="?attr/toolbarTheme"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">

<EditText
android:id="@+id/et_add_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="单词"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">

<EditText
android:id="@+id/et_add_phonetic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="音标"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">

<EditText
android:id="@+id/et_add_definition"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="释义"
android:inputType="textMultiLine"
android:minLines="3" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">

<EditText
android:id="@+id/et_add_example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="例句"
android:inputType="textMultiLine"
android:minLines="2" />
</com.google.android.material.textfield.TextInputLayout>

<Button
android:id="@+id/btn_search_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="在线查询" />

<Button
android:id="@+id/btn_save_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="保存单词" />

</LinearLayout>
</ScrollView>
</LinearLayout>