《第一行代码》核心知识点:Android的脸面之UI控件
- 前言
- 三、Android的脸面之UI控件
- 3.1 常用控件介绍
- 3.1.1 TextView
- 3.1.2 Button
- 3.1.3 EditText
- 3.1.4 ImageView
- 3.1.5 ProgressBar
- 3.1.6 AlertDialog
- 3.1.7 ProgressDialog
- 3.2 四种基本布局介绍
- 3.2.1 线性布局
- 3.2.2 相对布局
- 3.2.3 帧布局
- 3.2.4 百分比布局
- 3.2.5 布局使用小技巧
- 3.3 创建自定义控件
- 3.3.1 常用控件与布局的继承结构
- 3.3.2 自定义标题栏控件
- 3.4 玩转ListView控件
- 3.4.1 ListView控件简单案例(每项仅仅展示字符串)
- 3.4.1 定制ListView子项数据的展示样式
- 3.5 玩转RecyclerView控件
- 3.5.1 由于RecyclerView属于新增控件,我们需要引用这个控件
- 3.5.2 设计每个Item的布局
- 3.5.3 设计列表中每个Item所包含要素的类
- 3.5.4 定义适配器
- 3.5.5 RecyclerView使用该适配器
- 3.5.6 RecyclerView点击事件
- 参考书籍:第一行代码
前言
本文讲解Android常用控件(包括ListView、RecyclerView)以及Android中常用几种布局。
三、Android的脸面之UI控件
3.1 常用控件介绍
3.1.1 TextView
文本控件,常用于显示文字。
常用xml属性如下:
- android:id 标识该控件的id
- android:layout_width 控件宽度,常取值"match_parent(与屏幕一样宽)"、wrap_content(与文本内容一样宽)、或者固定值
- android:layout_height 控件宽度,取值与高度类似
- android:text 显示的文本内容
- android:textSize 文本字体大小
- android:textColor 文本颜色
- 等等
3.1.2 Button
按钮控件,一般用于用户点击操作。
常用xml属性与TextView差不多,其中Button控件的英文字母自动转为大写,可以通过android:textAllCaps="false"属性禁止该特性。
3.1.3 EditText
也是文本控件,但是与TextView控件的区别是,EditText控件允许用户在控件中输入和编辑内容。
常用xml属性与TextView差不多,一般编辑框中都会有提示信息,我们可以使用 android:hint=“info” xml属性设置提示信息。我们也可以通过android:maxLines="2"属性设置最大行数。
3.1.4 ImageView
展示图片的控件。
通过android:src 属性设置展示的图片
3.1.5 ProgressBar
用于在界面上显示一个进度条。
常用xml属性:
- android:visibility 可以设置控件是否可见,主要有三种值,visible表示可见,这也是默认的设置;invisible表示不可见,但是它仍然占据屏幕控件,类似控件透明了;gone表示不可见,并且不再占屏幕空间。
- style="@style/Widget.AppCompat.ProgressBar.Horizontal"设置进度条的形状
- android:max=“100” 设置进度条的最大值,我们可以在程序中调用setProgress()方法修改进度值。
3.1.6 AlertDialog
在当前界面弹出一个对话框,这个对话框置于所有界面元素之上,能够屏蔽掉其它控件的交互能力,一般用于展示向用户进行提示的信息。
使用方法示例代码:
AlertDialog.Builder dialog=new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("这是一个提示对话框");
dialog.setMessage("请注意一些信息");
dialog.setCancelable(false);//不能通过back键返回
dialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//点击确定后处理的事情
}
});
dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//点击取消后处理的事情
}
});
dialog.show();
3.1.7 ProgressDialog
ProgressDialog会在界面上弹出一个带有进度条的窗口,一般用于显示进度。
使用方法示例代码如下:
ProgressDialog dialog1=new ProgressDialog(MainActivity.this);
dialog1.setTitle("这是一条进度提示对话框");
dialog1.setMessage("进度提示信息");
dialog1.setCancelable(false);//用户不能通过back键关闭该窗口
dialog1.show();
数据处理完成后,我们可以调用 dialog1.dismiss();方法关闭窗口
3.2 四种基本布局介绍
布局是一种可以放置很多控件或布局的容器。布局就像是控件位置的顶层设计者,它可以按照一定的规律调整内部控件的位置,不然控件随意乱放,界面应该很难整洁精美。布局与控件的关系如图所示。
3.2.1 线性布局
线性布局(LinearLayout)可以将控件按照垂直或者水平方向排列。
- 垂直方向 xml属性:android:orientation=“vertical”
- 水平方向 xml属性:android:orientation=“horizontal”
- android:layout_gravity与android:gravity的区别?
android:layout_gravity是控件在布局中的对齐方式,而android:gravity是文字在控件中的对齐方式。需要注意的是,当LinearLayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上的对齐方式。同样的道理,当LinearLayout的排列方向是vertical时,只有水平方向上的对齐方 - android:layout_weight你真的会了吗?
系统会首先计算LinearLayout下所有控件的android:layout_weight总值,然后每个控件所占大小的比例就是用该控件的android:layout_weight除以刚计算到的总值。例如,LinearLayout下有一个EditText控件,一个Button控件,如果想让EditText占据屏幕宽度的3/5,Button占据屏幕宽度的2/5,只需要将EditText的 layout_weight改成3,Button的 layout_weight改成2就可以了。
3.2.2 相对布局
相对布局(RelativeLayout)可以通过设置各个控件之间的相对位置让控件出现在布局的任何位置。
相对于父布局进行定位的常用xml属性:
- android:layout_alignParentLeft 该控件是否在父布局的左侧
- android:layout_alignParentRight 该控件是否在父布局的右侧
- android:layout_alignParentTop 该控件是否在父布局的上侧
- android:layout_alignParentBottom 该控件是否在父布局的下侧
相对于控件进行定位的常用xml属性:
- android:layout_above 值为另一个控件的id,表明该控件在另一个控件的上方
- android:layout_below 值为另一个控件的id,表明该控件在另一个控件的下方
- android:layout_toLeftOf 值为另一个控件的id,表明该控件在另一个控件的左侧
- android:layout_toRightOf 值为另一个控件的id,表明该控件在另一个控件的右侧
- android:layout_alignLeft 值为另一个控件的id,表明该控件的左边缘另一个控件的左边缘对齐
- android:layout_alignRight 值为另一个控件的id,表明该控件的右边缘另一个控件的右边缘对齐
- android:layout_alignTop 值为另一个控件的id,表明该控件的上边缘另一个控件的上边缘对齐
- android:layout_alignBottom 值为另一个控件的id,表明该控件的下边缘另一个控件的下边缘对齐
3.2.3 帧布局
帧布局(FrameLayout)所有的控件都默认摆放在布局的左上角。
帧布局也可以使用android:layout_gravity指定控件在布局中的对齐方式。
3.2.4 百分比布局
上面介绍的三种布局,我们发现只有LinearLayout支持使用layout_weight实现按比例指定控件大小的功能,其它两种布局都不支持。
因此Android为了使得相对布局和帧布局也支持按比例指定控件大小的功能,引入了PercentRelativeLayout和PercentFrameLayout两个布局,这两个布局是在RelativeLayout和FrameLayout的基础上去掉了使用wrap_content,match_parent等方式指定控件大小,而是允许直接指定控件在布局中所占的百分比。
百分比布局属于新增布局,我们需要在build.grade中添加百分比布局的依赖就可以使用了。
implementation "androidx.percentlayout:percentlayout:1.0.0"
百分比布局,常用xml属性:
- app:layout_widthPercent 值为百分比,通过百分比指定控件宽度
- -app:layout_heightPercent 值为百分比,通过百分比指定控件高度
3.2.5 布局使用小技巧
如果有个布局(比如标题栏)要在各个界面重复使用,我们就可以通过include将这个布局引入到各个界面。
3.3 创建自定义控件
3.3.1 常用控件与布局的继承结构
常用控件与布局的继承结构如下图所示。从图中可以看到所有控件都是直接或者间接的继承自View,所有布局都是直接或间接继承自ViewGroup。同理我们也可以利用上面的继承结构自定义我们的控件。
3.3.2 自定义标题栏控件
- 编写标题栏xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/title_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="Back"
android:layout_gravity="center"
android:textColor="#fff"/>
<TextView
android:id="@+id/title_text"
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="@color/black"
android:textSize="24sp"/>
<Button
android:id="@+id/title_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_gravity="center"
android:text="Edit"
android:textColor="@color/white"/>
</LinearLayout>
- 自定义标题栏类继承自LinearLayout
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//获取并填充布局
View view = LayoutInflater.from(context).inflate(R.layout.title, this);
Button back = view.findViewById(R.id.title_back);
Button edit=view.findViewById(R.id.title_edit);
back.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "返回", Toast.LENGTH_SHORT).show();
}
});
edit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "编辑", Toast.LENGTH_SHORT).show();
}
});
}
}
3.4 玩转ListView控件
ListView数据有众多子项构成,每个子项代表一行数据,用于展示较多的数据,它根据设备屏幕大小展示部分数据,剩余部分的数据通过用户下拉展示。
3.4.1 ListView控件简单案例(每项仅仅展示字符串)
- 定义布局
<?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">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
- 将ListView与待展示的数据进行绑定,完成数据展示
public class MainActivity extends AppCompatActivity {
//1. 设置待展示的数据
private String[] data={"苹果","苹果","苹果","苹果","苹果","苹果","苹果","苹果",
"苹果","苹果","苹果","苹果","苹果","苹果","苹果","苹果",
"苹果","苹果","苹果","苹果","苹果","苹果","苹果","苹果"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//通过适配器,将ListView与待展示的数据进行绑定
//android.R.layout.simple_list_item_1是Android内置简单xml布局,此处用于展示每项数据的布局
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,data);
ListView listView = findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
}
3.4.1 定制ListView子项数据的展示样式
子项数据的展示样式也是通过布局设定的,因此我们只需要编写Item布局样式,然后将该布局样式与ListView关联即可。
核心点:继承已有的适配器如ArrayAdapter,自定义适配器,并重写getView方法
假设我们ListView中每项包括水果名称及对应图片,定制ListViews过程如下:
- 自定义一个包含每项所需属性的实体类
public class Fruit {
private String name;//水果名称
private int imgID;//图片资源ID
public Fruit(String name, int imgID) {
this.name = name;
this.imgID = imgID;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getImgID() {
return imgID;
}
public void setImgID(int imgID) {
this.imgID = imgID;
}
}
- 编写每项数据展示的布局样式
<?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">
<ImageView
android:id="@+id/fruit_img"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/fruit_name"
android:layout_weight="1"
android:gravity="center_vertical"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
- 定义适配器继承自ArrayAdapter,用于将ListView与待展示的每项数据进行绑定
public class FruitAdapter extends ArrayAdapter<Fruit> {
/**
* 展示每项数据的xml布局ID
*/
private int resourceID;
public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
super(context, resource, objects);
this.resourceID=resource;
}
//该方法在每个子项被滚动到屏幕内的时候调用
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//1. 获取该项数据
Fruit fruit = getItem(position);
//2. 获取该项用到的控件
View view=LayoutInflater.from(getContext()).inflate(resourceID, parent, false);
ImageView fruitImg = view.findViewById(R.id.fruit_img);
TextView fruitName = view.findViewById(R.id.fruit_name);
//3. 为每个控件设置相应的数据
fruitImg.setImageResource(fruit.getImgID());
fruitName.setText(fruit.getName());
return view;
}
}
- 使用自定义的ListView
public class MainActivity extends AppCompatActivity {
private String[] data={"苹果","苹果","苹果","苹果","苹果","苹果","苹果","苹果",
"苹果","苹果","苹果","苹果","苹果","苹果","苹果","苹果",
"苹果","苹果","苹果","苹果","苹果","苹果","苹果","苹果"};
private List<Fruit> fruits=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化要展示的数据
initFruits();
//1. 自定义的适配器中传入上下文,子项布局,子项数据集
FruitAdapter fruitAdapter = new FruitAdapter(this, R.layout.fruit_item, fruits);
ListView listView = findViewById(R.id.list_view);
//2. 将适配器添加到ListView中
listView.setAdapter(fruitAdapter);
//3. 定义点击每个子项时的事件
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Fruit fruit = fruits.get(position);
Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
}
});
}
private void initFruits()
{
for (String datum : data) {
fruits.add(new Fruit(datum,R.mipmap.apple));
}
}
}
上面第3步定义的适配器中会重复加载布局,并且重复调用findViewById,这会影响ListView展示数据的效率,下面进阶的适配器类解决了以上问题。
public class FruitAdapter extends ArrayAdapter<Fruit> {
/**
* 展示每项数据的xml布局ID
*/
private int resourceID;
public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
super(context, resource, objects);
this.resourceID=resource;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//获取子项数据
Fruit fruit = getItem(position);
View view;
ViewHolder viewTemp;
//如果子项的布局未获取,则获取布局并临时保存布局中的子view
if (convertView==null)
{
//获取布局
view = LayoutInflater.from(getContext()).inflate(resourceID, parent, false);
//获取布局中的控件并缓存到ViewHolder类中
viewTemp=new ViewHolder();
viewTemp.fruitImg=view.findViewById(R.id.fruit_img);
viewTemp.fruitName=view.findViewById(R.id.fruit_name);
//将缓存的布局中的控件通过setTag保存下来
view.setTag(viewTemp);
}
//如果子项的布局已经获取,则直接拿到布局,不要重复获取
else
{
//直接拿到布局,不要重复获取
view=convertView;
//通过getTag获取保存的布局中的控件
viewTemp= (ViewHolder) view.getTag();
}
ImageView fruitImg = viewTemp.fruitImg;
TextView fruitName = viewTemp.fruitName;
//为子项中的控件设置相应的数据
fruitImg.setImageResource(fruit.getImgID());
fruitName.setText(fruit.getName());
return view;
}
//包含子项所有控件的类,用于缓存对象
class ViewHolder
{
ImageView fruitImg;
TextView fruitName;
}
}
3.5 玩转RecyclerView控件
ListView具有以下缺点:
- 需要开发者使用一些技巧提高它的效率,否则性能非常差
- 扩展性不好,如它只能实现纵向滚动,无法实现横向滚动
针对ListView的确定,Android提供了RecyclerView,它可以说是ListView的增强版,它本身对性能进行了内部优化,并且可以由用户来设定布局,可以方便实现横向滚动的效果等。
3.5.1 由于RecyclerView属于新增控件,我们需要引用这个控件
dependencies {
//...
implementation 'androidx.recyclerview:recyclerview:1.2.1'
//...
}
3.5.2 设计每个Item的布局
如每个Item展示水果图片及名称
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_margin="5dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/fruit_img"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/fruit_name"
android:layout_gravity="left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
3.5.3 设计列表中每个Item所包含要素的类
如每项展示水果图片及图片名
package com.example.recyclerviewtest;
public class Fruit {
private String name;
private int imgID;
public Fruit(String name, int imgID) {
this.name = name;
this.imgID = imgID;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getImgID() {
return imgID;
}
public void setImgID(int imgID) {
this.imgID = imgID;
}
}
3.5.4 定义适配器
首先适配器类继承自 RecyclerView.Adapter,其次在适配器类内定义内部类继承自RecyclerView.ViewHolder(这类似我们对ListView优化时的操作),然后在构造函数中传入数据,最后重写onCreateViewHolder(加载布局),onBindViewHolder(绑定数据),getItemCount(获取数据数量)三个方法。
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
//继承RecyclerView.ViewHolder,为其添加列表属性
static class ViewHolder extends RecyclerView.ViewHolder{
ImageView fruitImage;
TextView fruitName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
fruitImage = itemView.findViewById(R.id.fruit_img);
fruitName = itemView.findViewById(R.id.fruit_name);
}
}
private List<Fruit> fruitList;
//构造函数中传入数据
public FruitAdapter(List<Fruit> fruitList)
{
this.fruitList=fruitList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
//加载Item的布局
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.fruit_item, viewGroup, false);
//临时存储每个Item中的要素
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
//获取指定位置的数据
Fruit fruit = fruitList.get(i);
//设置数据
viewHolder.fruitImage.setImageResource(fruit.getImgID());
viewHolder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return fruitList.size();
}
}
3.5.5 RecyclerView使用该适配器
开发者可以通过setLayoutManager控制展示数据的布局类型及方向
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
RecyclerView recyclerView = findViewById(R.id.recycle_view);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
//通过设置RecyclerView的布局,可以控制滚动的方向
recyclerView.setLayoutManager(linearLayoutManager);
FruitAdapter fruitAdapter = new FruitAdapter(fruits);
//通过适配器绑定数据
recyclerView.setAdapter(fruitAdapter);
}
3.5.6 RecyclerView点击事件
RecyclerView点击事件是在适配器类重写的onCreateViewHolder方法中注册,虽然它写法上比ListView稍复杂,但是它更加灵活,可以很方便的控制子项里的任意要素的点击。对第4步定义的适配器类进行改造,添加点击事件,示例代码如下
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
//继承RecyclerView.ViewHolder,为其添加列表属性
static class ViewHolder extends RecyclerView.ViewHolder{
ImageView fruitImage;
TextView fruitName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
fruitImage = itemView.findViewById(R.id.fruit_img);
fruitName = itemView.findViewById(R.id.fruit_name);
}
}
private List<Fruit> fruitList;
//构造函数中传入数据
public FruitAdapter(List<Fruit> fruitList)
{
this.fruitList=fruitList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
//加载Item的布局
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.fruit_item, viewGroup, false);
//临时存储每个Item中的要素
ViewHolder viewHolder = new ViewHolder(view);
//=============================新增点击事件 start=============================
//为图片添加点击事件
viewHolder.fruitImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取当前点击图片的索引
int position = viewHolder.getAdapterPosition();
//根据索引获取数据
Fruit fruit = fruitList.get(position);
Toast.makeText(v.getContext(), "你点击了"+fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
//=============================新增点击事件 end=============================
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
//获取指定位置的数据
Fruit fruit = fruitList.get(i);
//设置数据
viewHolder.fruitImage.setImageResource(fruit.getImgID());
viewHolder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return fruitList.size();
}
}
参考书籍:第一行代码
链接:https://pan.baidu.com/s/1aXtOQCXL6qzxEFLBlqXs1Q?pwd=n5ag
提取码:n5ag