本文讲解Android常用控件(包括ListView、RecyclerView)以及Android中常用几种布局。
文本控件,常用于显示文字。
常用xml属性如下:
按钮控件,一般用于用户点击操作。
常用xml属性与TextView差不多,其中Button控件的英文字母自动转为大写,可以通过android:textAllCaps="false"属性禁止该特性。
也是文本控件,但是与TextView控件的区别是,EditText控件允许用户在控件中输入和编辑内容。
常用xml属性与TextView差不多,一般编辑框中都会有提示信息,我们可以使用 android:hint=“info” xml属性设置提示信息。我们也可以通过android:maxLines="2"属性设置最大行数。
展示图片的控件。
通过android:src 属性设置展示的图片
用于在界面上显示一个进度条。
常用xml属性:
在当前界面弹出一个对话框,这个对话框置于所有界面元素之上,能够屏蔽掉其它控件的交互能力,一般用于展示向用户进行提示的信息。
使用方法示例代码:
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();
ProgressDialog会在界面上弹出一个带有进度条的窗口,一般用于显示进度。
使用方法示例代码如下:
ProgressDialog dialog1=new ProgressDialog(MainActivity.this);
dialog1.setTitle("这是一条进度提示对话框");
dialog1.setMessage("进度提示信息");
dialog1.setCancelable(false);//用户不能通过back键关闭该窗口
dialog1.show();
数据处理完成后,我们可以调用 dialog1.dismiss();方法关闭窗口
布局是一种可以放置很多控件或布局的容器。布局就像是控件位置的顶层设计者,它可以按照一定的规律调整内部控件的位置,不然控件随意乱放,界面应该很难整洁精美。布局与控件的关系如图所示。
线性布局(LinearLayout)可以将控件按照垂直或者水平方向排列。
相对布局(RelativeLayout)可以通过设置各个控件之间的相对位置让控件出现在布局的任何位置。
相对于父布局进行定位的常用xml属性:
相对于控件进行定位的常用xml属性:
帧布局(FrameLayout)所有的控件都默认摆放在布局的左上角。
帧布局也可以使用android:layout_gravity指定控件在布局中的对齐方式。
上面介绍的三种布局,我们发现只有LinearLayout支持使用layout_weight实现按比例指定控件大小的功能,其它两种布局都不支持。
因此Android为了使得相对布局和帧布局也支持按比例指定控件大小的功能,引入了PercentRelativeLayout和PercentFrameLayout两个布局,这两个布局是在RelativeLayout和FrameLayout的基础上去掉了使用wrap_content,match_parent等方式指定控件大小,而是允许直接指定控件在布局中所占的百分比。
百分比布局属于新增布局,我们需要在build.grade中添加百分比布局的依赖就可以使用了。
implementation "androidx.percentlayout:percentlayout:1.0.0"
百分比布局,常用xml属性:
如果有个布局(比如标题栏)要在各个界面重复使用,我们就可以通过include将这个布局引入到各个界面。
常用控件与布局的继承结构如下图所示。从图中可以看到所有控件都是直接或者间接的继承自View,所有布局都是直接或间接继承自ViewGroup。同理我们也可以利用上面的继承结构自定义我们的控件。
<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>
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();
}
});
}
}
ListView数据有众多子项构成,每个子项代表一行数据,用于展示较多的数据,它根据设备屏幕大小展示部分数据,剩余部分的数据通过用户下拉展示。
<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>
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);
}
}
子项数据的展示样式也是通过布局设定的,因此我们只需要编写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;
}
}
<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>
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;
}
}
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;
}
}
ListView具有以下缺点:
针对ListView的确定,Android提供了RecyclerView,它可以说是ListView的增强版,它本身对性能进行了内部优化,并且可以由用户来设定布局,可以方便实现横向滚动的效果等。
dependencies {
//...
implementation 'androidx.recyclerview:recyclerview:1.2.1'
//...
}
如每个Item展示水果图片及名称
<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>
如每项展示水果图片及图片名
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;
}
}
首先适配器类继承自 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();
}
}
开发者可以通过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);
}
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