完整代码Gitee地址:kotlin-demo: 15天Kotlin学习计划
第三天学习内容代码:Chapter3
目录
知识点1:公共标题栏
知识点2:自定义标题栏控件
知识点3:RecyclerView
①标准写法
②使用框架
知识点4:编写精美的聊天对话界面
①标准写法-多布局
②使用框架-多布局
市场上应用的界面顶部有一个标题栏,标题栏上会有一到两个按钮可用于返回或其他操作(iPhone没有专门的返回键)。虽然Android系统已经给每个Activity提供了标题栏功能,但样式有很大局限性,我们自定义一个标题栏,新建item_title布局。
在activity_learn3布局中引用:
运行效果如下:
可以看到,我们在LinearLayout中分别加入了两个TextView和一个LinearLayout包裹ImageView,左边的LinearLayout可用于返回,右边的TextView可用于编辑,中间的TextView则可以显示一段标题文本。
引入布局的技巧确实解决了重复编写布局代码的问题,但是如果布局中有一些控件要求能够响应事件,我们还是需要在每个Activity中为这些控件单独编写一次事件注册的代码。比如标题栏中的返回按钮,其实不管是在哪一个Activity中,这个按钮的功能都是相同的,即销毁当前Activity。而如果在每一个Activity中都需要重新注册一遍返回按钮的点击事件,无疑会增加很多重复代码,这种情况最好是使用自定义控件的方式来解决。
新建TitleLayout继承自LinearLayout,代码如下:
class TitleLayout(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
//init结构体中需要对标题栏布局进行动态加载
init {
val view = LayoutInflater.from(context).inflate(R.layout.item_title, this)
//为标题栏中的按钮注册点击事件
val llyBack: LinearLayout = view.findViewById(R.id.lly_back)
llyBack.setOnClickListener {
val activity = context as Activity
activity.finish()
}
}
}
现在自定义控件已经创建好了,接下来我们需要在布局文件中添加这个自定义控件,修改activity_learn3.xml中的代码,如下所示:
重新运行程序,和使用引入布局方式的效果是一样的。
ListView由于强大的功能,在过去的Android开发当中可以说是贡献卓越,直到今天仍然还有不计其数的程序在使用ListView。不过ListView并不是完美无缺的,比如如果不使用一些技巧来提升它的运行效率,那么ListView的性能就会非常差。还有,ListView的扩展性也不够好,它只能实现数据纵向滚动的效果,如果我们想实现横向滚动的话,ListView是做不到的。
它可以说是一个增强版的ListView,不仅可以轻松实现和ListView同样的效果,还优化了ListView存在的各种不足之处。目前Android官方更加推荐使用RecyclerView:
新建item_rcy_cont作为RecyclerView每项Item局部:
新建实体类UserBean,作为数据存储映射实体:
class UserBean(val herd: Int, val name: String, val phone: String)
修改activity_learn3.xml中的代码,如下所示:
预览效果如下:
接下来需要为RecyclerView准备一个适配器,新建UserAdapter类,让这个适配器继承自RecyclerView.Adapter,并将泛型指定为UserAdapter.ViewHolder。其中,ViewHolder是我们在UserAdapter中定义的一个内部类,代码如下所示:
//让这个适配器继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder。
class UserAdapter(private val userList: List) :
RecyclerView.Adapter() {
//内部类绑定控件
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val nameText: TextView = view.findViewById(R.id.tv_name)
val phoneText: TextView = view.findViewById(R.id.tv_phone)
val ivHerd: ImageView = view.findViewById(R.id.iv_herd)
}
//创建布局
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.item_rcy_cont, parent, false)
return ViewHolder(view)
}
//展示布局数量
override fun getItemCount(): Int {
return userList.size
}
//给控件赋值与样式
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.nameText.text = userList[position].name
holder.phoneText.text = userList[position].phone
holder.ivHerd.setImageResource(userList[position].herd)
}
}
虽然看上去好像多了好几个方法,但其实它比ListView的适配器要更容易理解。适配器准备好了之后,我们就可以开始使用RecyclerView了,修改Learn3Activity中的代码,如下所示:
class Learn3Activity : BaseActivity() {
private val userList = ArrayList()
private lateinit var mAdapter: UserNewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_learn3)
//3、RecyclerView控件
recyclerView()
}
private fun recyclerView() {
//初始化用户数据
initUser()
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
//①标准写法,使用适配器
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = UserAdapter(userList)
}
private fun initUser() {
repeat(2) {
userList.add(UserBean(R.mipmap.image_nv,"迪丽不热", "17321341289"))
userList.add(UserBean(R.mipmap.image_nan,"杀手不冷", "17377621412"))
userList.add(UserBean(R.mipmap.image_nv,"赵思露", "19987878221"))
userList.add(UserBean(R.mipmap.image_nv,"井川里予", "13612344637"))
userList.add(UserBean(R.mipmap.image_nan,"阿斯顿", "13635465678"))
userList.add(UserBean(R.mipmap.image_nan,"没啥用科技", "13801940921"))
userList.add(UserBean(R.mipmap.image_nv,"阿瑟东", "16622348923"))
}
}
}
这里使用了initUser()方法,用于初始化所有的用户数据。接着在onCreate()方法中先创建了一个LinearLayoutManager对象,并将它设置到RecyclerView当中。LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。接下来我们创建了UserAdapter的实例,并将水果数据传入UserAdapter的构造函数中,最后调用RecyclerView的setAdapter()方法来完成适配器设置,这样RecyclerView和数据之间的关联就建立完成了。现在运行一下程序,如下所示。
当然这只是RecyclerView的基本用法而已,还有更多用法,比如实现横向滚动和瀑布流布局,这个也并不复杂,与Java写法类似。
BaseRecyclerViewAdapterHelper是一个比较成熟的框架,代码书写简洁很大提升开发效率,接下来我们用Kotlin写法使用它,在app目录下的build.gradle添加依赖:
/* 灵活的RecyclerView框架 */
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
创建适配器UserNewAdapter:
class UserNewAdapter :
BaseQuickAdapter(R.layout.item_rcy_cont) {
override fun convert(holder: BaseViewHolder, item: UserBean) {
//获取控件ID
val icon = holder.getView(R.id.iv_herd)
//展示图片
icon.setImageResource(item.herd)
//直接设置文本内容
holder.setText(R.id.tv_name, item.name)
holder.setText(R.id.tv_phone, item.phone)
}
}
适配器准备好了之后,我们就可以开始使用RecyclerView了,修改Learn3Activity中的代码,如下所示:
private fun recyclerView() {
//初始化用户数据
initUser()
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
//②使用框架,BaseRecyclerViewAdapterHelper
mAdapter = UserNewAdapter()
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = mAdapter
mAdapter.addData(userList)//添加数据
}
总体来看框架写法更简单,运行效果与标准写法一样。
下面我们来编写一个聊天界面,需要用到.9图片,如何制作这里不做讲解,图片资源可去顶部gitee获取。我们在主界面中放置了一个RecyclerView用于显示聊天的消息内容,定义消息的实体类,新建MsgBean,代码如下所示:
//content表示消息的内容,type表示消息的类型
class MsgBean(val content: String, val itemType: Int) {
companion object {
//定义常量的关键字是const
//表示这是一条收到的消息
const val TYPE_LEFT = 0
//表示这是一条发出的消息
const val TYPE_RIGHT = 1
}
}
MsgBean类中只有两个字段:content表示消息的内容,type表示消息的类型。其中消息类型有两个值可选:TYPE_LEFT 表示这是一条收到的消息,TYPE_RIGHT表示这是一条发出的消息。定义常量的关键字是const,注意只有在单例类、companion object或顶层方法中才可以使用const关键字。
新建子项布局,左边显示item_msg_left.xml,代码如下所示:
预览效果:
新建子项布局,右边显示item_msg_right.xml,代码如下所示:
预览效果:
接下来需要创建RecyclerView的适配器类,新建类MsgAdapter,代码如下所示:
class MsgAdapter(private val msgList: List) :
RecyclerView.Adapter() {
inner class LeftViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val leftMsg: TextView = view.findViewById(R.id.tv_left)
}
inner class RightViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val rightMsg: TextView = view.findViewById(R.id.tv_right)
}
override fun getItemViewType(position: Int): Int {
return msgList[position].itemType
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
RecyclerView.ViewHolder {
return when (viewType) {
MsgBean.TYPE_LEFT ->
LeftViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_msg_left, parent, false)
)
MsgBean.TYPE_RIGHT ->
RightViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_msg_right, parent, false)
)
else -> throw IllegalArgumentException()
}
}
override fun getItemCount(): Int {
return msgList.size
}
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()
}
}
}
上述代码中用到了一个新的知识点:根据不同的viewType创建不同的界面。首先我们定义了LeftViewHolder和RightViewHolder这两个ViewHolder,分别用于缓存msg_left_item.xml和msg_right_item.xml布局中的控件。然后要重写getItemViewType()方法,并在这个方法中返回当前position对应的消息类型。
最后修改Learn3Activity中的代码:
class Learn3Activity : BaseActivity() {
private val msgList = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_learn3)
//4、编写精美的聊天对话界面
val tvTitle: TextView = findViewById(R.id.tv_title)
tvTitle.text = "聊天室"
recyclerNewView()
}
private fun initMsg() {
repeat(1) {
msgList.add(MsgBean("在吗?还记得我吗?",0))
msgList.add(MsgBean("当然,初中坐我前排的班花,那时候我还扯过你的肩带",1))
msgList.add(MsgBean("有啥事吗?",1))
msgList.add(MsgBean("能借我200块钱吗",0))
msgList.add(MsgBean("其实从初中开始我就一直暗恋你,没好意思跟你说,我们在一起吧",1))
msgList.add(MsgBean("别吧我们都多久没联系了,都不了解对方,也不太熟悉",0))
msgList.add(MsgBean("那你还好意思找我借钱?",1))
msgList.add(MsgBean("?",0))
}
}
private fun recyclerNewView() {
initMsg()
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
//①标准写法,使用适配器
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = MsgAdapter(msgList)
}
}
我们先在initMsg()方法中初始化了几条数据用于在RecyclerView中显示,接下来按照标准的方式构建RecyclerView,给它指定一个LayoutManager和一个适配器。运行程序之后,你将会看到非常美观的聊天界面:
布局不变,MsgBean实体类继承MultiItemEntity ,类型必须命名为itemType,并设置关键字override,修改如下:
class MsgBean(val content: String,override val itemType: Int) : MultiItemEntity {
companion object {
//定义常量的关键字是const
//表示这是一条收到的消息
const val TYPE_LEFT = 0
//表示这是一条发出的消息
const val TYPE_RIGHT = 1
}
}
创建多布局的适配器,新建类MsgNewAdapter,继承BaseMultiItemQuickAdapter,代码如下所示:
class MsgNewAdapter(data: MutableList?) :
BaseMultiItemQuickAdapter(data) {
init {
//必须绑定type和layout的关系
addItemType(MsgBean.TYPE_LEFT, R.layout.item_msg_left)
addItemType(MsgBean.TYPE_RIGHT, R.layout.item_msg_right)
}
override fun convert(holder: BaseViewHolder, item: MsgBean) {
holder ?: return
item ?: return
when (holder.itemViewType) {
MsgBean.TYPE_LEFT -> holder.setText(R.id.tv_left, item.content)
MsgBean.TYPE_RIGHT -> holder.setText(R.id.tv_right, item.content)
else -> throw IllegalArgumentException()
}
}
}
最后修改Learn3Activity中的代码:
private fun recyclerNewView() {
initMsg()
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
//②使用框架,BaseRecyclerViewAdapterHelper
msgAdapter = MsgNewAdapter(msgList)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = msgAdapter
}
运行结果与标准写法一致。