Android Kotlin实现的小项目总结
小项目功能:
实现一个使用SQLite存储数据的用户登录、注册、用户信息显示界面和修改登录用户信息,同时实现一个小的聊天界面可以回答一些简单的问题,使用广播实现强制下线的功能,利用SQLiteStudio软件进行数据库管理。
实现步骤:
SQLiteStudio 的使用可以参考—>SQLite数据查看工具SQLiteStudio 可以实现数据同步
数据库使用room进行操作,User实例类,Dao接口类数据库操作,Appdatabase建立数据库连接,都是使用注解
@Parcelize @Entity
class User(
var avatar: Int? = null,
var name: String? = null,
var account: String? = null,
var pwd: String? = null,
var phone: String? = null
) : Parcelable {
@PrimaryKey(autoGenerate = true)
var id :Long = 0
}
@Dao
interface UserDao {
@Insert
fun insertUser(user: User) : Long
@Delete
fun deleteUser(user: User)
@Update
fun updateUser(newUser: User)
@Query("select * from User")
fun loadAllUsers(): List<User>
//非实体类参数
@Query("delete from User where account = :account")
fun deleteUserByAccount(account: String)
}
@Database(version = 1, entities = [User::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var instance: AppDatabase? = null
@Synchronized
fun getDatabase(context: Context): AppDatabase {
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "btech.db")
.build().apply {
instance = this
}
}
}
}
1. 开屏显示界面:
首先利用多线程实现一个进入程序的开屏显示:
class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//隐藏状态栏
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
//隐藏标题栏
supportActionBar?.hide()
setContentView(R.layout.activity_splash)
thread {
Thread.sleep(2000)
//使用了MainActivity伴随对象
MainActivity.startActivity(this)
finish()
}
}
}
开屏的布局文件,其中backgroud设置一张背景图片即可,图片制作可以使用这个网站 --> 不凡App制作
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/img3"
tools:context=".SplashActivity" />
2. 登录功能:
获取账号和密码然后利用room进行数据库操作,进行配对,成功就进入首页界面否则提醒密码或者账号错误等信息。
//登录界面的伴随对象
companion object {
//伴随对象,属性-->登录用户
var user: User? = null
fun startActivity(context: Context) {
val intent = Intent(context, MainActivity::class.java)
context.startActivity(intent)
}
}
//登录的实现
//登录是否成功的标志
var flag = false
val account = editTextNumber.text.toString()
val pwd = editTextTextPassword.text.toString()
//room查询user表中所有的数据
val userDao = AppDatabase.getDatabase(this).userDao()
thread {
//使用Looper把线程加到消息队列中,因为Toast显示在this中
Looper.prepare()
//查询数据库中所有信息,进行配对
for (user in userDao.loadAllUsers()) {
if (account == user.account && pwd == user.pwd) {
//以user登录
Companion.user = user
//记住密码
val editor = prefs?.edit()
if (checkBox4.isChecked) {
editor?.putBoolean("remember_password", true)
editor?.putString("account", account)
editor?.putString("password", pwd)
} else {
editor?.clear()
}
editor?.apply()
//首页界面
IndexActivity.startActivity(this)
Toast.makeText(this, "登录成功!!!!", Toast.LENGTH_SHORT).show()
finish()
flag = true
break
}
}
if (account == "" || pwd == "") {
Toast.makeText(this, "输入不能为空!!", Toast.LENGTH_SHORT).show()
} else if (!flag) {
Toast.makeText(this, "账号或密码错误!!", Toast.LENGTH_SHORT).show()
}
Looper.loop()
}
3. 注册或添加用户功能:
获取数据库中的信息,如果用户账号已存在则提醒,否则注册成功
val userDao = AppDatabase.getDatabase(this).userDao()
thread {
//查询数据库中所有的信息
for (user in userDao.loadAllUsers()) {
users.add(user)
}
}
R.id.button3 -> {
var flag = true
val avatar = arr[i]
val name = editTextTextPersonName2.text.toString()
val account = editTextNumber2.text.toString()
val pwd = editTextTextPassword2.text.toString()
val phone = editTextNumber4.text.toString()
//返回用户数据
val user = User(avatar, name, account, pwd, phone)
//手机号码正则表达式,bool类型
val regExp =
"^((13[0-9])|(15[^4])|(18[0-9])|(17[0-8])|(14[5-9])|(166)|(19[8,9])|)\\d{8}$"
val p = Pattern.compile(regExp)
val m = p.matcher(phone)
//判断用户是否存在
for (u in users) {
if (u.account == user.account) {
Toast.makeText(this, "用户已存在", Toast.LENGTH_SHORT).show()
flag = false
break
}
}
if (name == "" || account == "" || pwd == "" || phone == "") {
Toast.makeText(this, "输入不能为空!!", Toast.LENGTH_SHORT).show()
flag = false
} else if (!m.matches()) {
editTextNumber4.setText("")
Toast.makeText(this, "手机号码输入格式不正确", Toast.LENGTH_SHORT).show()
flag = false
}
if (flag) {
Toast.makeText(this, "注册成功!!", Toast.LENGTH_SHORT).show()
thread {
val userDao = AppDatabase.getDatabase(this).userDao()
userDao.insertUser(user)
}
finish()
}
}
4. 首页显示所有用户:
使用recycleView控件,然后加适配器显示子项。先把数据库中的信息全部加入到data中(data是一个ArrayList对象)
val userDao = AppDatabase.getDatabase(this).userDao()
thread {
//查询数据库中信息
for (user in userDao.loadAllUsers()) {
data.add(user)
}
}
//列表显示
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
//使用decoration进行分组
//recyclerView.addItemDecoration(MyItemDecoration())
val adapter = UserAdapter(data)
recyclerView.adapter = adapter
//recycleview的适配器
class UserAdapter(val userList: List<User>) :
RecyclerView.Adapter<UserAdapter.ViewHolder>() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val userImg: ImageView = view.img
val userName: TextView = view.name
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
//加载子项布局文件
val view = LayoutInflater.from(parent.context).inflate(R.layout.recycle_item, parent, false)
val viewHolder = ViewHolder(view)
//点击子项进入聊天界面
viewHolder.itemView.setOnClickListener {
ChatActivity.starActivity(parent.context)
}
//点击头像图片进入用户详细信息界面
viewHolder.userImg.setOnClickListener {
val position = viewHolder.adapterPosition
val user = userList[position]
UserActivity.statActivity(parent.context, user)
}
return viewHolder
}
override fun getItemCount() = userList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val user = userList[position]
user.avatar?.let { holder.userImg.setImageResource(it) }
holder.userName.text = user.name
}
}
5. 详细信息显示和删除功能:
通过intent获取点击用户的信息,然后把信息显示在一个卡片布局的text中即可,然后再实现一个删除操作,利用room的删除操作即可通过id找到数据并且删除数据库中的数据,同时还可以通过悬浮按钮进入聊天界面。界面的UI设置是使用的material的设计如,折叠式标题栏、、卡片布局、悬浮按钮等等
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val user = intent.getParcelableExtra<User>("user")
//折叠式标题栏,设置标题
collapsingToolbar.title=user?.name
//加载图片
Glide.with(this).load(R.drawable.cherry).into(userImageView)
//用户信息
userContentText.text = generaterContent(user)
Toast.makeText(this, user?.name,Toast.LENGTH_SHORT).show()
chat.setOnClickListener {
ChatActivity.starActivity(this)
}
deleteUser.setOnClickListener {
thread {
val userDao = AppDatabase.getDatabase(this).userDao()
if (user != null) {
userDao.deleteUser(user)
}
}
Toast.makeText(this,"删除成功!!",Toast.LENGTH_SHORT).show()
finish()
}
6. 小机器人聊天功能:
两个子项布局文件一个发送消息的一个接收消息的,同时背景图要使用.9.png格式的。每发送一次消息就在msglist中添加一条信息,, 然后通过adapter?.notifyItemInserted(msglist.size - 1)
刷新recycleview界面
val layoutManager = LinearLayoutManager(this)
recyclerView2.layoutManager = layoutManager
adapter = MsgAdapter(msglist)
recyclerView2.adapter = adapter
button8.setOnClickListener(this)
button8 -> {
val content = editTextTextPersonName3.text.toString()
if (content.isNotEmpty()) {
val msg = Msg(content, Msg.TYPE_SENT)
msglist.add(msg)
response(content)
editTextTextPersonName3.setText("")
adapter?.notifyItemInserted(msglist.size - 1)
recyclerView2.scrollToPosition(msglist.size - 1)
}
}
聊天界面的适配器文件,其中R.layout.msg_left和R.layout.msg_right分别为接收 布局和发送布局,通过override fun getItemViewType(position: Int): Int
判断信息的类型,然后放入不同的子项布局中。
class MsgAdapter(val msglist:List<Msg>):RecyclerView.Adapter<RecyclerView.ViewHolder>() {
inner class LeftViewHolder(view: View):RecyclerView.ViewHolder(view){
val leftMsg : TextView = view.leftMsg
}
inner class RightViewHolder(view: View):RecyclerView.ViewHolder(view){
val rightMsg : TextView = view.rightMsg
}
override fun getItemViewType(position: Int): Int {
val msg = msglist[position]
return msg.type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = if(viewType == Msg.TYPE_RECEIVED){
val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_left,parent,false)
LeftViewHolder(view)
}else{
val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_right, parent,false)
RightViewHolder(view)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val msg = msglist[position]
when(holder){
is LeftViewHolder -> holder.leftMsg.text = msg.content
is RightViewHolder -> holder.rightMsg.text = msg.content
else -> throw IllegalArgumentException()
}
}
override fun getItemCount(): Int = msglist.size
}
7. 修改用户信息功能:
room进行数据库操作,通过id找到数据库中的信息,然后进行更新操作
R.id.button5 -> {
val name = editTextTextPersonName.text
val pwd = editTextTextPassword3.text
val phone = editTextPhone.text
if (!iname.equals(name.toString()) || !ipwd.equals(pwd.toString())
|| !iphone.equals(phone.toString())) {
val userDao = AppDatabase.getDatabase(this).userDao()
thread {
//改变user
MainActivity.user?.let {
it.name = name.toString()
it.pwd = pwd.toString()
it.phone = phone.toString()
}
//通过id主键去修改数据库中的数据
MainActivity.user?.let { userDao.updateUser(it) }
}
val intent = Intent("com.example.Force_Exit")
sendBroadcast(intent)
} else {
Toast.makeText(this, "请修改信息", Toast.LENGTH_SHORT).show()
}
}
8. 强制下线和一键退出功能:
每一个Activity都继承与基类使用单例类ActivityCollector把创建的Activity添加进去或者删除,就可以实现在任意一个Activity一键退出和知道在那个Activity中,同时基类也可以接收任意一个Activity中发送的广播。
//单例类
object ActivityCollector {
private val activities = ArrayList<Activity>()
fun addActivity(activity: Activity){
activities.add(activity)
}
fun removeActivity(activity: Activity){
activities.remove(activity)
}
fun finishAll(){
for (activity in activities){
if(!activity.isFinishing){
activity.finish()
}
}
activities.clear()
}
}
open class BaseActivity : AppCompatActivity() {
lateinit var receiver: ForceExitReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ActivityCollector.addActivity(this)
}
override fun onResume() {
super.onResume()
val intentFilter = IntentFilter()
intentFilter.addAction("com.example.Force_Exit")
receiver = ForceExitReceiver()
registerReceiver(receiver, intentFilter)
}
override fun onPause() {
super.onPause()
unregisterReceiver(receiver)
}
override fun onDestroy() {
super.onDestroy()
ActivityCollector.removeActivity(this)
}
//接收广播的操作,强制下线
inner class ForceExitReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
AlertDialog.Builder(context).apply {
setTitle("Warning")
setMessage("You are forced to be offline.")
setCancelable(false)
setPositiveButton("OK") { _, _ ->
if (context != null) {
ActivityCollector.finishAll()
MainActivity.startActivity(context)
}
}
show()
}
}
}
}
遇到的问题解决:
1)Parcelable注解传递对象,属性不能序列化,要在主构造函数中。
@Parcelize @Entity
class User(
var avatar: Int? = null,
var name: String? = null,
var account: String? = null,
var pwd: String? = null,
var phone: String? = null,
@PrimaryKey(autoGenerate = true)
var id :Long = 0
) : Parcelable {
}
2)使用room操作数据时,实体一定要有一个主键,主键用注解@PrimaryKey
3)在线程中使用Toast时,要添加Looper.prepare()和Looper.loop
总结:
在学完Android的基础知识后写了一个小的项目来巩固一下知识点,在写代码的时候也遇到了很多的问题,也有些问题不能解决的是,在用户分组时,粘性标题的设置,如何使下一组第一个子项到达时,通过onDrawOver方法覆盖上一组标题。对单例类和伴随对象的认识不是很清楚,Activity的启动模式不能乱用否则也会出现大问题的,最好是使用一个基类和单例类的方法进行Activity的存储和操作。在finish掉activity后类的存在状态如何?使用room操作和使用sql的基本操作的区别,room的操作实现是如何的?这些问题没有得到解决。尤其是在使用room进行数据的操作时,因为room的操作要在线程中进行,这样可以提供程序的运行效率,但同时也出现了一个问题是多线程时会重新线程不安全问题,数据不同步等等问题,还有就是在构建一个实体时一定要给一个主键,否则会运行不成功的。在写完这个小项目之后也收获很多的,进一步了解了App的实现和对kotlin语法的熟练。Android更新很快要学会阅读官方文档,有很多新功能,同时有些旧功能可能已经弃用了。
下一篇: Vue.js实战第七章笔记(二)
推荐阅读