UI编程 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的第6天
一、项目简介
项目是实现极简版抖音,在本次笔记中主要是记录UI的基础知识与简单应用
二、涉及技术&知识点
UI开发
Android可以通过: 编写XML来实现应用程序的界面。 通过ConstraintLayout来拖拽式的放置控件。 以下介绍XML
1.常用控件的使用方法
1.TextView
主要用于在界面上显示一段文本信息。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"//指定唯一标识符-textView
android:layout_width="match_parent"//控件大小和父布局大小一样
android:layout_height="wrap_content"//控件大小刚好可以包住内容
android:gravity="center"//指定文字对齐方式
//可选top,bottom,start,end,center等
//center效果等同于center_vertical|center_horizontal
android:textColor="#00ff00"//文字颜色
android:textSize="24sp"//文字大小
android:text="This is TextView"/>//显示内容
</LinearLayout>
2.Button
在activity_main.xml中加入Button组件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
然后在MainActivity中添加点击逻辑,这里使用函数式API的写法,也可以使用实现接口的方式来进行注册
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {//这里利用了java单抽象方法接口的特性。
// 在此处添加逻辑
}
}
}
3.EditText
就是用来让用户输入信息的横线栏。 同样,先在activity_main.xml中添加控件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here"//EditText栏上的指示性内容,用户开始键入内容后消失。
android:maxLines="2"//指定EditText的最大行数是两行,超过两行是文本会向上滚动,EditText不会再继续拉伸
//避免界面变得非常难看
/>
</LinearLayout>
各个控件可以相互结合
4.ImageView
用于在界面上展示图片。在res目录下新建一个drawable_xxhdpi的木,然后图片就可以放到这个目录下边。(图片通常放在以drawable开头的目录下,并且要带上具体的分辨率。现在大部分手机分辨率为xxhdpi。)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"//宽和高都设定为这个,这养无论什么尺寸的图片都可以完整的显示出来
android:layout_height="wrap_content"
android:src="@drawable/img_1"//设置路径
/>
</LinearLayout>
除此之外,还可以动态的更改ImageView中显示的图片。 使用ImageView中的setImageResource()方法。
5.ProgressBar
用于在界面上显示一个进度条,表示现在正在加载一些数据。
//上边一样
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
如何让进度条在数据加载完后消失? Android控件具有可见属性,可以通过android:visibility进行指定,可选择:
- visible:可见
- invisible:不可见但仍占有原来位置和大小(透明)
- gone:不可见,且不存在 可以使用: progerssBar.visibility进行查看和设置(直接等号赋值即可)。 除此之外,还可以设置水平态的进度条。在上述代码后边添加
style="?android:attr/progressBarStyleHorizontal"//设置为水平态
android:max="100"//设置 长度为100
最后那一行,我不知道咋回事会显示出行数等信息,知道的hxd可以告诉我一声 设置完长度后就可以动态的修改进度条进度了
progresssBar.progress=progressBar.progress+10
每次进度条+10.
6.AlertDialog
AlertDialog可以在当前界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,能够屏蔽其他控件的交互能力,因此AlertDialog一般用于提示一些非常重要的内容或者警告信息。 这个好像是不需要先定义组件在使用,好像直接使用就可以了 下边是一个点击按钮出现对话框的例子
class MainActivity : AppCompatActivity(), View.OnClickListener {
...
override fun onClick(v: View?) {
when (v?.id) {
R.id.button -> {
AlertDialog.Builder(this).apply {//使用apply函数
setTitle("This is Dialog")
setMessage("Something important.")
setCancelable(false)
setPositiveButton("OK") { dialog, which ->
}//这里看不太懂
setNegativeButton("Cancel") { dialog, which ->
}
show()//显示对话框
}
}
}
}
}
2.页面布局
布局是一种可用于放置很多控件的容器,它可以按照一定的规律调整内部控件的位置,从而编写出精美的界面。布局的内部除了放置控件外,也可以放置布局,通过多层布局的嵌套,我们就能够完成一些比较复杂的界面实现。

1.LinearLayout
称为线性布局,会将它所包含的控件在线性方向依次排列 通过在activity_main.xml文件中指定 android:orientation="vertical" 来实现水平/竖直布局,默认是horizontal Note:
- 1.整体布局为vertical时,控件的高度不能指定为“match_parent";为horizontal时,宽度不能指定为”match_parent".否则会导致控件占满整个屏幕
- 2.通过android:gravity指定文字在控件中的对齐方式。android:layout_gravity指定控件在布局中的对齐方式。LinearLayout排列方式为horizontal时,控件只有在垂直方向上的对齐才会起作用(因为水平方向上的长度不确定)。同理为vertical时,水平方向上的才会起作用。
控制控件大小
<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"//这里设置为0dp,是因为出现layout_weight后控件的layout_width就不起作用了。
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>
设布局中所有控件指定的layout_weight相加=n,单个部件所占为Xi,则每个部件在屏幕中所占比例为Xi/n。 如果有两个(及以上)部件,一个layout_weight=1,另一个通过layout_weith设置为文字适配,那么文字适配的那个会占满剩余的所有空间。
2.RelativeLayout
RelativeLayout又称作相对布局,可以通过相对定位的方式让控件出现在布局的任何位置。
相对于父布局定位
android:layout_alignParentLeft:左 android:layout_alignParentTop:上 android:layout_alignParentRight:右 android:layout_alignParentBottom:底 android:layout_centerInParent:中央 可以组合连续使用,例如:
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
相对于控件定位
android:layout_above:可以让一个控件位于另一个控件的上方 android:layout_below表示让一个控件位于另一个控件的下方android:layout_toLeftOf表示让一个控件位于另一个控件的左侧 android:layout_toRightOf表示让一个控件位于另一个控件的右侧。 由于是相对定位,因此需要引入id,且被引用的需要在前边出现(要不然肯定是没办法相对定义的) 使用方法:
android:layout_below="@id/button3"
android:layout_toRightOf="@id/button3"
除上边外,还有一组相对定义: android:layout_alignLeft表示让一个控件的左边缘和另一个控件的左边缘对齐。 android:layout_alignRight表示让一个控件的右边缘和另一个控件的右边缘对齐。 此外,还有android:layout_alignTop和android:layout_alignBottom,道理都是一样的
3.FrameLayout
FrameLayout又称作帧布局,这种布局没有丰富的定位方式,所有的控件都会默认摆放在布局的左上角。 如果两个部件均使用FrameLayout中的默认布局,那么后边的部件会压在前边的部件上边,重叠显示。 除了默认效果意外,还可以使用layout_gravity属性来指定控件在布局中的对齐方式。
3.自定义控件
控件和布局的继承结构:
所用的所有控件都是直接或间接继承自View的,所用的所有布局都是直接或间接继承自ViewGroup的。
View是Android中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在View的基础上又添加了各自特有的功能。
而ViewGroup则是一种特殊的View,它可以包含很多子View和子
ViewGroup,是一个用于放置控件和布局的容器。
1.自定义布局
android:background用于为布局或控件指定一个背景,可以使用颜色或图片来进行填充。 android:layout_margin这个属性,它可以指定控件在上下左右方向上的间距。当然也可以使用android:layout_marginLeft或android:layout_marginTop等属性来单独指定控件在某个方向上的间距。 新建完这个新的布局文件.xml后,在需要使用该布局文件的Activity中加入:
<include layout="@layout/title" />
最后别忘了在MainActivity中将系统自带的标题栏隐藏掉,在onCreate()方法中隐藏
supportActionBar?.hide()//ActionBar可能为空因此使用了?.操作符。
2.自定义控件
引入原因: 引入布局解决了重复写布局代码的问题,如果布局中有一些控件要求能够响应事件,我们还是需要在每个Activity中为这些控件单独编写一次事件注册的代码。 自定义控件不仅引入了布局,还对控件的相应进行了统一化,不必再每一个Activity中在写一遍相应代码。 编写一个自定义控件
class TitleLayout(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
init {
LayoutInflater.from(context).inflate(R.layout.title, this)
titleBack.setOnClickListener {
val activity = context as Activity
activity.finish()
}
titleEdit.setOnClickListener {
Toast.makeText(context, "You clicked Edit button", Toast.LENGTH_SHORT).show()
}
}
}
TitleLayout中接收的context参数实际上是一个Activity的实例,在返回按钮的点击事件里,我们要先将它转换成Activity类型,然后再调用finish()方法销毁当前的Activity。 Kotlin中的类型强制转换使用的关键字是as. 然后在init结构体中需要对标题栏布局进行动态加载。通过LayoutInflater的from()方法可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件。 inflate()方法接收两个参数:第一个参数是要加载的布局文件的id,这里我们传入R.layout.title;第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传入this。 最后在activity_main.xml(布局文件)中修改
<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.ListView
由于手机屏幕空间比较有限,能够一次性在屏幕上显示的内容并不多,当我们的程序中有大量的数据需要展示的时候,就可以借助ListView来实现。ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据会滚动出屏幕。
1.基本应用
首先先到activity_main.xml中声明
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"//均设置成match_parent那么就可以铺满整个屏幕了
android:layout_height="match_parent" />
接着就可以到Activity中进行调用了
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)
listView.adapter = adapter
}
}
集合中的数据没有办法直接传送到Listview中,需要适配器转换,例如ArrayAdapter(泛型) android.R.layout.simple_list_item_1作为ListView子项布局的id,这是一个Android内置的布局文件,里面只有一TextView,可用于简单地显示一段文本。 最后,调用ListView的setAdapter()方法,将构建好的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。
2.定制ListView界面
在上边的基础上:
- 1.先定义一个实体类,作为适配器的传入类型
- 2.创建一个自定义的适配器
- 3.最后在MainActivity中调用就可以了 就是上边中的默认ListView对传入的类,和适配器进行更新。
3.提升ListView的运行效率
1.利用缓存避免重复加载布局
在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。 getView()方法中还有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后进行重用。
2.避免多次调用部件
在适配器中新建一个内部类(关键字inner class)用来存储那些控件信息ViewHolder 调用的时候先到ViewHolder中查看,如果有的话就直接调用,如果没有就放到ViewHolder中。然后调用View的setTag()方 法,将ViewHolder对象存储在View中。当convertView不为null的时候,则调用View的getTag()方法,把ViewHolder重新取出。
4.ListView设置点击事件
在MainActivity中进行修改
listView.setOnItemClickListener { parent, view, position, id ->
val fruit = fruitList[position]
Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
}
}
Lambda表达式在参数列表中声明了4个参数,那么我们如何知道需要声明哪几个参数呢? 按住Ctrl键(Mac系统是command键)点setOnItemClickListener()方法查看它的源码.然后查看待时现的代码。 这里我们必须在Lambda表达式中声明4个参数,但实际上却只用到了 position这一个参数而已。针对这种情况,Kotlin允许我们将没有用到的参数使用下划线来替代(但是参数的相对位置不可以改变) 。
listView.setOnItemClickListener { _, _, position, _ ->
val fruit = fruitList[position]
Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
}
5.RecyclerView
直接用这个就行了,简称晋级版
1.基本用法
与之前不同,RecyclerView属于新增控件,Google将RecyclerView控件定义在了AndroidX当中,我们只需要在项目的build.gradle中添加RecyclerView库的依赖,就能保证在所有 Android系统版本上都可以使用RecyclerView控件了。 app/build.gradle文件中加入
implementation 'androidx.recyclerview:recyclerview:1.0.0'
每次修改完gradle,需要点击一下Sync Now. 在mainActivity中
- 1.建立一个初始化方法,对需要展示的数据进行初始化
- 2.在onCreate()中创建一个LinearLayoutManger对象(用来设置RecyclerView的布局方式,这个为线性布局),并且设置到RecyclerView当中。
- 3.创建构造器的实例,并且将展示数据传入到构造器中
- 4.调用RecyclerView的setAdapter()方法来完成适配器的设置。
2.横向滚动
在1的基础上:
- 1.把要展示的元素变为竖直排列
- 2.在mainActivity中设置
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
这样就可以了。 RecyclerView便于实现多种展示方式的原因: ListView的布局排列是由自身去管理的,而RecyclerView则将这个工作交给了LayoutManager。LayoutManager制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局了。 LayoutManager所提供的排列方式:
- LinearLayoutManager
- GridLayoutManager:用于实现网格布局
- StaggeredGridLayoutManager可以用于实现瀑布流布局
3.瀑布流布局
在1的基础上:
- 1.修改需要展示的元素布局
- 2.在mainActivity中设置
val layoutManager = StaggeredGridLayoutManager(3,
StaggeredGridLayoutManager.VERTICAL)
//第一个参数用于指定布局的列数,传入3表示会把布局分为3列;第二个参数用于指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示会让布局纵向排列
这个线性布局,网格布局和之前的水平布局什么的有什么区别呢? 我觉得应该是之前的布局是局限在一个手机屏幕中,无法实现上下,左右滚动。现在这个布局可以实现滚动等功能。
4.点击事件
不同于ListView的是,RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法,而是需要我们自己给子项具体的View注册点击事件。这相比于ListView来说,实现起来要复杂一些。 ListView在点击事件上的处理并不人性化, setOnItemClickListener()方法注册的是子项的点击事件,但如果我想点击的是子项里具体的某一个按钮,虽然ListView也能做到,但是实现起来就相对比较麻烦了。 方法: 修改适配器中的onCreateViewHolder()方法,为布局和控件注册点击事件。 RecyclerView可以实现子项中任意空间和View的点击事件。
四、总结思考
本次笔记记录了UI编程的基本知识,对于大项目的进展很有帮助。
五、 引用参考
本文主要参考了郭霖著的《第一行代码》。