说明: 本文是郭霖《第一行代码-第3版》的读书笔记
4.1 如何编写程序界面
编写XML,这是传统的方法
ConstraintLayout,Google推出的新方法,可以在可视化编辑器中拖动控件操作
这里使用的是编写XML方法
4.2 常用控件 4.2.1 TextView修改activity_main.xml的代码如下:
android:id是控件唯一标识符
android:layout_width和android:layout_height是所有控件都具有的两个属性,可选值3个:
match_parent,当前控件大小和父布局一样wrap_parent,当前控件大小刚好能包住里面的内容固定值,指定固定尺寸,单位是dp,像素独立单位,可以保证在不同分辨率的设备上显示效果尽可能相同
android:gravity,指定文字的对齐方式,TextView文字默认是左上角对齐的,所以可能看不出match_parent,这里指定了水平居中和垂直居中。
android:textColor, 指定文字的颜色
android:textSize, 指定文字的大小,单位是sp,这样当用户修改系统字体尺寸时,应用程序中的文字大小也会跟着修改。
4.2.2 Button在activity_main中加入一个Button控件:
...
android:textAllCaps指定为false可以保留指定的原始文字内容。因为Android默认将Button上的英文字母全部转成大写。
在MainActivity中为Button的点击事件注册一个监听器:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 注意不通过findViewById来找到button而直接写,需要引入'android-kotlin-extensions'插件
button.setOnClickListener {
//添加逻辑
}
}
}
上述方法是使用函数式API的方式来中注册监听器,也可以使用实现接口的方式来注册:
class MainActivity : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 用这种方式的时候不要忘了设置ClickListener
button.setOnClickListener(this)
}
override fun onClick(p0: View?) {
when (p0?.id) {
R.id.button -> {
//TODO
}
}
}
}
4.2.3 EditText
允许输入和编辑内容的控件,并可以在程序中对这些内容进行处理。
...
android:hint是EditText无输入时的提示文本。
android:maxlines: 随着输入的增多,文本框被不断拉长,这是因为我们指定高度是wrap_content,指定max_lines能固定输入文本框的大小,超过两行时,文本就会向上滚动而不会拉伸EditText。
点击按钮获取EditText中的内容
override fun onClick(p0: View?) {
when (p0?.id) {
R.id.button -> {
// 获取EditText中的内容
val inputText = editText.text.toString()
Toast.makeText(this, inputText, Toast.LENGTH_SHORT).show()
}
}
}
4.2.4 ImageView
imageView通常是放在以drawable开头的目录下的,并且要带上具体的分辨率,主流的手机屏幕分辨率大多数是xxhdpi的,因此可以在res目录下新建一个drawable-xxhdpi目录。
修改activity_main.xml:
...
android:src指定图片源
还可以在代码中动态更改图片:
override fun onClick(p0: View?) {
when (p0?.id) {
R.id.button -> {
// 按下按钮更改图片源
imageView.setImageResource(R.drawable.img_2)
}
}
}
4.2.5 ProgressBar
顾名思义,ProcessBar即进度条,用来显示进度
修改activity_main.xml代码如下:
...
android:visibility设置控件的可见性,是所有控件都有的属性。可选值有
visible, 控件可见,默认属性
invisiable, 控件不可见,但仍占据原来的屏幕空间
gone , 控件不可见,而且不占用屏幕空间
也可以通过代码改变这个属性的值,使用的是setVisiability()方法,可选值有View.VISIBLE、View.INVISIABLE、GONE
设置进度条样式为水平,此时静止在0,默认的是动态的环形不断旋转的进度条,当设置成水平进度条时,就可以设置一个最大值android:max,然后在代码内更改进度:
override fun onClick(p0: View?) {
when (p0?.id) {
R.id.button -> {
// 点击一下Button进度加一截
progressBar.progress += 10
}
}
}
4.2.6 alertDialog
alertDialog,顾名思义是警告对话框,可以弹出一个对话框,置于所有Activity之上,屏蔽掉其他Activity的交互,用于提醒一些非常重要的内容或者警告信息。
override fun onClick(p0: View?) {
when (p0?.id) {
R.id.button -> {
// alertDialog
// apply函数
alertDialog.Builder(this).apply {
setTitle("This is a alertDialog")
setMessage("Something important")
setCancelable(true)
setPositiveButton("OK") { dialog, which ->
Log.d("alertDialog", "Choose OK")
}
setNegativeButton("Cancel") { dialog, which ->
Log.d("alertDialog", "Choose Cancel")
}
show()
}
}
}
}
代码中setPositiveButton("OK") {dialog, which ->},dialog,which这部分是高阶函数,也可以写成:
builder.setPositiveButton(android.R.string.yes) { _,_ ->
Toast.makeText(applicationContext,
android.R.string.yes, Toast.LENGTH_SHORT).show()
}
以下是Journal Dev中的讲解:https://www.journaldev.com/309/android-alert-dialog-using-kotlin
The function type is (DialogInterface, Int) -> Unit. DialogInterface is an instance of the Dialog and Int is the id of the button that is clicked.
In the above code, we’ve represented this function as a Higher Order Kotlin function. The dialog and which represents the two arguments.
4.3 详解三种布局布局是一种可以放置很多控件的容器,除了放控件,还可以放布局,即布局的嵌套。
4.3.1 LinerLayoutLinearLayout是线性布局,可以通过android:orientation指定线性排列的方向。默认是android:orientation=horizontal.
注意: 如果LinearLayout的排列方向是horizontal,那么内部的控件就绝对不能将宽度指定为match_parent。同理,如果排列方向是vertical, 那么不能将高度指定为match_partent。
android:layout_gravity: 用于指定控件在布局中的对齐方式,而android:gravity用于指定文字在控件中的对齐方式。需要注意:当LinearLayout的排列是horizontal时,只有垂直方向的对齐方式才会生效。
还有一个重要的属性是layout_weight,允许使用比例的方式指定控件的大小。
因为我们指定了layout_weight,所以控件的宽度不再由layout_width指定,所以这里可以给0dp。android:layout_weight相当于是权重。也可以让它更好看些,比如Button的大小给他wrap_content.
4.3.2 RelativeLayoutRelativeLayout是相对布局,通过相对定位的方式让控件出现在布局的任何位置。RelativeLayout的属性非常多。
感觉这个注意下写法就好了。但上面的例子都是相对于父布局进行定位的,当然也可以相对于控件定位。用相对控件定位的时候需要先声明相对的这个控件。
就感觉这种RelativeLayout指定的是相对的大概的位置,没有那么精确。除此之外,还有android:layout_alignLeft这类的属性,表示左边缘和另一个控件的左边缘对齐,这种相对的一般都要指定两个才知道在哪吧。
4.3.3 frameLayoutframeLayout是帧布局,这种布局很简单,所有控件都会默认摆在布局的左上角。
除了默认效果之外,也可以使用layout_gravity来指定控件在布局中的对齐方式。
目前frameLayout应用场景偏少,但后面在Fragment时还可以用到它。
4.4 创建自定义控件首先介绍控件和布局的继承结构:
所有控件都直接或间接继承自View,所有布局都继承自ViewGroupView是Android中基本的UI组件,可以在屏幕上绘制一块矩形,并能响应这块区域的各种事件ViewGroup是一种特殊的View,可以包含许多子View和子ViewGroup,是一个放置布局和控件的容器 4.4.1 引入布局
如果我们想让所有的Activity都有相同的标题栏,那么是否可以不重复写代码?当然是可以的,可以通过引入布局的方式来解决这个问题:在layout目录下新建一个title.xml布局:
这里调用getSupportActionBar()方法获得ActionBar的实例,再调用hide()方法将标题栏隐藏起来。有关ActionBar的知识会在12章中讲到。
使用这种方式,不管有多少Activity要添加标题栏,只需要一行include语句就行了。
4.4.2 创建自定义控件引入布局的技巧使得我们不需要重复编写布局,但如果有一些控件要能响应事件,那还是需要在每个Activity中写一次事件注册的代码。这个时候最好使用自定义控件的方法来解决:
class TitleLayout(context: Context, attrs: AttributeSet): LinearLayout(context, attrs) { init { LayoutInflater.from(context).inflate(R.layout.title, this) val titleBack: Button = findViewById(R.id.titleBack) val titleEdit: Button = findViewById(R.id.titleEdit) titleBack.setOnClickListener { val activity = context as Activity activity.finish() } titleEdit.setOnClickListener { Toast.makeText(context, "You clicked Edit.", Toast.LENGTH_SHORT).show() } } }首先需要声明context和attrs这两个参数,然后在init结构体内声明加载的逻辑:先通过LayoutInflater的from()方法构建出一个LayoutInflater对象,调用inflater()方法动态加载一个布局文件,第一个参数传入布局文件,第二个参数是给加载的布局添加一个父布局。
这里我们给返回和编辑按钮注册了点击事件。注意:TitleLayout接收的context参数实际上是一个Activity的实例,我们需要先将它转成Activity类型,再调用finish()方法销毁当前的Activity(不太理解这句话)。Kotlin中强制类型转换的关键字是as。
然后就可以在activity_main.xml文件中引入这个布局:
4.5 最常用最难用的控件:ListView手机屏幕大小有限,当有大量数据要展示的时候可以借助ListView,ListView允许用户通过滑动的方式将屏幕外的数据滚动到屏幕内,屏幕上原有的数据滚动出屏幕。比如QQ聊天记录。
4.5.1 ListView的简单用法首先修改android_main.xml代码添加ListView:
然后修改Main_Activity.kt中的代码如下:
class MainActivity : AppCompatActivity() { private val data = listOf("Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango", "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango"); override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val adpter = ArrayAdapter(this, android.R.layout.simple_list_item_1, data) listView.adapter = adpter } } 首先我们有水果名字的集合需要显示,但是集合中的数据无法直接传给ListView,需要借助适配器来实现。Android中提供了很多适配器的实现类,这里使用的是ArrayAdapter.可以通过泛型指定要适配的数据类型。ArrayAdapter构造函数需要传入的参数是:Activity的实例、ListView子项布局的id,以及数据源。这里子项布局使用的是Android内置的布局文件android.R.layout_simple_list_item_1,里面只有一个TextView,可用于简单显示一段文本。最后调用ListView的setAdapter()方法,将构建好的适配器传进去。
4.5.2 定制ListView的界面上面写的ListView太过简单。我们想让每一个子项显示图片和文字。
新建一个Fruit类
class Fruit (val name: String, val imageId: Int) { }然后添加一个子项布局,左边是Img,右侧是文字说明
接下来需要创建一个自定义的适配器,
//自定义的适配器继承自ArrayAdapter,重写了getView方法 class FruitAdapter(activity: Activity, val resourceId: Int, data: List) : ArrayAdapter (activity, resourceId, data) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val view = LayoutInflater.from(context).inflate(resourceId, parent, false) val fruit = getItem(position) //获取当前项的Fruit实例 if (fruit != null) { view.fruitName.text = fruit.name view.fruitImage.setImageResource(fruit.imageId) } return view } } getView()在每个子项被滚动到屏幕的时候会被调用,首先使用LayoutInflater加载我们传入的布局,这里第三个参数指定为False表示我们只让在父布局中声明的Layout属性生效,但不会为这个View添加父布局。因为一旦View有了父布局后,就无法添加到ListView中了。(不太理解)再通过getItem()方法得到当前项的Fruit实例,为其赋予ImageId和name,最后将布局返回。
在MainActivity中:
class MainActivity : AppCompatActivity() { private val fruitList = ArrayList() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 初始化水果的数据 initFruits() val adapter = FruitAdapter(this, R.layout.fruit_item, fruitList) listView.adapter = adapter } private fun initFruits() { repeat(2) { fruitList.add(Fruit("Apple", R.drawable.apple_pic)) fruitList.add(Fruit("Banana", R.drawable.banana_pic)) fruitList.add(Fruit("Orange", R.drawable.orange_pic)) fruitList.add(Fruit("Watermelon", R.drawable.watermelon_pic)) fruitList.add(Fruit("Pear", R.drawable.pear_pic)) fruitList.add(Fruit("Grape", R.drawable.grape_pic)) fruitList.add(Fruit("Pineapple", R.drawable.pineapple_pic)) fruitList.add(Fruit("Strawberry", R.drawable.strawberry_pic)) fruitList.add(Fruit("Cherry", R.drawable.cherry_pic)) fruitList.add(Fruit("Mango", R.drawable.mango_pic)) } } } 初始化FruitList,然后构建我们的FruitAdapter适配器,传入ListView当中就可以了。
repeat()函数是Kotlin中非常常用的标准函数,允许传入一个数值n,然后将Lambda表达式中的内容执行n遍。
4.5.3 提升ListView的运行效率ListView难用是因为有很多细节可以优化,比如运行效率。上面的FruitAdapter的getView()方法中,对每一个子项,都要调用getView()每次都要将布局加载一遍,但在getView()方法中有一个convertView参数,可以将之前加载好的布局缓存:
//修改FruitAdapter中的getView方法 override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val view: View // 如果convertView为空则加载布局,否则使用缓存下来的布局 if (convertView == null) { view = LayoutInflater.from(context).inflate(resourceId, parent, false) } else { view = convertView val fruit = getItem(position) //获取当前项的Fruit实例 if (fruit != null) { view.fruitName.text = fruit.name view.fruitImage.setImageResource(fruit.imageId) } return view }同时我们也不需要每次创建itemView的时候都去重复绑定控件,不能都通过view.findViewById()来查找,可以缓存到一个自定义的类ViewHolder中。
class FruitAdapter(activity: Activity, val resourceId: Int, data: List) : ArrayAdapter (activity, resourceId, data) { inner class ViewHolder(val fruitImage: ImageView, val fruitName: TextView) override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val view: View val viewHolder: ViewHolder if (convertView == null) { view = LayoutInflater.from(context).inflate(resourceId, parent, false) val fruitName: TextView = view.findViewById(R.id.fruitName) val fruitImage: ImageView = view.findViewById(R.id.fruitImage) viewHolder = ViewHolder(fruitImage, fruitName) view.tag = viewHolder } else { view = convertView viewHolder = view.tag as ViewHolder } val fruit = getItem(position) //获取当前项的Fruit实例 if (fruit != null) { viewHolder.fruitName.text = fruit.name viewHolder.fruitImage.setImageResource(fruit.imageId) } return view } } ViewHolder类中用tag对ImageView和TextView进行缓存,然后在需要的时候再取出来。Kotlin中使用inner class来声明内部的类。
4.5.4 ListView的点击事件override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 初始化水果的数据 initFruits() val adapter = FruitAdapter(this, R.layout.fruit_item, fruitList) listView.adapter = adapter //为ListView注册监听器 listView.setOnItemClickListener { parent, view, position, id -> val fruit = fruitList[position] Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show() }没有用到的参数可以用占位符_代替
4.6 更强大的滚动控件:RecycleView如果不使用一些技巧优化ListView,那么其效率会很差,扩展性也不够好,只能实现纵向滚动的效果。RecycleView不仅能实现和ListView同样的效果,而且也优化了其不足之处。目前Android官方更推荐使用RecycleView。
4.6.1 RecycleView的基本用法RecycleView属于新增控件,需要在项目的build.gradle中添加RecycleView的依赖。
打开app/build.gradle文件,在dependencies上添加如下内容:
dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'com.google.android.material:material:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation "androidx.recyclerview:recyclerview:1.2.1" // For control over item selection of both touch and mouse driven selection // 现在的RecycleView好像要添加这一项 implementation "androidx.recyclerview:recyclerview-selection:1.1.0" testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' }然后修改布局:
接下来需要为RecycleView准备一个适配器,继承自RecycleView.Adapter,并指定泛型为自己内部定义的ViewHolder:
class FruitAdapter(val fruitList: List) : RecyclerView.Adapter () { inner class ViewHolder(view: View): RecyclerView.ViewHolder(view) { val fruitImage: ImageView = view.findViewById(R.id.fruitImage) val fruitName: TextView = view.findViewById(R.id.fruitName) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val fruit = fruitList[position] holder.fruitImage.setImageResource(fruit.imageId) holder.fruitName.text = fruit.name } override fun getItemCount(): Int { return fruitList.size } } 上述是RecycleView适配器的标准写法。由于继承自RecycleView.Adapter,因此需要重写onCreateViewHolder()、onBindViewHolder()、getItemCount()这三个方法。
onCreateViewHolder(): 用于创建ViewHolder的实例,在这个方法中传入fruit_item的布局
onBindViewHolder(): 用于对子项数据赋值
getItemCount(): 告诉RecycleView共有多少项,直接传入List的长度
修改MainActivity的代码如下:
class MainActivity : AppCompatActivity() { private val fruitList = ArrayList() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initFruits() val layoutManager = LinearLayoutManager(this) recycleView.layoutManager = layoutManager val adapter = FruitAdapter(fruitList) recycleView.adapter = adapter } private fun initFruits() { repeat(2) { fruitList.add(Fruit("Apple", R.drawable.apple_pic)) fruitList.add(Fruit("Banana", R.drawable.banana_pic)) fruitList.add(Fruit("Orange", R.drawable.orange_pic)) fruitList.add(Fruit("Watermelon", R.drawable.watermelon_pic)) fruitList.add(Fruit("Pear", R.drawable.pear_pic)) fruitList.add(Fruit("Grape", R.drawable.grape_pic)) fruitList.add(Fruit("Pineapple", R.drawable.pineapple_pic)) fruitList.add(Fruit("Strawberry", R.drawable.strawberry_pic)) fruitList.add(Fruit("Cherry", R.drawable.cherry_pic)) fruitList.add(Fruit("Mango", R.drawable.mango_pic)) } } } 首先需要初始化所有的水果数据,
再创建一个LinearLayoutManager对象,并将它设置到RecycleView当中。LinearLayoutManager对象指定了RecycleView的布局方式。
接下来创建一个适配器实例,赋给RecycleView。这样RecycleView和数据之间就关联起来了。
4.6.2 实现横向滚动和瀑布流布局横向滚动
首先需要改一下每一个子项的布局,从水平变为垂直,然后需要在MainActivity中改layoutManager为水平就可以了。
瀑布流布局
class MainActivity : AppCompatActivity() { private val fruitList = ArrayList() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initFruits() // 第一个参数指定3列,第二个参数指定布局的排列方向 val layoutManager = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL) recycleView.layoutManager = layoutManager // 网格布局 // val layoutManager = GridLayoutManager(this, 3) // recycleView.layoutManager = layoutManager val adapter = FruitAdapter(fruitList) recycleView.adapter = adapter } } 感觉布局还是得自己去调,怎么好看怎么来,这里主要掌握API的用法。
4.6.3 RecycleView的点击事件ListView提供了一个叫setonItemClickListener()的方法,但是RecycleView并没有类似的方法,需要我们自己给子项具体的View去注册点击事件。(这其实是一件好事,尽管麻烦一些)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item, parent, false) val viewHolder = ViewHolder(view) // 注册ViewHolder的点击事件 viewHolder.itemView.setOnClickListener { val position = viewHolder.adapterPosition val fruit = fruitList[position] Toast.makeText(parent.context, "You clicked Item ${fruit.name}", Toast.LENGTH_SHORT).show() } viewHolder.fruitImage.setOnClickListener { val position = viewHolder.absoluteAdapterPosition val fruit = fruitList[position] Toast.makeText(parent.context, "You clicked Image ${fruit.name}", Toast.LENGTH_SHORT).show() } return viewHolder }可以看到,我们在onCreateViewHolder()函数内注册点击事件。这里的itemView指的是子项的最外层布局。然后我们又找到子项的imageView,为其注册点击事件。
4.7 编写界面的最佳实践 4.7.1 制作9-Patch图片这个九宫格的图片之前也碰到过,指的是一张图片哪里能被拉伸。
在AndroidStudio中能直接绘制9-Patch图片,左键点击黑色的就是能被拉伸的区域,按住Shift拖动可以擦除。
4.7.2 编写聊天界面首先还是在build.gradle里添加recycleView的依赖,然后修改activity_main.xml:
添加了一个RecycleView,一个Button,一个EditText。
再创建一个Msg消息类:
class Msg(val content: String, val type: Int) { companion object { const val TYPE_RECEIVED = 0 const val TYPE_SEND = 1 } }Kotlin中定义常量的关键字是const,但是只有在单例类、companion object或顶层方法中才可以使用const关键字。
然后还需要编写RecycleView的子项布局:
关于padding和margin的区别:
为了使用RecycleView,需要定义适配器,适配器的写法和前面差不多,不过这里会判断是接收到的消息还是发送的消息,从而inflate不同的布局。
class MsgAdapter(val msgList: ArrayList):RecyclerView.Adapter () { inner class LeftViewHolder(view: View) : RecyclerView.ViewHolder(view){ val leftMsg: TextView = view.findViewById(R.id.leftMsg) } inner class RightViewHold(view: View) : RecyclerView.ViewHolder(view) { val rightMsg: TextView = view.findViewById(R.id.rightMsg) } override fun getItemViewType(position: Int): Int { val msg = msgList[position] return msg.type } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { if (viewType == Msg.TYPE_RECEIVED) { val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item, parent, false) return LeftViewHolder(view) } else { val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item, parent, false) return RightViewHold(view) } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val msg = msgList[position] when (holder) { is LeftViewHolder -> holder.leftMsg.text = msg.content is RightViewHold -> holder.rightMsg.text = msg.content else -> throw IllegalArgumentException() } } override fun getItemCount(): Int { return msgList.size } } 最后修改MainActivity中的代码:
class MainActivity : AppCompatActivity() { private val msgList = ArrayList() private var adapter: MsgAdapter? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //初始化消息列表 initMsg() //加载布局管理器 val layoutManager = LinearLayoutManager(this) recycleView.layoutManager = layoutManager //适配器 adapter = MsgAdapter(msgList) recycleView.adapter = adapter //为Button添加响应 send.setOnClickListener { val content = inputText.text.toString() if (content != null) { val msg = Msg(content, Msg.TYPE_SEND) msgList.add(msg) //刷新RecycleView中的显示 adapter?.notifyItemInserted(msgList.size - 1) //定位到最后一行 recycleView.scrollToPosition(msgList.size - 1) //清空输入框 inputText.setText("") } } } private fun initMsg() { val msg1 = Msg("Hello smallSheep.", Msg.TYPE_RECEIVED) val msg2 = Msg("Hello, Who is that?", Msg.TYPE_SEND) val msg3 = Msg("This is BigZ, Nice to meet u.", Msg.TYPE_RECEIVED) msgList.add(msg1) msgList.add(msg2) msgList.add(msg3) } } 这里有几个方法值得注意:
adapter.notifyItemInserted(): 用于通知列表有新数据插入,这样新增的消息才会在RecycleView中显示出来,或者也可以调用notifyDataSetChanged()方法,这样增删改数据都可以显示出来,但效率稍低。
recycleView.scrollToPosition: 将显示的数据定位到最后一行,保证新增的数据看得到。



