欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Kotlin实现的小项目总结

程序员文章站 2024-03-14 23:13:53
...

小项目功能:

实现一个使用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更新很快要学会阅读官方文档,有很多新功能,同时有些旧功能可能已经弃用了。