20212325 2023-2024-2 《移动平台开发与实践》综合实践
文章目录
1.综合实践内容
- 实现学生信息管理系统:
- 学生信息管理系统的用户登录信息MD5加密匹配, SharedPreferences 实现自动登录;
- 使用android studio内置SQLite数据库,能够进行学生信息的增、删、查、改;
- UI界面完善简洁,一目了然,函数调用逻辑清晰完整。
2.实践过程
2.1数据库设计
-
学生信息表:
字段 数据类型 主键 外键 是否为空 说明 number varchar 是 否 否 学号 name varchar 否 否 否 姓名 Year varchar 否 否 否 年龄 Sex varchar 否 否 否 性别 Birth varchar 否 否 否 出生日期 Address varchar 否 否 否 住址 Phone varchar 否 否 否 电话
2.2总体调用逻辑
2.3与UI的对应关系
-
确定了需要的UI界面后先写前端的UI界面代码。
2.4编写UI界面
-
登录界面activity_login
-
登录加载界面activity_begin(在登录后出现学生信息系统图标模仿app加载)
-
系统欢迎&功能选择界面activity_main
-
学生信息录入界面activity_add_students
-
学生信息浏览界面activity_search_students
-
学生信息修改界面activity_mod_students
-
学生信息检索查询界面activity_query_students
-
学生信息删除界面activity_delete_students
2.5代码编写逻辑
2.5.1用户登录
-
etName
和etPwd
两个文本控件用来输入用户名和密码:etName = findViewById(R.id.et_name); etPwd = findViewById(R.id.et_pwd);
-
用户点击
btnLogin
后触发登录操作:btnLogin = findViewById(R.id.btn_login);
-
初次登录需要完整填写用户密码,这里我使用了md5加密来验证密码:
if (name.equals("admin") && (MD5Utils.code(pwd)).equals(MD5Utils.code("20212325"))) { Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
新建MD5Utils类用于实现32位md5加密,将预设密码(20212325)的md5值和用户输入密码的md5值比对,比对成功则登录成功。
-
我在这里还使用了
CheckBox
控件,用于选择是否记住用户名和密码,选择记住就会将用户名和密码存储到SharedPreferences
:if (cb.isChecked()) { editor.putString("cb_name", name); editor.putString("cb_pwd", pwd); editor.putBoolean("cb_checked", true); }
如果没有勾选,则更新
SharedPreferences
不记住登录状态:else { editor.putBoolean("cb_checked", false); }
-
实现自动登录:
之前的课上我们学过SharedPreferences用法,目前只有MODE_PRIVATE一种模式能使用
这里SharedPreferences从“zidong”文件中读取记住的用户名和密码。
sp = getSharedPreferences("zidong", Context.MODE_PRIVATE); aBoolean = sp.getBoolean("cb_checked", false); if (aBoolean) { Intent intent = new Intent(LoginActivity.this, BeginActivity.class); startActivity(intent); finish(); }
通过
aBoolean = sp.getBoolean("cb_checked", false);
判断用户之前是否选择了记住登录状态,如果aBoolean
为true
,则直接跳转到BeginActivity
,实现自动登录。
2.5.2登录加载
-
在登录成功之后我想模仿app的加载界面,主要使用了Timer和TimerTask进行延时跳转
Timer timer=new Timer(); TimerTask task=new TimerTask() { @Override public void run() { Intent intent=new Intent(BeginActivity.this,MainActivity.class); startActivity(intent); finish(); } }; timer.schedule(task,3000);
这里的3000毫秒就是显示启动画面后,等待3秒,然后自动跳转到主界面。
2.5.3系统欢迎和功能选择界面
-
这一页为系统的中心界面,监听不同的按钮点击事件,提供登录后所有操作的入口并进行相应跳转。
public void onClick(View v) { int id=v.getId(); if(id==R.id.add_students){ Intent intent1=new Intent(MainActivity.this,AddStudentsActivity.class); startActivity(intent1); } else if (id== R.id.search_students) { Intent intent2=new Intent(MainActivity.this,SearchStudentsActivity.class); startActivity(intent2); } else if (id== R.id.mod_students) { Intent intent3=new Intent(MainActivity.this,ModStudentsActivity.class); startActivity(intent3); } else if (id== R.id.query_students) { Intent intent4=new Intent(MainActivity.this,QueryStudentsActivity.class); startActivity(intent4); } else if (id==R.id.delete_students) { Intent intent5=new Intent(MainActivity.this,DeleteStudentsActivity.class); startActivity(intent5); } else if (id== R.id.btn_back) { DigLogShow(); } } /**
-
另外本页还可以退出登录,用户点击退出时就清空在登录时写入“zidong”文件的登录信息,并跳回登录界面
private void DigLogShow() { final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("退出提示") .setMessage("是否退出?") .setPositiveButton("否", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .setNegativeButton("是", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent6=new Intent(MainActivity.this,LoginActivity.class); SharedPreferences sharedPreferences = getSharedPreferences("zidong", Context.MODE_PRIVATE); sharedPreferences.edit().clear().apply(); Toast.makeText(MainActivity.this,"退出成功!",Toast.LENGTH_LONG).show(); startActivity(intent6); } }); builder.create().show(); }
2.5.4学生信息浏览、添加、修改、删除、查找
- 学生信息浏览
点击学生信息浏览功能后加载数据,因为学生信息以列表ListView形式呈现,为了将学生信息数据和ListView绑定,这里用到了适配器,使用适配器绑定控件和学生信息之后学生信息便可以动态更新,以便屏幕上的信息可滚动呈现。
mData = new ArrayList<>();
System.out.println("长度为:" + mData.size());//设置适配器:加载每一行数据到列表当中
adapter = new Adapter(this, mData);
listView.setAdapter(adapter);
loadData();//显示学生信息列表函数
之后便可以动态更新
private void loadData() {
List<StudentsBean> list = DbStudents.search();
mData.clear();
mData.addAll(list);
adapter.notifyDataSetChanged();//实时更新
}
适配器adapter的代码逻辑,先定义变量并初始化:
context
用于获取当前环境的引用。mData
列表,存储我们要显示的数据对象StudentsBean
。inflater
用于将XML布局转换为视图对象。
Context context;
List<StudentsBean> mData;
LayoutInflater inflater;
public Adapter(Context context, List<StudentsBean> mData) {
this.context = context;
this.mData = mData;
inflater = LayoutInflater.from(context);
}
返回需要的各类数据,重写getview方法,返回视图
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_students, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
StudentsBean bean=mData.get(position);
holder.TvNumber.setText("学号:"+bean.getNumber());
holder.TvName.setText("姓名:"+bean.getName());
holder.TvYear.setText("年龄:"+bean.getYear());
holder.TvSex.setText("性别:"+bean.getSex());
holder.TvBirth.setText("生日:"+bean.getBirth());
holder.TvAddress.setText("地址:"+bean.getAddress());
holder.TvPhone.setText("电话:"+bean.getPhone());
return convertView;
}
使用ViewHolder存储对于列表项的引用,可以避免在每次 getView
调用时重复调用 findViewById
,可以大大提高性能。
class ViewHolder {
TextView TvNumber,TvName, TvYear, TvSex, TvBirth,TvAddress,TvPhone;
public ViewHolder(View view) {
TvNumber= view.findViewById(R.id.tv_number);
TvName = view.findViewById(R.id.tv_name);
TvYear = view.findViewById(R.id.tv_year);
TvSex = view.findViewById(R.id.tv_sex);
TvBirth = view.findViewById(R.id.tv_birth);
TvAddress = view.findViewById(R.id.tv_address);
TvPhone = view.findViewById(R.id.tv_phone);
}
}
-
学生信息添加、修改
在编写代码时一开始想把修改和添加编写到一起,后面发现使用一个页面非常乱,于是又添加了一个修改的搜索页面,只是在AddStudentActivity中根据intent中
getStringExtra
方法获取的number
是否为空判断是否为修改操作
private void initData() {
Intent intent = getIntent();
if (intent != null) {
number = intent.getStringExtra("number");
if (number != null) {
tv_title.setText("修改学生信息");
BtnSave.setText("修 改");
EtNumber.setText(intent.getStringExtra("number"));
EtName.setText(intent.getStringExtra("name"));
EtYear.setText(intent.getStringExtra("year"));
EtSex.setText(intent.getStringExtra("sex"));
EtBirth.setText(intent.getStringExtra("birth"));
EtAddress.setText(intent.getStringExtra("address"));
EtPhone.setText(intent.getStringExtra("phone"));
}
}
}
若 number
为null则保持默认界面,默认为添加操作
if(id==R.id.btn_back) {
Intent intent1 = new Intent(AddStudentsActivity.this, MainActivity.class);
startActivity(intent1);
} else if (id== R.id.btn_save) {
//输入
String number = EtNumber.getText().toString().trim();
String name = EtName.getText().toString().trim();
String year = EtYear.getText().toString().trim();
String sex = EtSex.getText().toString().trim();
String birth = EtBirth.getText().toString().trim();
String address = EtAddress.getText().toString().trim();
String phone = EtPhone.getText().toString().trim();
StudentsBean studentsBeans = dbStudents.QueryFromStudentByNumber(number);
添加成功提示框:
private void showDeleteDialog() {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("录入成功提示")
.setMessage("已成功录入!\n是否继续?")
.setPositiveButton("否", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent=new Intent(AddStudentsActivity.this,MainActivity.class);
startActivity(intent);
}
})
.setNegativeButton("是", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//将输入框清空
EtNumber.setText("");
EtName.setText("");
EtYear.setText("");
EtSex.setText("");
EtBirth.setText("");
EtAddress.setText("");
EtPhone.setText("");
}
});
builder.create().show();
}
若 number
不为null界面标题 (tv_title
) 设置为 "修改学生信息"
,保存按钮 (BtnSave
) 的文本设置为 "修 改"
。使用从 Intent
中获取的其他额外信息填充界面上剩余的 EditText
控件,包括学号、姓名、年龄、性别、出生日期、地址和电话。
if (number != null) {//修改操作
if (name.length() > 0) {
if (dbStudents.updateStudent(number, name, year,sex,birth,address,phone)) {
Toast.makeText(AddStudentsActivity.this,"修改成功",Toast.LENGTH_LONG).show();
setResult(2);
showDeleteDialog2();
}
} else {
Toast.makeText(AddStudentsActivity.this,"修改的内容不能为空!",Toast.LENGTH_LONG).show();
}
}
修改成功提示框:
private void showDeleteDialog2() {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("修改成功提示")
.setMessage("已成功修改!")
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent=new Intent(AddStudentsActivity.this,MainActivity.class);
startActivity(intent);
}
});
builder.create().show();
}
下面为内部调用图:
- 学生信息删除
点击删除功能后,要先显示出目前数据库中已有的学生信息数据,然后我想的是长按进行删除。
先加载学生信息数据:
private void loadData() {
List<StudentsBean> list = DbStudents.search();
mData.clear();
mData.addAll(list);
adapter.notifyDataSetChanged();
}
然后设置长按时间监听器setLVClickListener
,当用户长按列表项时,调用 deleteItem
方法进行删除操作:
private void setLVClickListener() {
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
StudentsBean studentsBean = mData.get(position);
deleteItem(studentsBean);
return false;
}
});
}
deleteItem
方法显示一个对话框,让用户确认是否删除选中的学生记录。如果用户确认,就调用 DbStudents
类的 deleteItemFromStudentByNumber
方法从数据库中删除记录,并从数据源 mData
中移除对应的 StudentsBean
对象,然后实时通知适配器更新界面:
private void deleteItem(final StudentsBean studentsBean) {
//弹出对话框确认删除操作
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示信息").setMessage("您确定要删除这条记录么?")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
DbStudents.deleteItemFromStudentByNumber(studentsBean.getNumber());
mData.remove(studentsBean);//实时刷新,从数据源删除
adapter.notifyDataSetChanged();
}
});
builder.create().show();
}
- 学生信息查找
同学生信息浏览一样,查找与其非常类似,所以为了更方便地将学生信息和ListView动态绑定,这里也采用适配器,创建好 Adapter
适配器实例并将其设置给 ListView
。同时绑定 ListView
和 EditText
控件,初始化 mData
,创建 DbStudents
数据库操作类的实例
QueryLv = findViewById(R.id.query_lv);
EtName = findViewById(R.id.et_name);
EtNumber = findViewById(R.id.et_number);
mData = new ArrayList<>();
adapter = new Adapter(this, mData);
QueryLv.setAdapter(adapter);
dbStudents = new DbStudents(this);
实现 View.OnClickListener
接口的 onClick
方法,处理按钮点击事件
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.iv_back) {
// 处理返回按钮点击事件
} else if (id == R.id.iv_query) {
// 处理查询按钮点击事件
}
}
当用户点击返回按钮时,创建一个新的 Intent
跳转到 MainActivity
并完成当前 QueryStudentsActivity
if (id == R.id.iv_back) {
Intent intent = new Intent(QueryStudentsActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
当用户点击查询按钮时,获取 EditText
中输入的学号和姓名,验证输入是否有效。如果输入有效,调用 DbStudents
类的 getStudentListByNumberAndName
方法执行查询,获取查询结果列表,更新 mData
,并通知适配器数据已更改,之后更新 ListView
显示。查询成功后就清空原有数据,将查询结果添加到 mData
中,并调用适配器的 notifyDataSetChanged
方法来刷新 ListView
显示新的学生信息列表。
else if (id == R.id.iv_query) {
String number = EtNumber.getText().toString().trim();
String name = EtName.getText().toString().trim();
if (TextUtils.isEmpty(number) || TextUtils.isEmpty(name)) {
// 检查输入是否为空,并提示用户
} else {
// 执行查询操作
List<StudentsBean> list = DbStudents.getStudentListByNumberAndName(number, name);
mData.clear();
mData.addAll(list);
adapter.notifyDataSetChanged();
}
}
2.5.5数据库交互操作
数据库的操作非常简单,在数据库文件中写好创建库、增、删、查、改的相关代码:
public class DbStudents extends SQLiteOpenHelper {
private static SQLiteDatabase db;
public DbStudents(@Nullable Context context) {
super(context, "birth.db", null, 1);
System.out.println("数据库创建成功");
db = getReadableDatabase();
}
public static List<StudentsBean> getStudentListByNumberAndName(String number , String name) {
List<StudentsBean> list = new ArrayList<>();
String sql = "select * from student where number like '%" + number + "%' and name like '%" + name + "%'";
Cursor cursor = db.rawQuery(sql, null);
while (cursor.moveToNext()) {
@SuppressLint("Range") int id = cursor.getInt(cursor.getColumnIndex("id"));
@SuppressLint("Range") String number1 = cursor.getString(cursor.getColumnIndex("number"));
@SuppressLint("Range") String name1 = cursor.getString(cursor.getColumnIndex("name"));
@SuppressLint("Range") String year = cursor.getString(cursor.getColumnIndex("year"));
@SuppressLint("Range") String sex = cursor.getString(cursor.getColumnIndex("sex"));
@SuppressLint("Range") String birth = cursor.getString(cursor.getColumnIndex("birth"));
@SuppressLint("Range") String address = cursor.getString(cursor.getColumnIndex("address"));
@SuppressLint("Range") String phone = cursor.getString(cursor.getColumnIndex("phone"));
StudentsBean studentsBean = new StudentsBean(id,number1, name1,year, sex,birth,address,phone);
list.add(studentsBean);
}
return list;
}
public static List<StudentsBean> getStudentListByNumber(String number) {
List<StudentsBean> list = new ArrayList<>();
String sql = "select * from student where number="+number;
Cursor cursor = db.rawQuery(sql, null);
while (cursor.moveToNext()) {
@SuppressLint("Range") int id = cursor.getInt(cursor.getColumnIndex("id"));
@SuppressLint("Range") String number1 = cursor.getString(cursor.getColumnIndex("number"));
@SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex("name"));
@SuppressLint("Range") String year = cursor.getString(cursor.getColumnIndex("year"));
@SuppressLint("Range") String sex = cursor.getString(cursor.getColumnIndex("sex"));
@SuppressLint("Range") String birth = cursor.getString(cursor.getColumnIndex("birth"));
@SuppressLint("Range") String address = cursor.getString(cursor.getColumnIndex("address"));
@SuppressLint("Range") String phone = cursor.getString(cursor.getColumnIndex("phone"));
StudentsBean studentsBean = new StudentsBean(id,number1, name,year, sex,birth,address,phone);
list.add(studentsBean);
}
return list;
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = ("CREATE TABLE student(id INTEGER PRIMARY KEY AUTOINCREMENT," +
"number VARCHAR(50) not null," +
"name VARCHAR(20) not null," +
"year VARCHAR(2) not null," +
"sex VARCHAR(2) not null," +
"birth varchar(60) not null," +
"address Varchar(60) not null," +
"phone VARCHAR(11) not null)");
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists student");
onCreate(db);
}
public void insertStudents(String number,String name, String year, String sex, String birth,
String address,String phone) {
db.execSQL("INSERT INTO student(number,name,year,sex,birth,address,phone)VALUES(?,?,?,?,?,?,?)",
new String[]{number,name,year,sex, birth,address,phone});
}
/**
* 浏览
* */
public static List<StudentsBean> search() {
List<StudentsBean> list = new ArrayList<>();
String sql = "select * from student order by number";
Cursor cursor = db.rawQuery(sql, null);
while (cursor.moveToNext()) {
@SuppressLint("Range") int id = cursor.getInt(cursor.getColumnIndex("id"));
@SuppressLint("Range") String number = cursor.getString(cursor.getColumnIndex("number"));
@SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex("name"));
@SuppressLint("Range") String year = cursor.getString(cursor.getColumnIndex("year"));
@SuppressLint("Range") String sex = cursor.getString(cursor.getColumnIndex("sex"));
@SuppressLint("Range") String birth = cursor.getString(cursor.getColumnIndex("birth"));
@SuppressLint("Range") String address = cursor.getString(cursor.getColumnIndex("address"));
@SuppressLint("Range") String phone = cursor.getString(cursor.getColumnIndex("phone"));
StudentsBean studentsBean = new StudentsBean(id,number, name,year, sex,birth,address,phone);
list.add(studentsBean);
}
return list;
}
public static int deleteItemFromStudentByNumber(String number) {
int i = db.delete("student", "number=?", new String[]{number + ""});
return i;
}
/**
* 查询学号是否存在
* */
public static StudentsBean QueryFromStudentByNumber(String number) {
// List<StudentsBean> list = new ArrayList<>();
StudentsBean studentsBean = null;
String sql = "select * from student where number ="+number;
Cursor cursor = db.rawQuery(sql, null);
while (cursor.moveToNext()) {
@SuppressLint("Range") int id = cursor.getInt(cursor.getColumnIndex("id"));
@SuppressLint("Range") String number1 = cursor.getString(cursor.getColumnIndex("number"));
@SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex("name"));
@SuppressLint("Range") String year = cursor.getString(cursor.getColumnIndex("year"));
@SuppressLint("Range") String sex = cursor.getString(cursor.getColumnIndex("sex"));
@SuppressLint("Range") String birth = cursor.getString(cursor.getColumnIndex("birth"));
@SuppressLint("Range") String address = cursor.getString(cursor.getColumnIndex("address"));
@SuppressLint("Range") String phone = cursor.getString(cursor.getColumnIndex("phone"));
studentsBean = new StudentsBean(id, number1, name, year, sex, birth, address, phone);
// list.add(studentsBean);
}
return studentsBean;
}
//修改数据
public boolean updateStudent(String number,String name,String year,String sex,String birth,String address,String phone){
ContentValues contentValues=new ContentValues();
contentValues.put("name",name);
contentValues.put("year",year);
contentValues.put("sex",sex);
contentValues.put("birth",birth);
contentValues.put("address",address);
contentValues.put("phone",phone);
String sql="number"+"=?";
String[] strings=new String[]{number};
return
db.update("student",contentValues,sql,strings)>0;
}
}
2.6程序运行视频
学生信息系统
3.学习中遇到的问题及解决
-
问题1:将学生信息录入和修改的代码合并在一个java文件后前端无法识别是录入还是修改操作,但是二者实际操作类似,把代码拆开成两个java则赘余
-
问题1解决方案:单独写一个学生信息修改的跳转界面,在该界面用户搜索要修改的学生学号,点击该学生信息后,跳转到修改/录入界面,根据intent中GetStringExtra获取传入的学号,有学号信息这说明为修改操作,对应页面也进行修改。
-
问题2:在后台线程中更新组件引起
-
问题2解决方案:使用
runOnUiThread()
方法将UI更新操作切换回主线程执行 -
问题3:适配器与数据绑定后ListView显示还是空的
-
问题3解决方案:在学生信息录入、更新、删除后调用适配器的
notifyDataSetChanged()
方法 -
问题4:滑动列表有一些卡顿
-
问题4解决方法:使用ViewHolder模式来缓存视图,确保每个列表项的视图只被创建一次,避免在每次调用时进行复杂的布局操作或重复的视图创建。因为学生信息列表项的布局中包含图片,所以在
getView()
中,就只更新变化的部分,不用每次更新整个视图。
4.综合实践学习感悟
-
本次开发过程中,我对Android中的适配器和Intent机制有了更深入的理解和实践。适配器模式在Android中扮演着至关重要的角色,作为数据和视图之间的桥梁,负责将数据源中的数据展示到用户界面上。适配器不仅填充ListView,通过复用视图提高性能,并动态展示学生信息列表。
-
编写适配器时,在
getView()
方法中,本次采用了ViewHolder模式来优化列表的滚动性能,通过缓存视图引用避免重复调用findViewById()
,这样就可以减少内存分配和提高滚动灵敏度,可见适配器的灵活性。其他视图组件比如GridView和RecyclerView也能跟其搭配使用。在上第二节课的时候,我们就知道Intent机制是Android组件间通信的基石。使用Intent主要是在不同的Activity之间传递数据和请求、启动新的Activity、比如从主界面跳转到添加学生信息的界面,或者从查询结果跳转到修改学生信息的界面。在进行学生信息修改和录入时我也是通过Intent携带的额外数据学号来区分用户选择的操作类型,最终通过
getIntent().getStringExtra()
方法获取后对应地进行页面修改。以及还有使用Intent进行结果的回传。在添加或修改学生信息后,我是通过设置结果码setResult()
和Intent,然后将结果传递回发起请求的Activity,接着进行进一步的刷新界面或显示提示信息。
5.课程总结
随着这段丰富而充实的学习旅程渐渐落下帷幕,我心中充满了感激与收获。王老师的课程讲解深入浅出,为我们打开了Android开发的大门。一学期以来,我不仅系统地掌握了从基础语法到应用的各方位知识,更在实践中深化了对技术的理解与应用。
课程内容涵盖了Android开发的多个方面,从界面布局的精细打磨到UI组件的灵活运用,从资源管理与权限控制的必要知识到四大组件的深入理解,再到数据存储和网络编程的实用技巧,以及多媒体功能的集成应用。这一系列的知识点,通过王老师条理清晰的讲解和精心设计的实验内容,变得生动而易于掌握。
课程中王老师给我们推荐了《第一行代码:Android》这本书,书籍内容也是对课程内容很好的补充,由浅入深,整体很全面,从最基本的四大组件到MVVM模式、异步、协程等,有着大量Kotlin的介绍,都是一些比较使用的编码技巧,实践参考性比较强,上课的时候没怎么听懂的在书中大部分也能找到答案。
最后对王老师的耐心指导表示最诚挚的感谢。也感谢和我一起学习的同学们的一路陪伴,这是我本科阶段最后的课业学习,我所学到的知识和技能,我所遇到的bug踩到的坑,与老师和同学们共度的时光,与代码互相折磨的时刻,我都将永远珍惜。