4.1 如何编写程序界面
4.2 常用控件
事前准备
首先另外创建一个Empty Activity
再按照上面提到的方法创建一个Layout.xml
,配置如下:
接着在MainActivity
中修改一下布局配置:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_linear)
}
}
4.2.1 TextView
高度和宽度
首先在刚刚创建的xml文件下添加如下控件:
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is TextView"/>
id和text就不多解释了,比较容易理解,重点解释一下中间两个属性的意思:
- match_parent表示让当前控件的大小和父布局的大小一样,也就是由父布局来决定当前控件的大小。
- wrap_content表示让当前控件的大小能够刚好包含住里面的内容,也就是由控件内容决定当前控件的大小。
所以上面的代码就表示让TextView的宽度和父布局一样宽,也就是手机屏幕的宽度,让TextView的高度足够包含住里面的内容就行。
当然这里的高度和宽度也可以直接用固定值表示:
固定值表示表示给控件指定一个固定的尺寸,单位一般用dp,这是一种屏幕密度无关的尺寸单位,可以保证在不同分辨率的手机上显示效果尽可能地一致,如50 dp
就是一个有效的固定值。
对齐方式
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="This is TextView"/>
这里新添加的`android:gravity`就是用来控制对齐方式的,可选值有`top`、`bottom`、`start`、`end`、`center`等。<br />可以用`**|**`来同时指定多个值,对齐方式又区分上下对齐和左右对齐:<br />比如这里的`center`其实是`center_vertical | center_horizontal`,表示文字在**垂直**和**水平**方向都居中对齐。
字体颜色和大小
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#00ff00"
android:textSize="24sp"
android:text="This is TextView"/>
显而易见,`android:textColor`属性可以指定**文字的颜色**,通过`android:textSize`属性可以指定文字的**大小**。
注意:指定尺寸时,文字用
sp
,图形用dp
4.2.2 Button
基本属性
这个组件上一章中已经用到过了,这里再复习一下:
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button"
android:textAllCaps="false"/>
默认情况下Button控件会把字符转化为大写,添加
android:textAllCaps="false"
可以保留原始文字内容。
函数实现
Button组件通常都会**绑定事件**(一般都是点击事件):
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button:Button = findViewById(R.id.button)
button.setOnClickListener(){
//...
}
}
}
上述绑定事件的方法我们一般叫做**使用函数式API的方式来注册监听器。**
因为
setOnClickListener()
就类似于一个函数的使用,通过这个函数来监听点击事件的发生。
接口实现
除了上述方法,还可以通过实现接口的方法:
class MainActivity : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_linear)
val button:Button = findViewById(R.id.button)
button.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.button -> {
Toast.makeText(this,"TestOnClick",Toast.LENGTH_LONG).show()
// 在此处添加逻辑
}
}
}
}
`setOnClickListener()`是给**指定控件**监听点击事件,那么`override fun onClick(v: View?) ` 就是一个所有监听的点击事件的**集合**,鼠标点击**某一个控件**之后会自动传入`**控件参数(View)**`,onClick接口会通过**when**来判断控件的id,根据id来响应不同的事件。<br />注意这里又继承了一个类`View.OnClickListener`,所以必须重写`onClick`方法
4.2.3 EditText
EditText
是程序用于和用户进行交互的另一个重要控件,它允许用户在控件里输入和编辑内容,并可以在程序中对这些内容进行处理。
占位文本(提示文本)
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here"
/>
这里的android:hint
后面的内容就是输入框中的提示内容:
最大行数
随着输入的内容不断增多,EditText
会被不断地拉长。这是由于EditText
的高度指定的是wrap_content
,因此它总能包含住里面的内容,但是当输入的内容过多时,界面就会变得非常难看。我们可以使用android:maxLines
属性来解决这个问题,修改activity_main.xml,如下所示:
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here"
android:maxLines="2"
/>
获取输入的内容
修改一下onClick接口:
class MainActivity : AppCompatActivity(), View.OnClickListener {
...
override fun onClick(v: View?){
when(v?.id){
R.id.button->{
val editText:EditText = findViewById(R.id.editText)
val inputText = editText.text.toString()
Toast.makeText(this,inputText,Toast.LENGTH_LONG).show()
}
}
}
}
在按钮的点击事件里调用EditText的getText()
方法获取输入的内容,再调用toString()
方法将内容转换成字符串,最后使用Toast将输入的内容显示出来。
重点语句:val inputText = editText.text.toString()
4.2.4 ImageView
ImageView
是用于在界面上展示图片的一个控件。图片通常是放在以drawable开头的目录下的,并且要带上具体的分辨率。
基本用法
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/tom_and_jerry"
/>
这里的android:src
表示的就是图片的存放路径。
图片素材:(由于语雀现在还上传不了安装包 只能凑合 有Android 群的可以在群里面下载)
back_bg.pngedit_bg.pngmessage_left.pngmessage_right.pngtitle_bg.pngtom_and_jerry.jpgtom_and_jerry2.jpg
apple_pic.pngbanana_pic.pngcherry_pic.pnggrape_pic.pngmango_pic.pngorange_pic.pngpear_pic.pngpineapple_pic.pngstrawberry_pic.pngwatermelon_pic.png
动态修改图片
这里使用的接口函数:
class MainActivity : AppCompatActivity(), View.OnClickListener {
...
override fun onClick(v: View?) {
when (v?.id) {
R.id.button -> {
// 根据id获取图片控件
val imageView:ImageView = findViewById(R.id.imageView)
// 设置图片控件的地址(src)
imageView.setImageResource(R.drawable.tom_and_jerry2)
}
}
}
}
点击Button之后,效果如下:<br />![点击之前](https://cdn.nlark.com/yuque/0/2022/png/22022942/1647929006560-831e5c82-daef-4cb9-9418-709bad2f871d.png#averageHue=%23c4ccc1&clientId=u5ef1e71f-329d-4&from=paste&height=432&id=ufde8feab&originHeight=864&originWidth=407&originalType=binary&ratio=1&rotation=0&showTitle=true&size=171132&status=done&style=none&taskId=u6a920246-07af-493f-a194-53347aaab42&title=%E7%82%B9%E5%87%BB%E4%B9%8B%E5%89%8D&width=203.5 "点击之前")![点击之后](https://cdn.nlark.com/yuque/0/2022/png/22022942/1647929018072-9b60d9a4-2ec2-4d1e-85bb-a8f16e770d39.png#averageHue=%23adc4a2&clientId=u5ef1e71f-329d-4&from=paste&height=432&id=u261810a0&originHeight=864&originWidth=407&originalType=binary&ratio=1&rotation=0&showTitle=true&size=226547&status=done&style=none&taskId=u3f918726-505e-43c5-b41e-b389bd8839f&title=%E7%82%B9%E5%87%BB%E4%B9%8B%E5%90%8E&width=203.5 "点击之后")
4.2.5 ProcessBar
ProgressBar
用于在界面上显示一个进度条,表示我们的程序正在加载一些数据。
基本用法
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
设置可见属性
Android 控 件 的 可 见 属 性。 所 有 的 Android 控 件 都 具 有 这 个 属 性, 可 以 通 过android:visibility进行指定,可选值有3种:visible
、invisible
和gone
。
visible
:表示控件是可见的,这个值是默认值,不指定android:visibility时,控件都是可见的。invisible
:表示控件不可见,但是它仍然占据着原来的位置和大小,可以理解成控件变成透明状态了。gone
:则表示控件不仅不可见,而且不再占用任何屏幕空间。
在代码中可以通过setVisibility()
方法来动态改变控件的可见性:View.VISIBLE、View.INVISIBLE和View.GONE。
例如:
override fun onClick(v: View?) {
when (v?.id) {
R.id.button -> {
// 根据id获取进度条控件
val progressBar:ProgressBar =findViewById(R.id.progressBar)
// 当进度条控件的可见性为可见的时候 点击button会让进度条控件消失 反之则出现
if (progressBar.visibility == View.VISIBLE){
progressBar.visibility = View.GONE
} else{
progressBar.visibility = View.VISIBLE
}
}
}
}
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22022942/1647929655965-1523e47c-87b9-496e-9c16-b64637d5b089.png#averageHue=%23b5c4b4&clientId=u5ef1e71f-329d-4&from=paste&height=432&id=ufc1ef780&originHeight=864&originWidth=407&originalType=binary&ratio=1&rotation=0&showTitle=false&size=172064&status=done&style=none&taskId=u5aa38c85-bc71-4b49-90ee-8f4765a8636&title=&width=203.5) ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22022942/1647929665241-e2d43a89-1c9d-479d-a5f0-7bf91ad5b161.png#averageHue=%23b5c3b4&clientId=u5ef1e71f-329d-4&from=paste&height=432&id=u4d09f53a&originHeight=864&originWidth=407&originalType=binary&ratio=1&rotation=0&showTitle=false&size=171150&status=done&style=none&taskId=ub7b66fca-3c89-473a-a3ab-82729f01483&title=&width=203.5)
设置进度条样式
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
/>
这里的`style`就是设置样式,这里代表的就是**横向进度条**。<br />`android:max`属性给进度条设置一个**最大值**。<br />还可以在代码里面动态修改进度条的进度:
override fun onClick(v: View? ) {
when (v?.id) {
// 每次点击button,进度都+10
R.id.button -> {
progressBar.progress = progressBar.progress + 10
}
}
}
更多属性和API参见:
Android 进度条 ProgressBar丨慕课网教程
4.2.6 AlertDialog
AlertDialog
可以在当前界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,能够屏蔽其他控件的交互能力,因此AlertDialog
一般用于提示一些非常重要的内容或者警告信息。比如为了防止用户误删重要内容,在删除前弹出一个确认对话框。
基本语法
override fun onClick(v: View?) {
when (v?.id) {
R.id.button -> {
// 构建AlertDialog控件
AlertDialog.Builder(this).apply {
setTitle("This is Dialog")
setMessage("Something important.")
setCancelable(false)
setPositiveButton("OK") { dialog, id ->
}
setNegativeButton("Cancel") {_,_->
}
show()
}
}
}
}
来逐个解析一下:
AlertDialog.Builder(this)
基本就是固定写法,表示构建一个对话框。apply
函数用于设置对黄框的一些属性- setTitle:设置标题
- setMessage:设置提示信息
- setCancelable:设置是否可以用BACK键关闭对话框
- setPositiveButton:为对话框设置确定按钮的文本样式(“OK”)和点击事件
- setNegativeButton:设置取消按钮的文本样式和点击事件
- show:显示对话框
这设置的点击事件不用管,因为我也看不懂。
4.3 三种基本布局
基本概念
布局是一种可用于放置很多控件的容器,它可以按照一定的规律调整内部控件的位置,从而编写出精美的界面。当然,布局的内部除了放置控件外,也可以放置布局,通过多层布局的嵌套,我们就能够完成一些比较复杂的界面实现,图很好地展示了它们之间的关系。
4.3.1 LinearLayout
基本介绍
LinearLayout
又称作线性布局,这个布局会将它所包含的控件在线性方向上依次排列,一般情况下都是从上到下排序,如果想改变排序方向可以通过android:orientation
改变:
水平就是horizontal
,默认的垂直布局就是vertical
。
例如:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical|horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 2" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 3" />
</LinearLayout>
需要注意,如果LinearLayout的排列方向是horizontal,内部的控件就绝对不能将宽度指定为
match_parent
,否则,单独一个控件就会将整个水平方向占满,其他的控件就没有可放置的位置了。
布局中的对齐方式
在前面我们知道android:gravity
用于指定文字在控件中的对齐方式, 而android:layout_gravity
用于指定控件在布局中的对齐方式。
需要注意,当LinearLayout的排列方向是horizontal
时,只有垂直方向上的对齐方式才会生效。因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上的对齐方式。同样的道理,当LinearLayout的排列方向是vertical
时,只有水平方向上的对齐方式才会生效。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:text="Button 1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="Button 2" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="Button 3" />
</LinearLayout>
效果如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22022942/1647932604166-00974fff-6e49-40e8-b7b4-938f1810c613.png#averageHue=%235d5034&clientId=u5ef1e71f-329d-4&from=paste&height=432&id=uc789d9a7&originHeight=864&originWidth=407&originalType=binary&ratio=1&rotation=0&showTitle=false&size=86321&status=done&style=none&taskId=u12afa27a-a9f3-4cfd-85b3-cad7c00f62c&title=&width=203.5)
指定控件大小
指定控件大小的属性是android:layout_weight
,更贴切一点应该是设置布局的比重。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/input_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Type something"
/>
<Button
android:id="@+id/send"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Send"
/>
</LinearLayout>
在这个代码里面,可以看到编辑框和按钮的高度都是自适应的高度,但是宽度都统一设置为0dp,这是由于我们使用了`android:layout_weight`属性,此时控件的宽度就不应该再由`android:layout_width`来决定了,这里指定成0 dp是一种比较规范的写法。<br />这里两个控件都设置的`android:layout_weight="1"`,所以每个控件的宽度是整个屏幕宽度的1/(1+1) = 1/2。
因此如果想让EditText占据屏幕宽度的3/5,Button占据屏幕宽度的2/5,只需要将EditText的layout_ weight改成3,Button的layout_weight改成2就可以了。
当然也可以一个设置比重,另一个不设置,此时设置比重的就会根据总宽度(高度)减去不设置控件宽度(高度)来分配比重。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/input_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Type something"
/>
<Button
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send"
/>
</LinearLayout>
4.3.2 RelativeLayout
基本介绍
RelativeLayout
又称作相对布局,也是一种非常常用的布局。和LinearLayout的排列规则不同,RelativeLayout显得更加随意,它可以通过相对定位的方式让控件出现在布局的任何位置。
相对于父控件
来看一下实例:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="Button 1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:text="Button 2" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Button 3" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:text="Button 4" />
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:text="Button 5" />
</RelativeLayout>
效果如下:
来解释一下上面新出现的属性:
android:layout_alignParentLeft // 位于父控件的左边
android:layout_alignParentTop // 位于父控件的顶部
android:layout_alignParentRight // 位于父控件的右边
android:layout_alignParentBottom // 位于父控件的底部
android:layout_centerInParent // 位于父控件的正中间
控件默认都是位于左上角的,设立属性值为
ture
的时候属性生效。这里的五个button 的父控件就是整个手机屏幕。
相对于其他控件
还是先看实例:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Button 3" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/button3"
android:layout_toLeftOf="@id/button3"
android:text="Button 1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/button3"
android:layout_toRightOf="@id/button3"
android:text="Button 2" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button3"
android:layout_toLeftOf="@id/button3"
android:text="Button 4" />
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button3"
android:layout_toRightOf="@id/button3"
android:text="Button 5" />
</RelativeLayout>
效果如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22022942/1648282869752-9e952d8d-d69c-45ef-af80-6c8a98faf2e5.png#averageHue=%233d3931&clientId=u8e0a46eb-fa95-4&from=paste&height=691&id=uf7cd69d5&originHeight=864&originWidth=408&originalType=binary&ratio=1&rotation=0&showTitle=false&size=89671&status=done&style=none&taskId=uf069a58f-07dc-4e63-81bf-a19babb780e&title=&width=326.4)<br />其中几个属性的解析:
android:layout_below
在某控件下方android:layout_above
在某控件上方android:layout_toLeftOf
在某控件的左边android:layout_toRightOf
在某控件的右边android:layout_alignTop
本控件的上边缘和某控件的上边缘对齐android:layout_alignLeft
本控件的左边缘和某控件的左边缘对齐android:layout_alignBottom
本控件的下边缘和某控件的下控件对齐android:layout_alignRight
本控件的右边缘和某控件的右边缘对齐版权声明:本文为CSDN博主「 异乡人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/nine_dollar/article/details/107103467
4.3.3 FrameLaylout
这种布局不怎么常用,所以不细讲了,有兴趣的可以参照下面这个文档:
2.2.4 FrameLayout(帧布局) | 菜鸟教程
4.4 自定义
基本介绍
先看一下Android中的一些常用控件和基本布局的继承结构,如图所示:
可以看到,所用的所有控件都是直接或间接继承自View
的,所用的所有布局都是直接或间接继承自ViewGroup的。View是Android中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,因此,使用的各种控件其实就是在View的基础上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器。
4.4.1 引入自定义布局
这里我们拿标题栏举例。
首先在layout
文件目录下创建一个title.xml
布局,并填写如下代码:
属性都比较简单,不知道的搜一下百度就可以,这里就不讲解了。
<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">
<android.widget.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="Back"
android:textColor="#fff" />
<TextView
android:id="@+id/titleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:text="Title Text"
android:textColor="#fff"
android:textSize="24sp" />
<android.widget.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="Edit"
android:textColor="#fff" />
</LinearLayout>
样式如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22022942/1648284420743-9a55a465-6704-4670-8f47-84d1095e077f.png#averageHue=%23354052&clientId=u8e0a46eb-fa95-4&from=paste&height=824&id=u5644a476&originHeight=1030&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=325409&status=done&style=none&taskId=ua4c82e41-a5e0-4796-aa1d-e5d5519acf9&title=&width=1536)<br />接下来在需要title布局的xml中引入:
<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>
一开始的时候,系统会自动给页面创建标题栏,所以需要先隐藏自带的标题栏:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 隐藏标题栏
supportActionBar?.hide()
}
}
最后的效果为:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22022942/1648284869818-9adf1261-1b74-424d-a1fe-51c575765712.png#averageHue=%237d9e19&clientId=u8e0a46eb-fa95-4&from=paste&height=691&id=ud2736ce1&originHeight=864&originWidth=408&originalType=binary&ratio=1&rotation=0&showTitle=false&size=180804&status=done&style=none&taskId=ub843831a-2c4d-4e36-aba9-0cd0847f0fc&title=&width=326.4)
4.4.2 创建自定义控件
还是拿上面的标题栏举例,在开发过程中,不仅是布局样式需要复用,其中的比如点击事件也是需要复用的,所以这个时候会设定一个自定义控件。
首先在com.example
文件目录下新建一个包uicustomviews
,在这个包里面新建一个TitleLayout
的控件(实际上一开始创建的时候是一个kt文件),具体代码如下:
package com.example.uicustomviews
import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.Button
import android.widget.LinearLayout
import android.widget.Toast
import com.example.layoutactivity.R
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:Button = findViewById(R.id.titleEdit)
titleEdit.setOnClickListener {
Toast.makeText(context, "You clicked Edit button", Toast.LENGTH_SHORT).show()
}
}
}
下面来节解析一下代码:
这里在TitleLayout的主构造函数中声明了Context
和AttributeSet
这两个参数,在布局中引入TitleLayout控件时就会调用这个构造函数。
看一下这个两个参数的源码部分:
/**
* Constructor that is called when inflating a view from XML. This is called
* when a view is being constructed from an XML file, supplying attributes
* that were specified in the XML file. This version uses a default style of
* 0, so the only attribute values applied are those in the Context's Theme
* and the given AttributeSet.
*
* <p>
* The method onFinishInflate() will be called after all children have been
* added.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
* @see #View(Context, AttributeSet, int)
*/
public View(Context context, @Nullable AttributeSet attrs)
简单翻译一下可以知道,Content
参数的作用是:可以通过它访问当前主题,资源等。AttributeSet
是在xml资源文件中指定的属性集。
不知道也没有关系,只需要知道在自定义控件中必须使用这两个参数即可。
然后在init结构体中需要对标题栏布局进行动态加载,这就要借助LayoutInflater
来实现了。
这里也可以理解为先讲这个空白的控件在init中初始化。
通过LayoutInflater
的from()
方法可以构建出一个LayoutInflater
对象,然后调用inflate()方法就可以动态加载一个布局文件。inflate()方法接收两个参数:
- 第一个参数是要加载的布局文件的id,这里传入
R.layout.title
; - 第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为
TitleLayout
,于是直接传入this
。LayoutInflater.from(context).inflate(R.layout.title,this)
这里的title是之前就写好的样式。
后面的代码就比较简单了:
val titleBack:Button = findViewById(R.id.titleBack)
titleBack.setOnClickListener(){
val activity = context as Activity
activity.finish()
}
val titleEdit:Button = findViewById(R.id.titleEdit)
titleEdit.setOnClickListener {
Toast.makeText(context, "You clicked Edit button", Toast.LENGTH_SHORT).show()
}
从布局文件中获取相应的基础控件,并且注册相应的点击事件。
到这里一个自定义控件就写好了。
接下来我们就可以再其他布局文件中使用这个自定义控件,在我们需要使用自定义控件的地方添加如下代码。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
...
<com.example.uicustomviews.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
这里就不运行,直接分屏看一下代码和最终的布局样式效果。
4.5 ListView
4.5.1基础用法
由于手机屏幕空间比较有限,能够一次性在屏幕上显示的内容并不多,当程序中有大量的数据需要展示的时候,就可以借助ListView
来实现。ListView
允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据会滚动出屏幕。
新建一个ListViewTestActivity
,新建一个drawable-xxhdpi
文件夹,把之前的图片先全放进去。
修改main的布局文件:
<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="match_parent" />
</LinearLayout>
这里使用了ListView
控件,属性比较简单就不讲解了。
接下来修改MainActivity:
package com.example.listviewtest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.ListView
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: ListView = findViewById(R.id.listView)
listView.adapter = adapter
}
}
还是解析代码的环节,看一下之前没见过的。
private val data = listOf("Apple", "Banana", "Orange", "Watermelon","Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango","Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape","Pineapple", "Strawberry", "Cherry", "Mango")
这一段不用多说,就是构造了一个字符串的数组。
我们预期在屏幕上显示的就是上面这个字符串数组的内容。
val adapter = ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data)
val listView: ListView = findViewById(R.id.listView)
listView.adapter = adapter
首先看adapter
是什么:
由于集合中的数据是无法直接传递给ListView的,还需要借助适配器来完成。其中ArrayAdapter
就是适配器的一种。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。
这里不知道泛型的定义没关系,直接看赋值语句:val adapter = ArrayAdapter<String>(...)
,其中ArrayAdapter<String>(...)
原始的样子应该是ArrayAdapter< T >(...)
。
所谓泛型可以简单的理解为可以传入任意类型,这里需要适配的类型是字符串类型,所以这个地方写的是String。ArrayAdapter<String>
后面的括号中还有三个参数,依次传入的
- Activity的实例(这里因为是传入类本身,所以直接使用
this
即可) - ListView子项布局的id(这是一个Android内置的布局文件,用于简单显示文本内容)
- 以及数据源(也就是刚刚构建的字符串数组)
最后只要实例化ListView的实例listView
的适配器adapter就可以了。
最后看一下效果:
4.5.2 定制ListView界面
①添加控件
在avtivity_main
中添加:
<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="match_parent" />
</LinearLayout>
②创建Fruit类
建包的操作之后就不再赘述了,看图即可:
package com.example.model
class Fruit (val name: String,val imageId:Int){
}
③创建fruit_item.xml
这个的作用就是给每一行定义一下布局样式:
<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>
也就是左边是一张图片,右边是文字介绍,注意这里的id取值。
④创建Fruit的适配器:FruitAdapter
package com.example.adapter
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import com.example.listviewtest.R
import com.example.model.Fruit
class FruitAdapter(activity: Activity, val layoutResourceId: Int, data: List<Fruit>) :
ArrayAdapter<Fruit>(activity, layoutResourceId, data) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = LayoutInflater.from(context).inflate(layoutResourceId, parent, false)
val fruitImageView: ImageView = view.findViewById(R.id.fruitImage)
val fruitNameTextView: TextView = view.findViewById(R.id.fruitName)
val fruit = getItem(position) // 获取当前项的Fruit实例
if (fruit != null) {
fruitImageView.setImageResource(fruit.imageId)
fruitNameTextView.text = fruit.name
}
return view
}
}
首先看一开始的类的定义:
class FruitAdapter(activity: Activity, val layoutResourceId: Int, data: List<Fruit>) :
ArrayAdapter<Fruit>(activity, layoutResourceId, data) {
...
}
由于这个是我们自己构造的一个适配器,所以这里需要继承基础用法里面提到的适配器,其三个参数也同样继承,分别是`Activity的实例`、`ListView子项布局的id`和`数据源`。<br />主体代码部分重写了`getView()`方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。<br />这里看一下重写方法的参数:
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View{...}
position:Int
:单个数据在数据源中的位置因为传入的数据一般是一个数组或者列表,通过position可以查询到单个数据。
convertView: View?
:用于缓存优化的只要了解就可以了。parent: ViewGroup
:每个item的视图,被放在了parent
中,listeview
要显示新出现的一行的视图时,把其取出来。
重写方法的第一个语句在创建自定义控件中有讲到:
val view = LayoutInflater.from(context).inflate(layoutResourceId, parent, false)
- 第一个参数是要加载的布局文件的id,这里传入
layoutResourceId
,通过定义适配器的时候传入; - 第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为
parent
(这里不要纠结为什么,一般都是parent) - 第三个参数是表示只让在父布局中声明的layout属性生效,但不会为这个View添加父布局。(这里也不用管为什么,填false就完事了)
因为一旦View有了父布局之后,它就不能再添加到ListView中了。
接着看下面的:
val fruitImageView: ImageView = view.findViewById(R.id.fruitImage)
val fruitNameTextView: TextView = view.findViewById(R.id.fruitName)
val fruit = getItem(position) // 获取当前项的Fruit实例
if (fruit != null) {
fruitImageView.setImageResource(fruit.imageId)
fruitNameTextView.text = fruit.name
}
return view
前两句很明显是为了根据id获取相应控件。
后面一句val fruit = getItem(position)
是根据位置获取数据源中的实例。
当这个实例不是空的时候,给这个实例设置图片和文案内容。
最后返回一个view对象。
这里的view对象其实就指代一条数据。 比如这里的Apple
每次有一项数据被加载的时候,就会调用这个getView
方法,返回一个View对象。
⑤修改MainActivity
这个反而是最简单解析的,我就直接写在注释里面了:
package com.example.listviewtest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.ListView
import com.example.adapter.FruitAdapter
import com.example.model.Fruit
class MainActivity : AppCompatActivity() {
// 定义一个列表,用于存储水果数据
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initFruits() // 初始化水果数据
// 构建适配器
val adapter = FruitAdapter(this, R.layout.fruit_item, fruitList)
val listView:ListView = findViewById(R.id.listView)
listView.adapter = adapter
}
private fun initFruits() {
// repeat方法里面的代码执行2遍
repeat(2) {
// 实例化一个Fruit对象,并且添加到列表当中
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))
}
}
}
4.5.3 增加点击事件
在MainActivity的OnCreate方法下增加如下代码:
listView.setOnItemClickListener { parent, view, position, id ->
val fruit = fruitList[position]
Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
}
// 因为这里四个参数值只用到了position 所以其他的可以写成_
listView.setOnItemClickListener { _, _, position, _ ->
val fruit = fruitList[position]
Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
}
4.6 RecyclerView
RecyclerView
可以说是一个增强版的ListView
,不仅可以轻松实现和ListView
同样的效果,还优化了ListView
存在的各种不足之处。
这里提示一瞎,下面的部分比较难,如果一开始不理解,可以去看后面的总结部分
4.6.1 基本使用
首先还是先创建一个RecyclerViewTest
项目。
RecyclerView属于新增控件,AndroidX当中,我们只需要在项目的build.gradle中添加RecyclerView库的依赖,就能保证在所有Android系统版本上都可以使用RecyclerView控件了。
打开app/build.gradle
文件,在dependencies
闭包中添加如下内容:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
鼠标移动到刚刚输入的代码上,按住
Alt + Enter
可以切换至最新版本。
接下来的步骤和上面的ListView就差不多了,需要的步骤如下:
① 在activity_main中添加RecyclerView
控件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recyclerView"/>
</LinearLayout>
② 增加基础的Fruit
类和fruit_item
,添加图片素材
③ 增加适配器FruitAdapter
package com.example.Adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.model.Fruit
import com.example.recyclerviewtest.R
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() = fruitList.size
}
适配器里面写了三个方法和一个内部类,其中最后一个方法比较简单,就是设定这个`RecyclerView`的大小。<br />先看内部类:`inner class`就表明这是一个内部类,也就是在类里面定义了一个类。
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
val fruitName: TextView = view.findViewById(R.id.fruitName)
}
这个类的主要作用就是通过传入一个参数View,来 **获取该**`**View**`**中的控件**,比如这里的`fruitImage`和`fruitName`。详细原理不用管,记住怎么用就可以。<br />由于FruitAdapter是继承自RecyclerView.Adapter的,那么就**必须重写** `onCreateViewHolder()`、 `onBindViewHolder() `和` getItemCount() `这 3 个 方法。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fruit_item, parent, false)
return ViewHolder(view)
}
所以这里只需要理解一点,就是加载**fruit_item**
布局,其余的都是固定不变的。
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val fruit = fruitList[position]
holder.fruitImage.setImageResource(fruit.imageId)
holder.fruitName.text = fruit.name
}
这里就是给RecyclerView
的每一个子项根据位置进行赋值即可。
更多内容可以参见:
Android简单使用 RecyclerView
④ 在MainActivity修改代码
package com.example.recyclerviewtest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.Adapter.FruitAdapter
import com.example.model.Fruit
class MainActivity : AppCompatActivity() {
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initFruits() // 初始化水果数据
val layoutManager = LinearLayoutManager(this)
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter(fruitList)
recyclerView.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))
}
}
}
其他部分跟上面差不多不讲,重点注意这两条语句:
val layoutManager = LinearLayoutManager(this)
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = layoutManager
LayoutManager
用 于 指 定 RecyclerView
的 布 局 方 式, 这 里 使 用 的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。
因为ListView只能垂直布局,也就是上下滚动,而
RecyclerView
是可以设置水平布局的。
4.6.2 实现横向滚动和瀑布流布局
上面提到了,RecyclerView
有比较好的拓展性,除了可以垂直布局,还有其他的布局方式,这里主要讲的两个布局。
首先来看
横向滚动
首先简单修改一下fruit_item
的布局:
<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_marginTop="10dp" />
<TextView
android:id="@+id/fruitName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp" />
</LinearLayout>
主要修改的地方是修改了整体宽度以及设置了布局的方向是水平方向。
接下来只要在MainActivity修改成水平布局即可:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initFruits() // 初始化水果数据
val layoutManager = LinearLayoutManager(this)
// 核心语句,设置布局方向是水平的
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter(fruitList)
recyclerView.adapter = adapter
}
效果如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22022942/1648551154314-ddba4cf2-c4e0-41f7-a28e-7dbc79845ee3.png#averageHue=%23d5b428&clientId=ufa7a4816-ae64-4&from=paste&height=691&id=uf289e67e&originHeight=864&originWidth=408&originalType=binary&ratio=1&rotation=0&showTitle=false&size=93882&status=done&style=none&taskId=ub73b42af-0edf-4787-adab-b8e10ff2d78&title=&width=326.4)
瀑布流布局
这个就不细讲了,对照着看一下差别就可以:
<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="5dp">
<ImageView
android:id="@+id/fruitImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp" />
<TextView
android:id="@+id/fruitName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="10dp" />
</LinearLayout>
class MainActivity : AppCompatActivity() {
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initFruits() // 初始化水果数据
val layoutManager = StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL)
recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter(fruitList)
recyclerView.adapter = adapter
}
...
}
`StaggeredGridLayoutManager`的构造函数接收两个参数:
- 第一个参数用于指定布局的列数,传入3表示会把布局分为3列
第二个参数用于指定布局的排列方向,传入
StaggeredGridLayoutManager.VERTICAL
表示会让布局纵向排列。4.6.3 点击事件
只需要再适配器中修改
OnCreateViewHolder
即可:override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fruit_item, parent, false)
// 构建一个viewHolder
val viewHolder = ViewHolder(view)
// 给整个子项设置点击事件
viewHolder.itemView.setOnClickListener {
// 获取绑定子项的位置
val position = viewHolder.bindingAdapterPosition
// 根据位置获取实例
val fruit = fruitList[position]
Toast.makeText(parent.context, "you clicked view ${fruit.name}",
Toast.LENGTH_SHORT).show()
}
// 给水果图片设置点击事件
viewHolder.fruitImage.setOnClickListener {
// 获取绑定子项的位置
val position = viewHolder.bindingAdapterPosition
// 根据位置获取实例
val fruit = fruitList[position]
Toast.makeText(parent.context, "you clicked image ${fruit.name}",
Toast.LENGTH_SHORT).show()
}
return viewHolder
}
4.6.4 有关适配器的总结
第一步:设计子项
也就是一开始设置的
Fruit
类和fruit_item.xml
,比如在上面的横向滚动的案例中设计的:
这个就是一个子项,多个子项组合在一起就是:
第二步:配置适配器
由于
RecyclerView
更常用,所以这里只讲RecycrView
。
适配器首先第一步需要继承所需要继承的类,接着就是重写方法等:
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() = fruitList.size
}
接下来是重点,稍微讲讲个人的见解。
有错误欢迎斧正。
首先是是View
和ViewHolder
的关系,View
在安卓里面就是指的控件,ViewHolder
就是把多个控件组合在一起。作为一整个控件使用,这样就达到了在加载的时候减少view的创建,从而优化加载速度的目的。
最简单的例子就是这里的水果的名字和水果的图片组合在一起。
那很明显如果想要使用ViewHolder
就需要先找到需要被组合到一起的控件,这个控件就可以通过view内部的方法findViewById
找到,这就是内部类ViewHolder
的意义。
既然有了内部类,就需要有三个方法:创建方法,绑定方法,计算一共多少个ViewHolder
的方法(因为大部分数据源都是一个数组或者列表)。
首先是创建方法,根据内部类的定义,inner class ViewHolder(view: View)
,也就是说需要传入一个View类型的实例即可,这个实例就需要在创建方法中获取,这个实例怎么获得呢?
通过下面这个语句:
val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item, parent, false)
LayoutInflater
的作用是动态加载,也就是当滑动到当前子项的时候,才会加载这个子项的相关资源。
其中一些参数的含义上面讲过了,这里再回顾一下:
那么这里的parent
是什么呢?
也就是所有子项需要继承在一块的地方,那答案就呼之欲出了,就是之前在main_activity
中添加的控件:
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recyclerView"/>
所以最终view实例是一个,根据父布局的上下文(.from(parent.context)
)创建的实例,view实例的布局样式的Id是R.layout.fruit_item
,父布局是parent
。
最后只要吧获取的view实例作为参数传入ViewHolder当中,再返回即可:
return ViewHolder(view)
当然只有ViewHolder还不够,因为这只是把相应的控件组合在一起,还需要把相应的资源绑定到这些控件上,这就是第二个绑定方法的作用。
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val fruit = fruitList[position]
holder.fruitImage.setImageResource(fruit.imageId)
holder.fruitName.text = fruit.name
}
这里很明显需要被绑定的资源有两个:
- 图片的id
- 图片的名字(文案)
之前就在说的,因为传入的数据源基本都是一个数组或者列表,所以我们可以通过索引(位置)获取相应的实例,赋值给相应的ViewHolder
。
最后一个重写方法没什么好总结的,就不讲了。
第三步:调用适配器
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initFruits() // 初始化水果数据
val layoutManager = LinearLayoutManager(this)
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter(fruitList)
recyclerView.adapter = adapter
}
首先初始化数据源:
initFruits() // 初始化水果数据
给`recyclerView`设置布局:
val layoutManager = LinearLayoutManager(this)
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = layoutManager
最后设定适配器:
val adapter = FruitAdapter(fruitList)
recyclerView.adapter = adapter
泪目了,总算是写完了,歇一天再接着肝这个笔记