书籍资源进入官网下载 ,PC端进入
第四章- 软件也要拼脸蛋,UI开发的点点滴滴 常见控件写法 常见公共属性 1 2 3 4 5 6 7 8 9 10 11 12 控件ID android:id="@+id/text1" 控件在整个布局中的宽度:match_parent(根据父元素),wrap_content(根据内容),xxdp(类型html的px是一种单位) android:layout_width="match_parent" 控件在整个布局中的高度:可选项和宽度一样 android:layout_height="wrap_content" 控件本身的垂直对齐方式:可多个值|分隔 android:gravity="center" 控件在整个布局中的垂直对齐方式.若使用线性布局固定了垂直或者水平,那么则只能选择相反方向的值,不然无法生效 android:layout_gravity="center" 控件的可见属性,可选值:visible:显示控件(默认值),invisible:不可见但任然占用屏幕空间,gone:不可见且不占用屏幕空间 android:visibility="gone"
常用控件-文本(TextView) 在安卓中显示文本使用的控件是TextView.若要使用它,在activity的布局文件中添加<TextView/>标签即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <TextView android:id ="@+id/text1" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:gravity ="center" android:layout_gravity ="center" android:text ="你好" android:textColor ="#cc66ff" android:textSize ="30sp" />
在安卓中显示文本使用的控件是Button.若要使用它,在activity的布局文件中添加<Button/>标签即可
1 2 3 4 5 6 7 8 9 10 11 12 <Button android:id ="@+id/button1" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:textAllCaps ="false" android:text ="按钮" />
使用函数式API注册监听事件
1 2 3 4 5 6 7 8 9 10 11 12 class MainActivity : AppCompatActivity () { override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) var buttn1 = findViewById<Button>(R.id.button1) buttn1.setOnClickListener{ Toast.makeText(this ,"你点击了按钮" ,Toast.LENGTH_LONG).show() } } }
使用接口实现监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class MainActivity : AppCompatActivity (), View.OnClickListener{ override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) var buttn1 = findViewById<Button>(R.id.button1) buttn1.setOnClickListener(this ) } override fun onClick (p0: View ?) { when (p0?.id){ R.id.button1->{ Toast.makeText(this ,"你点击了按钮" ,Toast.LENGTH_LONG).show() } } } }
常见控件-可编辑文本框(EditText) EditText 它允许用户在控件里输入和编辑内容,并可以在程序中对这些内容进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 <EditText android:id ="@+id/editText" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:hint ="这是输入框提示文字" android:maxLines ="2" />
通过点击按钮来获取EditText文本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class MainActivity : AppCompatActivity (), View.OnClickListener{ override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) var buttn1 = findViewById<Button>(R.id.button1) buttn1.setOnClickListener(this ) } override fun onClick (p0: View ?) { when (p0?.id){ R.id.button1->{ val editText = findViewById<EditText>(R.id.editText) val inText = editText.text.toString() Toast.makeText(this ,inText,Toast.LENGTH_LONG).show() } } } }
常见控件-图片(ImageView) ImageView 是用于在界面上展示图片的一个控件,它可以让我们的程序界面变得更加丰富多彩.图片通常是放在以drawable 开头的目录下的,并且要带上具体的分辨率。现在最主流的手机屏幕分辨率大多是xxhdpi 的,所以我们在res 目录下再新建一个drawable-xxhdpi 目录,然后将事先准备好的两张图片img_1.png 和img_2.png (在随书资源的源码\第4章\UIWidgetTest\app\src\main\res\drawable-xxhdpi目录下) 制到该目录当中。
1 2 3 4 5 6 7 8 9 <ImageView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:src ="@drawable/img_1" />
使用代码更改src
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class MainActivity : AppCompatActivity (), View.OnClickListener{ override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) var buttn1 = findViewById<Button>(R.id.button1) buttn1.setOnClickListener(this ) } override fun onClick (p0: View ?) { when (p0?.id){ R.id.button1->{ val imgView = findViewById<ImageView>(R.id.image) imgView.setImageResource(R.drawable.img_2) } } } }
常见控件-进度条(ProgressBar) Progr essBar 用于在界面上显示一个进度条,表示我们的程序正在加载一些数据。
1 2 3 4 5 <ProgressBar android:id ="@+id/progress_1" android:layout_width ="match_parent" android:layout_height ="wrap_content" />
使用代码控制进度条的可见性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class MainActivity : AppCompatActivity (), View.OnClickListener{ override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) var buttn1 = findViewById<Button>(R.id.button1) buttn1.setOnClickListener(this ) } override fun onClick (p0: View ?) { when (p0?.id){ R.id.button1->{ val progressBar = findViewById<ProgressBar>(R.id.progress_1) when (progressBar.visibility){ View.VISIBLE-> progressBar.visibility = View.GONE else -> progressBar.visibility = View.VISIBLE } } } } }
此时,这个并不是进度条而是循环圆圈,我们可以给它加上style来变成进度条
1 2 3 4 5 6 7 8 9 10 11 12 13 <ProgressBar android:id ="@+id/progress_1" android:layout_width ="match_parent" android:layout_height ="wrap_content" style ="?android:attr/progressBarStyleHorizontal" android:max ="100" />
使用代码来更改进度,每次点击按钮加10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class MainActivity : AppCompatActivity (), View.OnClickListener{ override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) var buttn1 = findViewById<Button>(R.id.button1) buttn1.setOnClickListener(this ) } override fun onClick (p0: View ?) { when (p0?.id){ R.id.button1->{ val progressBar = findViewById<ProgressBar>(R.id.progress_1) progressBar.progress = progressBar.progress + 10 } } } }
常见控件-消息弹窗(AlertDialog) AlertDialog 可以在当前界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,能够屏蔽其他控件的交互能力,因此AlertDialog 一般用于提示一些非常重要的内容或者警告信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class MainActivity : AppCompatActivity (), View.OnClickListener{ override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) var buttn1 = findViewById<Button>(R.id.button1) buttn1.setOnClickListener(this ) } override fun onClick (p0: View ?) { when (p0?.id){ R.id.button1->{ AlertDialog.Builder(this ).apply { setTitle("这是一个弹窗" ) setMessage("这是弹窗内容" ) setCancelable(false ) setPositiveButton("确认" ,{dialog,which->}) setNegativeButton("关闭" ,{dialog,which->}) }.show() } } } }
其它控件 到此为止第一行代码三中的全部控件已经讲解完毕,其他的控件前往安卓官网指南 ->界面->外观和风格进行了解
基本布局 控件和布局的关系
线性布局-LinearLayout 这个布局会将它所包含的控件在线性方向(垂直或水平)上依次排列 基本结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" xmlns:android ="http://schemas.android.com/apk/res/android" > </LinearLayout >
宽度平分-layout_weight 给在同一水平方向宽度为0dp的控件加上此属性,会将在这一方向添加此属性的控件layout_weight的值总和N,除以每个控件layout_weight的值M,得到单个宽度.若同一方向有设置固定宽度或者wrap_content的控件,则在计算时只会占用剩余的空间 例如下面的layout_weight总和为5,按钮1和按钮3分别占2/5,按钮2占1/5.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" xmlns:android="http://schemas.android.com/apk/res/android" > <!--关键代码开始--> <Button android:layout_width="0dp" android:layout_weight="2" android:layout_height="wrap_content" android:text="按钮1" /> <Button android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="按钮2" /> <Button android:layout_width="0dp" android:layout_weight="2" android:layout_height="wrap_content" android:text="按钮3" /> <!--关键代码结束--> </LinearLayout>
相对布局-RelativeLayout 它可以通过相对定位的方式让控件出现在布局的任何位置。也正因为如此,RelativeLayout 中的属性非常多,不过这些属性都是有规律可循的,其实并不难理解和记忆。
根据父标签对齐 下面是一个简单的根据父标签上下左右居中对齐,属性见名知其意就不再赘述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?xml version="1.0" encoding="utf-8" ?> <RelativeLayout android:layout_width ="match_parent" android:layout_height ="match_parent" xmlns:android ="http://schemas.android.com/apk/res/android" > <Button android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="左上角" android:layout_alignParentLeft ="true" android:layout_alignParentTop ="true" /> <Button android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="右上角" android:layout_alignParentRight ="true" android:layout_alignParentTop ="true" /> <Button android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="居中" android:layout_centerInParent ="true" /> <Button android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="左下角" android:layout_alignParentLeft ="true" android:layout_alignParentBottom ="true" /> <Button android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="右下角" android:layout_alignParentRight ="true" android:layout_alignParentBottom ="true" /> </RelativeLayout >
根据同级标签对齐 下面是一个简单的根据同级标签上下左右居中对齐,属性见名知其意就不再赘述
注意: 被引用的标签一定要在最前面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?xml version="1.0" encoding="utf-8" ?> <RelativeLayout android:layout_width ="match_parent" android:layout_height ="match_parent" xmlns:android ="http://schemas.android.com/apk/res/android" > <Button android:id ="@+id/root" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_centerInParent ="true" android:text ="Root" /> <Button android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_above ="@+id/root" android:layout_toLeftOf ="@+id/root" android:text ="左上角" /> <Button android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_above ="@+id/root" android:layout_toRightOf ="@+id/root" android:text ="右上角" /> <Button android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_below ="@+id/root" android:layout_toLeftOf ="@+id/root" android:text ="左下角" /> <Button android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_below ="@+id/root" android:layout_toRightOf ="@+id/root" android:text ="右上角" /> </RelativeLayout >
约束布局-ConstraintLayout 安卓官方文档
由于ConstraintLayout 的特殊性,很难展示如何通过xml进行操作,所以使用可视化编辑器来对界面进行动态操作
要使用constraintLayout布局,先将根标签修改为如下:
1 2 3 4 5 6 7 <?xml version="1.0" encoding="utf-8" ?> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width ="match_parent" android:layout_height ="match_parent" xmlns:android ="http://schemas.android.com/apk/res/android" > </androidx.constraintlayout.widget.ConstraintLayout >
为了更加简单的开发,将不再使用Android Studio的Code模式,将改为Design模式
由于无法进行文字描述,请前往哔哩哔哩学习
自定义控件 控件和布局的继承结构
所有布局都是直接或间接继承自ViewGroup的.控件其实就是在View 的基础上又添加了各自特有的功能.而ViewGroup则是一种特殊的View ,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器。
引入布局 当一个控件在不同的地方被重复调用,当系统自带的控件并不能满足我们的需求时,可以利用上面的继承结构来创建自定义控件以实现控件的复用,就类似于Vue的Component.下面我们就来学习一下创建自定义控件的两种简单方法。先将准备工作做好,创建一个UICustomViews 项目,实现自定义标题栏控件.
在res目录下创建图片文件夹drawable-xxhdpi 目录,将配套资源中的源码\第4章\UICustomViews\app\src\main\res\drawable-xxhdpi\复制
在layout目录下新建一个title.xml布局 代码如下:title.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:background ="@drawable/title_bg" > <Button android:id ="@+id/titleBack" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_gravity ="center" android:layout_margin ="5dp" android:background ="@drawable/back_bg" android:text ="返回" android:textColor ="#fff" /> <TextView android:id ="@+id/textText" android:layout_width ="0dp" android:layout_height ="wrap_content" android:layout_weight ="1" android:layout_gravity ="center" android:gravity ="center" android:text ="标题" android:textColor ="#fff" android:textSize ="24sp" /> <Button android:id ="@+id/titleEdit" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_gravity ="center" android:layout_margin ="5dp" android:background ="@drawable/edit_bg" android:text ="编辑" android:textColor ="#fff" /> </LinearLayout >
引用title.xml 若要引用改文件,则在对应xml文件中键入一下代码
1 <include layout ="@layout/title" />
这里以activity_main.xml为例子
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" > <include layout ="@layout/title" /> </LinearLayout >
覆盖原有标题栏 某一些设备可能存在自带的标题栏在对应activity.kt文件中使用如下代码隐藏掉.
1 supportActionBar?.hide()
以MainActivity为例
1 2 3 4 5 6 7 class MainActivity : AppCompatActivity () { override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) supportActionBar?.hide() } }
更改主题 如果不更改主题可能会出现看不清等意外情况,打开res/values/themes.xml文件.更改parent为Theme.AppCompat.Light.DarkActionBar.如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <resources xmlns:tools ="http://schemas.android.com/tools" > <style name ="Theme.UIConstomViews" parent ="Theme.AppCompat.Light.DarkActionBar" > <item name ="colorPrimary" > @color/purple_500</item > <item name ="colorPrimaryVariant" > @color/purple_700</item > <item name ="colorOnPrimary" > @color/white</item > <item name ="colorSecondary" > @color/teal_200</item > <item name ="colorSecondaryVariant" > @color/teal_700</item > <item name ="colorOnSecondary" > @color/black</item > <item name ="android:statusBarColor" > ?attr/colorPrimaryVariant</item > </style > </resources >
最终效果
自定义控件 引入布局的技巧确实解决了重复编写布局代码的问题,但无法响应事件. 我们还是需要在每个Activity中为这些控件单独编写一次事件注册的代码,不管是在哪一个Activity中,这个控件的功能都是相同的,这就是称为自定义控件.
新建TitleLayout.kt文件让它继承LinearLayout
1 2 class TitleLayout (context: Context, attrs: AttributeSet) : LinearLayout(context,attrs){}
这里我们在TitleLayout的主构造函数中声明了Context 和AttributeSet这两个参数,在布局中引入TitleLayout 控件时就会调用这个构造函数。
然后在init结构体中需要对标题栏布局进行动态加载,这就要借助LayoutInflater. 通过LayoutInflater 的from()方法可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件。inflate()方法接收两个参数:
第一个参数是要加载的布局文件的id,这里我们传入R.layout.title;
第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout ,于是直接传入this。
1 2 3 4 5 class TitleLayout (context: Context, attrs: AttributeSet) : LinearLayout(context,attrs){ init { LayoutInflater.from(context).inflate(R.layout.title,this ) } }
引用控件,在这里以activity_main.xml为例
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" > <com.example.uiconstomviews.TitleLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" /> </LinearLayout >
为标题栏的返回按钮注册点击事件,修改`TitleLayout中的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 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) titleBack.setOnClickListener{ val activity = context as Activity activity.finish() } } }
当点击返回按钮时销毁当前Activity 5. 为标题栏的编辑按钮注册点击事件,修改`TitleLayout中的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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) titleBack.setOnClickListener{ val activity = context as Activity activity.finish() } val titleEdit = findViewById<Button>(R.id.titleEdit) titleEdit.setOnClickListener{ Toast.makeText(context,"你点击了编辑按钮" ,Toast.LENGTH_LONG).show() } } }
注意,TitleLayout 中接收的context参数实际上是一个Activity 的实例,在返回按钮的点击事件里,我们要先将它转换成Activity 类型,然后再调用finish()方法销毁当前的Activity 。Kotlin 中的类型强制转换使用的关键字是as,由于是第一次用到,可以看这里 。
列表-ListView(最常用和最难用的控件) ListView 的简单用法
首先新建一个ListViewTest 项目,然后修改activity_main.xml 中的代码,如下所示:1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" > <ListView android:id ="@+id/listView" android:layout_width ="match_parent" android:layout_height ="wrap_content" /> </LinearLayout >
接下来修改MainActivity中的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 adapter = ArrayAdapter<String>(this ,android.R.layout.simple_list_item_1,data ) val listView = findViewById<ListView>(R.id.listView) listView.adapter = adapter } }
定制ListVie的界面
首先需要准备好一组图片资源,见配套资源源码\第4章\ListViewTest\app\src\main\res\drawable-xxhdpi\.复制里面的图片到res/drawable-xxhdpi文件夹.
定义实体类Fruit用于作为适配器的适配类型
1 2 3 class Fruit (name:String,imageId:Int ) {}
要为ListView的子项指定一个我们自定义的布局,在layout 目录下新建fruit_item.xml ,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="60dp" > <ImageView android:id ="@+id/fruitImage" android:layout_width ="40dp" android:layout_height ="40dp" android:layout_gravity ="center_vertical" android:layout_marginLeft ="10dp" /> <TextView android:id ="@+id/fruitName" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_gravity ="center_vertical" android:layout_marginLeft ="10dp" /> </LinearLayout >
在这个布局中,我们定义了一个ImageView 用于显示水果的图片,又定义了一个TextView 用于显示水果的名称,并让ImageView 和TextView 都在垂直方向上居中显示。 4. 要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类。新建类FruitAdapter,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class FruitAdapter (activity: Activity,val resouceId: Int , data : List<Fruit>) :ArrayAdapter<Fruit>(activity, resouceId, data ) { override fun getView (position: Int , convertView: View ?, parent: ViewGroup ) : View { val view = LayoutInflater.from(context).inflate(resouceId,parent,false ) val fruitImage = view.findViewById<ImageView>(R.id.fruitImage) val fruitName = view.findViewById<TextView>(R.id.fruitName) val fruit = getItem(position) if (fruit!=null ){ fruitImage.setImageResource(fruit.imageId) fruitName.text = fruit.name } return view } }
接下来就是使用FruitAdapter,在MainActivity中修改模拟数据,将适配器换成FruitAdapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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" ) private val fruitList = ArrayList<Fruit>().apply { repeat(2 ) { add(Fruit("Apple" , R.drawable.apple_pic)) add(Fruit("Banana" , R.drawable.banana_pic)) add(Fruit("Orange" , R.drawable.orange_pic)) add(Fruit("Watermelon" , R.drawable.watermelon_pic)) add(Fruit("Pear" , R.drawable.pear_pic)) add(Fruit("Grape" , R.drawable.grape_pic)) add(Fruit("Pineapple" , R.drawable.pineapple_pic)) add(Fruit("Strawberry" , R.drawable.strawberry_pic)) add(Fruit("Cherry" , R.drawable.cherry_pic)) add(Fruit("Mango" , R.drawable.mango_pic)) } } override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) val adapter = FruitAdapter(this ,R.layout.fruit_item,fruitList) val listView = findViewById<ListView>(R.id.listView) listView.adapter = adapter } }
提升ListView 的运行效率 之所以说ListView 这个控件很难用,是因为它有很多细节可以优化,其中运行效率就是很重要的一点。目前我们ListView 的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当ListView 快速滚动的时候,这就会成为性能的瓶颈。 在getView()方法中还有一个convertView参数,这个参数会将之前加载好的view进行缓存,以便之后进行复用.我们修改FruitAdapter中的代码进行优化.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class FruitAdapter (activity: Activity,val resouceId: Int , data : List<Fruit>) :ArrayAdapter<Fruit>(activity, resouceId, data ) { inner class ViewHolder (val fruitImage:ImageView,val fruitName:TextView) override fun getView (position: Int , convertView: View ?, parent: ViewGroup ) : View { val viewHolder:ViewHolder val view:View if (convertView ==null ){ view = LayoutInflater.from(context).inflate(resouceId,parent,false ) val fruitImage = view.findViewById<ImageView>(R.id.fruitImage) val fruitName = view.findViewById<TextView>(R.id.fruitName) viewHolder = ViewHolder(fruitImage,fruitName) view.tag = viewHolder }else { view = convertView viewHolder = view.tag as ViewHolder } val fruit = getItem(position) if (fruit!=null ){ viewHolder.fruitImage.setImageResource(fruit.imageId) viewHolder.fruitName.text = fruit.name } return view } }
ListView的点击事件 修改MainActivity 中的代码,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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" ) private val fruitList = ArrayList<Fruit>().apply { repeat(2 ) { add(Fruit("Apple" , R.drawable.apple_pic)) add(Fruit("Banana" , R.drawable.banana_pic)) add(Fruit("Orange" , R.drawable.orange_pic)) add(Fruit("Watermelon" , R.drawable.watermelon_pic)) add(Fruit("Pear" , R.drawable.pear_pic)) add(Fruit("Grape" , R.drawable.grape_pic)) add(Fruit("Pineapple" , R.drawable.pineapple_pic)) add(Fruit("Strawberry" , R.drawable.strawberry_pic)) add(Fruit("Cherry" , R.drawable.cherry_pic)) add(Fruit("Mango" , R.drawable.mango_pic)) } } override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) val adapter = FruitAdapter(this ,R.layout.fruit_item,fruitList) val listView = findViewById<ListView>(R.id.listView) listView.adapter = adapter listView.setOnItemClickListener{ _, _, position, _ -> val fruit = fruitList[position] Toast.makeText(this ,fruit.name,Toast.LENGTH_LONG).show() } } }
更强大的滚动控件-RecyclerView 基本用法
检查是否存在依赖 最新版的Android Studio已经集成了RecyclerView,请在布局文件中输入RecyclerView查看是否有提示 若没有请参考这篇文章 添加依赖
修改activity_main.xml中的代码,如下所示:
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" > <androidx.recyclerview.widget.RecyclerView android:id ="recyclerView" android:layout_width ="match_parent" android:layout_height ="wrap_content" /> </LinearLayout >
需要注意的是,由于RecyclerV iew 并不是内置在系统SDK当中的,所以需要把完整的包路径写出来。 3. 由于实现的效果一样,所以将ListView项目中的图片复制过来,同时也将Fruit类和fruit_item.xml复制过来. 4. 为RecyclerView指定适配器,新建FruitAdapter类继承RecyclerView.Adapter且泛型指定为FruitAdapter.ViewHolder.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class FruitAdapter (val fruitList:List<Fruit>):RecyclerView.Adapter<FruitAdapter.ViewHolder>(){ 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 = fruitList.size }
适配器准备好了之后,我们就可以开始使用RecyclerView了,修改MainActivity中的代码,如 下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class MainActivity : AppCompatActivity () { private val fruitList = ArrayList<Fruit>().apply { repeat(2 ) { add(Fruit("Apple" , R.drawable.apple_pic)) add(Fruit("Banana" , R.drawable.banana_pic)) add(Fruit("Orange" , R.drawable.orange_pic)) add(Fruit("Watermelon" , R.drawable.watermelon_pic)) add(Fruit("Pear" , R.drawable.pear_pic)) add(Fruit("Grape" , R.drawable.grape_pic)) add(Fruit("Pineapple" , R.drawable.pineapple_pic)) add(Fruit("Strawberry" , R.drawable.strawberry_pic)) add(Fruit("Cherry" , R.drawable.cherry_pic)) add(Fruit("Mango" , R.drawable.mango_pic)) } } override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) val layoutManager = LinearLayoutManager(this ) val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) recyclerView.layoutManager = layoutManager val adapter = FruitAdapter(fruitList) recyclerView.adapter = adapter } }
现在运行一下程序,效果如图所示。 可以看到,我们使用RecyclerView 实现了和ListView几乎一模一样的效果,虽说在代码量方面 并没有明显的减少,但是逻辑变得更加清晰了。
实现横向滚到和瀑布流布局 若要实现为了实现横向滚动的话,ListView就做不到了.这时候就需要使用RecyclerView来实现了.
修改fruit_item.xml,将LinearLayout中的对齐方式(orientation)修改为垂直排列.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:orientation ="vertical" android:layout_width ="80dp" android:layout_height ="wrap_content" > <ImageView android:id ="@+id/fruitImage" android:layout_width ="40dp" android:layout_height ="40dp" android:layout_gravity ="center_horizontal" android:layout_marginLeft ="10dp" /> <TextView android:id ="@+id/fruitName" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_gravity ="center_horizontal" android:layout_marginLeft ="10dp" /> </LinearLayout >
将LinearLayout 改成垂直方向排列,并把宽度设为80 dp 。这里将宽度指定为固定值是因为每种水果的文字长度不一致,如果用wrap_content的话,RecyclerView 的子项就会有长有短,非常不美观,而如果用match_parent的话,就会导致宽度过长,一个子项占满整个屏幕。然后我们将ImageV iew 和Te xtView 都设置成了在布局中水平居中,并且使用layout_marginTop属性让文字和图片之间保持一定距离 2. 修改MainActivity中的代码,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class MainActivity : AppCompatActivity () { private val fruitList = ArrayList<Fruit>().apply { repeat(2 ) { add(Fruit("Apple" , R.drawable.apple_pic)) add(Fruit("Banana" , R.drawable.banana_pic)) add(Fruit("Orange" , R.drawable.orange_pic)) add(Fruit("Watermelon" , R.drawable.watermelon_pic)) add(Fruit("Pear" , R.drawable.pear_pic)) add(Fruit("Grape" , R.drawable.grape_pic)) add(Fruit("Pineapple" , R.drawable.pineapple_pic)) add(Fruit("Strawberry" , R.drawable.strawberry_pic)) add(Fruit("Cherry" , R.drawable.cherry_pic)) add(Fruit("Mango" , R.drawable.mango_pic)) } } override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) val layoutManager = LinearLayoutManager(this ) layoutManager.orientation = LinearLayoutManager.HORIZONTAL val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) recyclerView.layoutManager = layoutManager val adapter = FruitAdapter(fruitList) recyclerView.adapter = adapter } }
MainActivity 中只加入了一行代码,调用LinearLayoutManager 的setOrientation()方法设置布局的排列方向。默认是纵向排列的,我们传入LinearLayoutManager.HORIZONTAL表示让布局横行排列,这样RecyclerV iew 就可以横向滚动了。 3. 实现瀑布流布局
修改一下fruit_item.xml中的代码,如下所示:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:orientation ="vertical" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_margin ="10dp" ><ImageView android:id ="@+id/fruitImage" android:layout_width ="40dp" android:layout_height ="40dp" android:layout_gravity ="center_horizontal" android:layout_marginLeft ="10dp" /> <TextView android:id ="@+id/fruitName" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_gravity ="left" android:layout_marginLeft ="10dp" /> </LinearLayout >
这里做了几处小的调整,首先将LinearLayout 的宽度由80 dp 改成了`match_parent`,因为瀑
布流布局的宽度应该是根据布局的列数来自动适配的,而不是一个固定值。其次我们使用了
`layout_margin`属性来让子项之间互留一点间距,这样就不至于所有子项都紧贴在一些。最后
还将TextView 的对齐属性改成了`居左对齐`,因为待会我们会将文字的长度变长,如果还是居中
显示就会感觉怪怪的.
接着修改MainActivity 中的代码,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package com.example.recyclerviewtestimport androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport androidx.recyclerview.widget.LinearLayoutManagerimport androidx.recyclerview.widget.RecyclerViewimport androidx.recyclerview.widget.StaggeredGridLayoutManagerclass MainActivity : AppCompatActivity () { private val fruitList = ArrayList<Fruit>().apply { repeat(2 ) { add(Fruit(getRandomLengthString(getRandomLengthString("Apple" )), R.drawable.apple_pic)) add(Fruit(getRandomLengthString(getRandomLengthString("Banana" )), R.drawable.banana_pic)) add(Fruit(getRandomLengthString(getRandomLengthString("Orange" )), R.drawable.orange_pic)) add(Fruit(getRandomLengthString(getRandomLengthString("Watermelon" )), R.drawable.watermelon_pic)) add(Fruit(getRandomLengthString(getRandomLengthString("Pear" )), R.drawable.pear_pic)) add(Fruit(getRandomLengthString(getRandomLengthString("Grape" )), R.drawable.grape_pic)) add(Fruit(getRandomLengthString(getRandomLengthString("Pineapple" )), R.drawable.pineapple_pic)) add(Fruit(getRandomLengthString(getRandomLengthString("Strawberry" )), R.drawable.strawberry_pic)) add(Fruit(getRandomLengthString(getRandomLengthString("Cherry" )), R.drawable.cherry_pic)) add(Fruit(getRandomLengthString(getRandomLengthString("Mango" )), R.drawable.mango_pic)) } } override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) val layoutManager = StaggeredGridLayoutManager(3 ,StaggeredGridLayoutManager.VERTICAL) val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) recyclerView.layoutManager = layoutManager val adapter = FruitAdapter(fruitList) recyclerView.adapter = adapter } private fun getRandomLengthString (str:String ) :String{ val n = (1. .20 ).random() val builder = StringBuilder() repeat(n){ builder.append(str) } return builder.toString() } }
StaggeredGridLayoutManager的构造函数接收两个参数: - 第一个参数用于指定布局的列s数,传入3表示会把布局分为3列 - 第二个参数用于指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示会让布局纵向排列。 这时候运行项目你大概会看到这样的效果
RecyclerView的点击事件 RecyclerView 并没有提供类似于setOnItemClickListener()这样的注册监听器方法,而是需要我们自己给子项具体的View去注册点击事件。这相比于ListView 来说,实现起来要复杂一些。 RecyclerView摒弃了子项点击事件的监听器,让所有的点击事件都由具体的View 去注册.
下面我们来具体学习一下如何在RecyclerView 中注册点击事件,修改FruitAdapter中的代码,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.example.recyclerviewtestimport android.media.Imageimport android.util.Logimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.ImageViewimport android.widget.TextViewimport android.widget.Toastimport androidx.recyclerview.widget.RecyclerViewclass FruitAdapter (val fruitList:List<Fruit>):RecyclerView.Adapter<FruitAdapter.ViewHolder>(){ 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 ) val viewHolder = ViewHolder(view) viewHolder.itemView.setOnClickListener { val position = viewHolder.adapterPosition val fruit = fruitList[position] Toast.makeText(parent.context,"你点击了文本${fruit.name} " ,Toast.LENGTH_SHORT).show() } viewHolder.fruitImage.setOnClickListener { val position = viewHolder.adapterPosition val fruit = fruitList[position] Toast.makeText(parent.context,"你点击了图片${fruit.name} " ,Toast.LENGTH_SHORT).show() } return viewHolder } 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 = fruitList.size }
可以看到,这里我们是在onCreateViewHolder()方法中注册点击事件。上述代码分别为最外层布局和ImageView 都注册了点击事件,itemView 表示的就是最外层布局。RecyclerView的强大之处也在于此,它可以轻松实现子项中任意控件或布局的点击事件。我们在两个点击事件中先获取了用户点击的position ,然后通过position 拿到相应的Fruit实例,再使用Toast 分别弹出两种不同的内容以示区别。
编写界面的最佳实践 既然已经学习了那么多UI开发的知识,是时候实战一下了。这次我们要综合运用前面所学的大量内容来编写出一个较为复杂且相当美观的聊天界面,你准备好了吗?要先创建一个UIBestPractice 项目才算准备好了哦。
制作9-Patch (点9)图片 9-Patch你之前可能没有听说过这个名词,它是一种被特殊处理过的png 图片,能够指定哪些区域可以被拉伸、哪些区域不可以。接下来还是通过一个例子来了解一下它吧!
复制资料的源码\第4章\UIBestPractice\app\src\main\res\drawable-xxhdpi\下的message_left_original.png和message_right_original.png文件到res\drawable-xxhdpi,如图所示.
制作作9-Patch 图片其实并不复杂,只要掌握好规则就行了,那么现在我们就来学习一下。 在Andr oid Studio 中,我们可以将任何png 类型的图片制作成9-Patch 图片。首先对着message_left_original.png 图片右击→Create 9-P atch file ,会弹出如图所示的对话框。这里保持默认文件名就可以了,其实就相当于创建了一张以9.png 为后缀的同名图片,点击“Save” 完成保存。 这时Andr oid Studio 会显示如图所示的编辑界面。
在体魄四个边框按鼠标左键可以进行边框绘制,被标黑的区域表示该方向图片可以被拉伸,按shift进行拖动可以取消标记. 左键标记 shift+左键取消标记 自行修改图片left和right或者将资料中的.9文件复制即可.
编写精美的聊天界面 既然是要编写一个聊天界面,那肯定要有收到的消息和发出的消息。
接下来开始编写主界面,修改activity_main.xml 中的代码,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:orientation ="vertical" android:layout_height ="match_parent" android:layout_width ="match_parent" android:background ="#d8e0e8" > <androidx.recyclerview.widget.RecyclerView android:id ="@+id/recyclerView" android:layout_width ="match_parent" android:layout_height ="0dp" android:layout_weight ="1" /> <LinearLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <EditText android:id ="@+id/inputText" android:layout_width ="0dp" android:layout_height ="wrap_content" android:layout_weight ="1" android:hint ="在这输入消息" android:maxLines ="2" /> <Button android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="发送" /> </LinearLayout > </LinearLayout >
然后定义消息的实体类,新建Msg.kt,代码如下所示:
1 2 3 4 5 6 class Msg (val content: String, val type: Int ) { companion object { const val TYPE_RECEIVED = 0 const val TYPE_SENT = 1 } }
Msg类中只有两个字段:content表示消息的内容,type表示消息的类型。其中消息类型有两个值可选:TYPE_RECEIVED表示这是一条收到的消息,TYPE_SENT表示这是一条发出的消息。这里我们将TYPE_RECEIVED和TYPE_SENT定义成了常量,定义常量的关键字是const,注意只有在单例类、companion object 或顶层方法中才可以使用const关键字。
接下来开始编写RecyclerView的子项布局,新建msg_left_item.xml ,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="utf-8" ?> <FrameLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:padding ="10dp" > <LinearLayout android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_gravity ="left" android:background ="@drawable/message_left" > <TextView android:id ="@+id/leftMsg" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:gravity ="center" android:layout_margin ="10dp" android:textColor ="#fff" /> </LinearLayout > </FrameLayout >
里我们让收到的消息居左对齐,并使用message_left.9.png 作为背景图。
接下来开始编写个发送消息的子项布局,新建msg_right_item.xml ,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="utf-8" ?> <FrameLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:padding ="10dp" > <LinearLayout android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_gravity ="right" android:background ="@drawable/message_right" > <TextView android:id ="@+id/rightMsg" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:gravity ="left" android:layout_margin ="10dp" android:textColor ="#000000" /> </LinearLayout > </FrameLayout >
里我们让收到的消息居右对齐,并使用message_right.9.png 作为背景图。
创建RecyclerView 的适配器类,新建类MsgAdapter,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.example.uibaseprojectimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.TextViewimport androidx.recyclerview.widget.RecyclerViewimport androidx.recyclerview.widget.RecyclerView.ViewHolderclass MsgAdapter (val msgList:List<Msg>) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){ inner class LeftViewHolder (view: View,val leftMsg : TextView=view.findViewById(R.id.leftMsg)) : RecyclerView.ViewHolder(view) inner class RightViewHolder (view: View,val rightMsg : TextView=view.findViewById(R.id.rightMsg)) : RecyclerView.ViewHolder(view) override fun getItemViewType (position: Int ) : Int = msgList[position].type override fun onCreateViewHolder (parent: ViewGroup , viewType: Int ) : RecyclerView.ViewHolder = when (viewType){ Msg.TYPE_RECEIVED -> LeftViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item,parent,false )) else -> RightViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item,parent,false )) } override fun onBindViewHolder (holder: RecyclerView .ViewHolder , position: Int ) { val msg = msgList[position] when (holder){ is LeftViewHolder -> holder.leftMsg.text = msg.context is RightViewHolder -> holder.rightMsg.text = msg.context else -> throw IllegalArgumentException() } } override fun getItemCount () : Int = msgList.size }
最后修改MainActivity 中的代码,为RecyclerView初始化一些数据,并给发送按钮加入事件响应,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class MainActivity : AppCompatActivity () { private val msgList = ArrayList<Msg>().apply { add( Msg("Hello" ,Msg.TYPE_RECEIVED)) add( Msg("HI" ,Msg.TYPE_SENT)) add( Msg("小明" ,Msg.TYPE_RECEIVED)) add( Msg("小红" ,Msg.TYPE_SENT)) add( Msg("在做什么" ,Msg.TYPE_RECEIVED)) add( Msg("写代码" ,Msg.TYPE_SENT)) } private var adapter:MsgAdapter? = null override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) val layoutManager = LinearLayoutManager(this ) val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) recyclerView.layoutManager = layoutManager adapter = MsgAdapter(msgList) recyclerView.adapter = adapter val send = findViewById<Button>(R.id.send) send.setOnClickListener{ val inputText = findViewById<EditText>(R.id.inputText) val content = inputText.text.toString() if (content.isNotEmpty()){ val msg = Msg(content,Msg.TYPE_SENT) msgList.add(msg) adapter?.notifyItemInserted(msgList.size-1 ) recyclerView.scrollToPosition(msgList.size -1 ) inputText.setText("" ) } } } }
msgList中初始化了几条数据用于在RecyclerV iew 中显示,接下来按照标准的方式构建RecyclerView ,给它指定一个LayoutManager 和一个适配器。然后在发送按钮的点击事件里获取了EditText 中的内容,如果内容不为空字符串,则创建一个新的Msg对象并添加到msgList 列表中去。之后又调用了适配器的notifyItemInserted()方法,用于通知列表有新的数据插入,这样新增的一条消息才能够在RecyclerV iew 中显示出来。或者你也可以调用适配器的notifyDataSetChanged()方法,它会RecyclerV iew 中所有可见的元素全部刷新,这样不管是新增、删除、还是修改元素,界面上都会显示最新的数据,但缺点是效率会相对差一些。接着调用RecyclerV iew 的scrollToPosition()方法将显示的数据定位到最后一行,以保证一定可以看得到最后发出的一条消息。最后调用EditTe xt 的setText()方法将输入的内容清空。这样所有的工作都完成了,终于可以检验一下我们的成果了。 运行程序之后,你将会看到非常美观的聊天界面,并且可以输入和发送消息,如图所示。 8. 本篇代码示例Github