Android开发中编写界面的方法主要有如下两种:
可视化编辑器
优点:允许拖放控件来编写布局,同时可以在视图上修改控件的属性
缺点:不利于了解界面背后的原理,屏幕适配性不好
XML代码
这是用的最多的方式。不仅能够实现高度复杂的控件,还能分析和修改当前现有界面。
Android中提供了大量的UI控件,下面我们学习几种比较常用的控件。
主要作用:
在界面上显示一段文本信息。
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="24sp"
android:textColor="#00ff00"
android:text="This is TextView" />
属性说明:
● android:id 给当前控件定义一个唯一标识符。
● android:layout_width和android:layout_height指定控件的宽度和高度,Android中所有的控件都有这两个属性,值有match_parent、fill_parent和wrap_content。
其中:match_parent和fill_parent意义相同,表示让当前控件的大小和父布局的大小一样;wrap_content表示让当前控件的大小能够刚好包含住里面的内容。
● android:gravity 指定文字的对齐方式,可选值有top、bottom、left、right、center等。可以用“|”来同时指定多个值。
● android:textSize 指定文字的大小。Android中字体大小使用sp作为单位。
● android:textColor 指定文字颜色。
● android:text 指定TextView中显示的文本内容。
主要作用:
显示一个按钮,与用户进行交互。
Button可配置的属性和TextView差不多。
使用方法:
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button"
android:textAllCaps="false"/>
属性说明:
● android:textAllCaps :
如果不加这个属性,那么上面设置的文字Button最终显示的却是“BUTTON”,这是因为系统会对Button中的所有英文字母自动进行大写转换,如果不想要这个效果,可以将android:textAllCaps设置为false。
Android中点击事件的四种写法
1.使用匿名内部类实现点击事件
2.让MainActivity实现View.OnClickListener接口
3. 使用内部类实现点击事件
4.通过布局文件中控件的属性
1.内部类实现
private Button button; //匿名对象实现
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"匿名类实现",Toast.LENGTH_LONG).show();
}
});
}
2.让MainActivity实现View.OnClickListener接口
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button button2;//this 实现
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button2 = findViewById(R.id.button3);
button2.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"this实现",Toast.LENGTH_LONG).show();
}
}
3. 使用内部类实现点击事件
private Button button1;//内部类实现
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1 = findViewById(R.id.button2);
button1.setOnClickListener(new Click());
}
class Click implements View.OnClickListener{
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"内部类实现",Toast.LENGTH_LONG).show();
}
}
4.通过布局文件中控件的属性
xml文件
<Button
android:id="@+id/button4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:onClick="Call"//代码中重写Call(View view) 公有接口
android:text="OnClick3">
</Button>
直接进行重写**public void Call(View view)**接口,不用去findViewById和注册监听器
注意:view对象就是Button 本身
public void Call(View view) {
Toast.makeText(getApplicationContext(),"XML", Toast.LENGTH_LONG).show();
}
主要作用:
用于和用户进行交互,用户可以在控件里面输入和编辑内容,并可以在程序中对这些内容进行处理。
使用方法:
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here"
android:maxLines="2"/>
属性说明:
● android:hint 指定一段提示性文本,用户输入后,文本自动消失。
● android:maxLines 指定EditText的最大行数,当输入的内容超过行数后,文本就会向上滚动。
获取输入框文本:
EditText editText = (EditText) findViewById(R.id.edit_text);
String inputText = editText.getText().toString();
主要作用:
用于在界面上展示图片。图片根据分辨率的不同放在不同的drawable或mipmap目录下。
使用方法:
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"/>
属性说明:
● android:src 给ImageView指定一张图片。
代码中指定图片:
ImageView imageView = (ImageView) findViewById(R.id.image_view);
imageView.setImageResource(R.mipmap.ic_launcher_round);
主要作用:
在界面上显示一个进度条
使用方法:
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible"
style="?android:attr/progressBarStyleHorizontal" //设置成水平方向
android:max="100"/> //设置成100
属性说明:
ProgressBar默认为原型,需要设置style才为水平
● android:visibility 控制控件是否可见。 visible表示控件可见,默认值。invisible表示控件不可见,但仍占据原来的位置,相当于控件透明。gone表示控件不可见,且不占据原来的位置。
● style 指定控件的形状。
● android:max 给进度条设置一个最大值。
代码中控制控件是否可见
控件可见三种方式:
View.VISIBLE;
View.INVISIBLE;
View.GONE
注意: View.VISIBLE; View.INVISIBLE; 还是占有原来控件的位置,只有设置成了View.GONE,才在布局文件中不占有控件的位置,并且不可见
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
if (progressBar.getVisibility() == View.GONE) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
}
主要作用:
用于提示一些非常重要的内容或者警告信息。比如为了防止用户误删重要内容,在删除前弹出一个确认对话框。对话空可以屏蔽用户与其他控件的交互能力。
简单用法:
https://www.cnblogs.com/shen-hua/p/5709663.html
例如:点击button弹出对话框,进行选择
AlertDialog.Builder diaglog = new AlertDialog.Builder(MainActivity.this);
diaglog.setTitle("请做出选择")
.setMessage("1+1= ?")
.setPositiveButton("2", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getApplicationContext(),"恭喜你答对了",Toast.LENGTH_LONG).show();
}
})
.setNegativeButton("不知道", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getApplicationContext(),"???",Toast.LENGTH_LONG).show();
}
})
.setNeutralButton("3", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getApplicationContext(),"错误",Toast.LENGTH_LONG).show();
}
});
diaglog.show();
使用说明:
● AlertDialog.Builder 创建一个AlertDialog实例
● setTitle 设置标题
● setMessage 设置内容
● setCancelable 是否用Back键关闭对话框
● setPositiveButton() 设置确认按钮的点击事件
● setNegativeButton() 设置取消按钮的点击事件
● show() 将dialog显示出来
主要作用:
跟AlertDialog类似,都能够屏蔽掉其他控件的交互能力。只是ProgressDialog会在对话框中显示一个进度条,一般用于表示当前操作比较耗时,让用户耐心地等待。
使用方法:
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setTitle("This is ProgressDialog");
progressDialog.setMessage("Loding...");
progressDialog.setCancelable(true);
progressDialog.show();
使用说明:
● new ProgressDialog 创建一个ProgressDialog对象
● setTitle 设置标题
● setMessage 设置内容
● setCancelable 是否用Back键关闭对话框
● show() 将dialog显示出来
备注:
如果在setCancelable()中传入了false,表示ProgressDialog不能通过Back键取消掉,这时需要在代码中做好控制,当数据加载完成后必须调用ProgressDialog的dismiss()方法来关闭对话框,否则ProgressDialog将会一直存在。
布局是一种可以放置多个控件或者布局的容器,可以按照一定的规律调整内部控件或布局的位置,从而来写出精美的界面。
LinearLayout称作线性布局,该布局会将它所包含的控件在线性方向(水平或垂直方向)上依次排列。
基本使用:
<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"/>
<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>
属性说明:
● android:orientation 指定排列方向,可以是vertical或horizontal。默认的排列方向是horizontal
● android:layout_weight 使用比例的方式来指定控件的大小
备注:
● LinearLayout的排列方向是horizontal,内部的控件宽度就不能指定为match_parent,因为这样的话,单独的一个控件就会将整个水平方向占满,其他的控件就没有可放的位置。
● LinearLayout的排列方向是vertical,内部控件的高度就不能指定为match_parent。
● android:layout_gravity用于指定控件在布局中的对齐方式,而android:gravity用于指定文字在控件中的对齐方式。
● LinearLayout的排列方式是horizontal时,只有在垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因此无法确定该方向上的对齐方式。同理,当LinearLayout的排列方向是vertical时,只有水平方向上的对齐方式才会生效。
权重说明:
1.如果按照水平方向布局,控件比例为1:1 则控件的宽为0dp
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
</Button>
<Button
android:id="@+id/button1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
</Button>
</LinearLayout>
2.如果按照垂直平方向布局,控件比例为1:1 则控件的高为0dp
3.水平布局,左边按照控件实际大小进行排列,右边控件按照权重1方式排列
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Button>
<Button
android:id="@+id/button1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
</Button>
</LinearLayout>
RelativeLayout称作相对布局。通过相对定位的方式让控件出现在布局的任何位置。
属性说明:
主要用在控件相对于父布局进行定位的属性
● android:layout_alignParentLeft 与父布局的左边对齐
● android:layout_alignParentRight 与父布局的右边对齐
● android:layout_alignParentTop 与父布局的上边对齐
● android:layout_alignParentBottom 与父布局的下边对齐
● android:layout_centerInParent 位于父布局中心
控件也可以相对于其他控件进行定位
● android:layout_above 位于相对控件的上方
● android:layout_below 位于相对控件的下方
● android:layout_toLeftOf 位于相对控件的左侧
● android:layout_toRightOf 位于相对控件的右侧
注意: 当一个控件去引用另一个控件id的时候,该控件一定要定义在引用控件的后面,不然会出现找不到id的情况。
基本使用:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<Button
android:id="@+id/top_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true">
</Button>
<Button
android:id="@+id/top_right"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true">
</Button>
<Button
android:id="@+id/center"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_centerInParent="true">
</Button>
<Button
android:id="@+id/bottom_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true">
</Button>
<Button
android:id="@+id/bottom_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true">
</Button>
<Button
android:id="@+id/center_top_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/center"
android:layout_toLeftOf="@+id/center">
</Button>
<Button
android:id="@+id/center_top_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/center"
android:layout_toRightOf="@id/center">
</Button>
<Button
android:id="@+id/center_bottom_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/center"
android:layout_toLeftOf="@+id/center">
</Button>
<Button
android:id="@+id/center_bottom_right"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_below="@+id/center"
android:layout_toRightOf="@+id/center">
</Button>
</RelativeLayout>
FrameLayout称作帧布局。它里面所有的控件都会默认摆放在布局的左上角。
基本使用:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Button>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="100dp"></Button>
</FrameLayout>
属性说明:
● android:layout_gravity 指定控件在布局中的对齐方式。
下面是控件和布局的继承结构:
从上面的图中可得出如下结论:
● 所有控件都是直接或间接继承自View
● 所有的布局都是直接或间接继承自ViewGroup
● View是Android中最基本的一种UI控件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件
● ViewGroup是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个
例如:创建一个标题栏布局,引入到主布局中
创建标题栏布局:
<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/title_back"
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/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="#fff"
android:textSize="24sp"/>
<Button
android:id="@+id/title_edit"
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>
属性说明:
● android:background 用于给控件或布局指定一个背景
● android:layout_margin 指定控件在上下左右方向上偏移的距离,单位为dp
使用标题栏:
<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>
通过使用 include语句将标题栏布局引入进来。
隐藏系统自带的标题栏
在显示我们通过引入布局写的标题栏前,我们需要隐藏掉系统自带的标题栏:
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
通过调用getSupportActionBar()获得ActionBar的实例,然后调用ActionBar的hide()方法将标题栏隐藏。
注意:getSupportActionBar() 要在setContentView前面调用。而且getSupportActionBar() 只适用于AppCompatActivity
引入布局很好的解决了重复编写布局代码的问题,但是如果布局中有一些控件要求能够响应事件,那么我们就需要在每个活动中单独编写一次事件注册的代码。比如标题栏中的返回按钮,其实不管在哪个活动中,这个按钮的功能都是销毁当前活动。如果每一个活动中都需要重新注册一遍返回按钮的点击事件,这样就会增加很多重复代码,此时我们就应使用自定义控件的方式来解决。
自定义控件
<?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:orientation="horizontal">
<Button
android:id="@+id/button"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
</Button>
<EditText
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="你好请输入"
android:layout_weight="1">
</EditText>
<Button
android:id="@+id/button1"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
</Button>
</LinearLayout>
控件代码:
public class Title extends LinearLayout implements View.OnClickListener{
private Button button;
private Button button1;
public Title(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
View view = LayoutInflater.from(context).inflate(R.layout.layout,this);
button = view.findViewById(R.id.button);
button1 = view.findViewById(R.id.button1);
button.setOnClickListener(this);
button1.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
Toast.makeText(getContext(),"button1",Toast.LENGTH_LONG).show();
break;
case R.id.button1:
Toast.makeText(getContext(),"button2",Toast.LENGTH_SHORT).show();
break;
}
}
}
Layout inflation是在android系统中使用的术语,当XML布局资源被解析并转换成View对象时会用到。
● 重写了LinearLayout带有两个参数的构造函数,在布局中引入TitleLayout控件就会调用这个构造函数
● 使用LayoutInflater对标题栏布局进行动态加载,LayoutInflater的from()方法构建出一个LayoutInflater对象,然后调用inflate()方法动态加载一个布局。
inflate()方法第一个参数是要加载布局文件的id,第二个参数是给加载好的布局添加一个父布局。
● 通过findViewById()找到布局文件中的控件,分别为各个按钮注册点击事件。
引用 LinearLayout 用法: https://www.jianshu.com/p/14610c347f09
MainActivity_layout引入控件布局:
<?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"
android:orientation="vertical"
tools:context=".MainActivity">
<com.example.relative.Title
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.relative.Title>
</LinearLayout>
MainActivity代码:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
setContentView(R.layout.activity_main);
}
}
ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕。
在activity_main.xml中的使用:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
在MainActivity中的代码:
public class MainActivity extends AppCompatActivity {
private String[] data = {"Apple","Banana","Orange","Watermelon","Pear",
"Grape","Pineapple","Strawberry","Cherry","Mango",
"Apple","Banana","Orange","Watermelon","Pear",
"Grape","Pineapple","Strawberry","Cherry","Mango",};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView) findViewById(R.id.list_view);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_list_item_1,data);
listView.setAdapter(adapter);
}
}
● data 是ListView中要显示的数据,可以自定义,也可以从后台获取
● ArrayAdapter 数据无法直接传给ListView,需要借助适配器来完成。而ArrayAdapter是Android中提供的适配器实现类,可以通过泛型来指定要适配的数据类型。
● ArrayAdapter 构造函数参数一:当前上下文;参数二:ListView子项布局的id;参数三:要适配的数据。
● android.R.layout.simple_list_item_1 是一个Android内置的布局,里面只有一个TextView,可用于简单地显示一段文本。
● listView.setAdapter() 通过该方法将构建好的适配器对象传递进去,这样ListView和数据之间就建立了关联。
ListView的子项布局正常情况下不止一段文本,下面我们就在上面每个子项布局的水果旁边加上一个图样。
定义实体类
public class Fruit {
private String Name;
private int Id;
public Fruit(String _Name,int _Id) {
Name = _Name;
Id = _Id;
}
public String getName() {return this.Name;}
public int getId(){return this.Id;}
}
其中:name表示水果的名字,imageId表示水果对应图片的资源id。
自定义ListView子项布局
<?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"
android:orientation="horizontal">
<ImageView
android:id="@+id/fruitimage"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
</ImageView>
<TextView
android:id="@+id/fruittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
</LinearLayout>
自定义适配器
public class FruitAdpter extends ArrayAdapter<Fruit> {
private int resource;
public FruitAdpter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
super(context, resource, objects);
resource =resource;
}
public
View getView(int position, View convertView,ViewGroup parent) {
Fruit fruit = getItem(position);
View view=null;
ViewHodle viewHodle = null;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.layout,parent,false);
viewHodle = new ViewHodle();
viewHodle.imageView = view.findViewById(R.id.fruitimage);
viewHodle.textView = view.findViewById(R.id.fruittext);
view.setTag(viewHodle);
}else {
view = convertView;
viewHodle = (ViewHodle) view.getTag();
}
viewHodle.imageView.setImageResource(fruit.getId());
viewHodle.textView.setText(fruit.getName());
return view;
}
private class ViewHodle {
public ImageView imageView;
public TextView textView;
}
}
● FruitAdapter 重写父类个构造函数,将上下文、ListView子项布局id和数据传递进来
● 重写getView()方法。每个子项滚动到屏幕内时都会调用这个方法。
● getItem()方法得到当前项的Fruit实例
● LayoutInflater 为子项加载我们传入的布局
● inflate()方法的参数三要传入false,表示只让我们在父布局中声明的layout生效,但不会为这个View添加父布局,因为View一旦有了父布局,就不能将它添加到ListView中
● 调用View的findViewById()方法分别获取到ImageView和TextView的实例,然后分别调用setImageResource()和setText()来设置显示图片和文字
在MainActivity中的代码:
public class MainActivity extends AppCompatActivity {
private ListView listView = null;
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
FruitAdpter fruitAdpter = new FruitAdpter(getApplicationContext(),R.layout.layout,fruitList);
listView = findViewById(R.id.listview);
listView.setAdapter(fruitAdpter);
}
public void initData() {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
fruitList.add(apple);
Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
fruitList.add(banana);
Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
fruitList.add(orange);
Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
fruitList.add(watermelon);
Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
fruitList.add(pear);
Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
fruitList.add(grape);
Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
fruitList.add(pineapple);
Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
fruitList.add(strawberry);
Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
fruitList.add(cherry);
Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
fruitList.add(mango);
}
}
}
<?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/listview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
FruitAdapter的getView方法中,每次都将布局重新加载一遍 FruitAdapter的getView方法中,每次都会调用View的findViewById()方法去获取控件的实例
public class FruitAdpter extends ArrayAdapter<Fruit> {
private int resource;
public FruitAdpter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
super(context, resource, objects);
resource =resource;
}
public
View getView(int position, View convertView,ViewGroup parent) {
Fruit fruit = getItem(position);
View view=null;
ViewHodle viewHodle = null;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.layout,parent,false);
viewHodle = new ViewHodle();
viewHodle.imageView = view.findViewById(R.id.fruitimage);
viewHodle.textView = view.findViewById(R.id.fruittext);
view.setTag(viewHodle);
}else {
view = convertView;
viewHodle = (ViewHodle) view.getTag();
}
viewHodle.imageView.setImageResource(fruit.getId());
viewHodle.textView.setText(fruit.getName());
return view;
}
private class ViewHodle {
public ImageView imageView;
public TextView textView;
}
}
● 在getView()方法中,判断convertView是否为null,如果为null,则使用LayoutInflater去加载布局;如果不为null,则直接对convertView重用
●通过setTag getTag 重复利用ViewHodle
● 新建一个内部类ViewHolder,然后分别使用setTag()和getTag()方法去存储和取出
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
ListView listView = (ListView) findViewById(R.id.list_view);
FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, mFruitList);
listView.setAdapter(adapter);
//注册监听器
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Fruit fruit = mFruitList.get(position);
Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
}
当用户点击了ListView中的任何一个子项时,就会回调onItemClick()方法,这个方法中我们可以通过position参数来判断出用户点击的是哪一个子项。
ListView只能实现数据纵向滚动效果,无法实现横向滚动。因此Android提供了一个增强版的ListView——RecyclerView。
RecyclerView属于新增的控件,为了让RecyclerView在所有的版本上都能适用,Android团队采用同样的方式,将RecyclerView定义在了support库中.
使用步骤:
1.build.gradle添加依赖:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
2.XML引入
由于RecyclerView并不是内置在系统SDK当中的,所以需要把完整的包路径写出来。
main_activity.xml
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
layout.xml
<?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">
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ImageView>
<TextView
android:id="@+id/text"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
</TextView>
</LinearLayout>
3.创建适配器
步骤:
1.创建适配器类继承自RecyclerView.Adapter,泛型传入RecyclerView.ViewHolder类。
2.创建内部类即RecyclerView.ViewHolder类的子类,并初始化item的控件。
3.重写RecyclerView.Adapter类的相关方法。
必须重写onCreateViewHolder(), onBindViewHolder( )和 getItemCount()三个方法
● onCreateViewHolder()用于创建ViewHolder实例,并把加载的布局传入到构造函数去,再把ViewHolder实例返回。
● onBindViewHolder()则是用于对子项的数据进行赋值,会在每个子项被滚动到屏幕内时执行。position得到当前项的Fruit实例。
● getItemCount()返回RecyclerView的子项数目。
public class Fruit {
private String Name;
private int Id;
public Fruit(String _Name,int _Id) {
Name = _Name;
Id = _Id;
}
public String getName(){return this.Name;}
public int getId(){return this.Id;}
}
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHodeler> {
private static final String TAG = "fruit";
private List<Fruit> fruitList;
static class ViewHodeler extends RecyclerView.ViewHolder {
ImageView imageView;
TextView textView;
public ViewHodeler(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image);
textView = itemView.findViewById(R.id.text);
}
}
public FruitAdapter(List<Fruit> fruitList1) {
fruitList = fruitList1;
}
@NonNull
@Override
public ViewHodeler onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
Log.d(TAG,"onCreateViewHolder");
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout,parent,false);
return new ViewHodeler(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHodeler holder, int position) {
Log.d(TAG,"onBindViewHolder: "+position);
Fruit fruit = fruitList.get(position);
holder.imageView.setImageResource(fruit.getId());
holder.textView.setText(fruit.getName());
}
@Override
public int getItemCount() {
Log.d(TAG,"getItemCount: "+ fruitList.size());
return fruitList.size();
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
private FruitAdapter fruitAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
setContentView(R.layout.activity_main);
initData();
fruitAdapter = new FruitAdapter(fruitList);
RecyclerView recyclerView = findViewById(R.id.recyclerview);
//默认为垂直方向
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(fruitAdapter);
}
public void initData() {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
fruitList.add(apple);
Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
fruitList.add(banana);
Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
fruitList.add(orange);
Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
fruitList.add(watermelon);
Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
fruitList.add(pear);
Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
fruitList.add(grape);
Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
fruitList.add(pineapple);
Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
fruitList.add(strawberry);
Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
fruitList.add(cherry);
Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
fruitList.add(mango);
}
}
}
LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。
效果:
因为要横向滚动,所以需要对上面的layout.xml文件进行修改:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/image"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center_horizontal">
</ImageView>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp">
</TextView>
</LinearLayout>
MainActivity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
setContentView(R.layout.activity_main);
initData();
FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
RecyclerView recyclerView = findViewById(R.id.recycler_h);
//设置方向水平 LinearLayoutManager.HORIZONTAL
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setAdapter(fruitAdapter);
recyclerView.setLayoutManager(layoutManager);
}
RecyclerView的布局排列是由LayoutManager去控制,LayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局。
除了LinearLayoutManager之外,RecyclerView还提供了GridLayoutManager和StaggerdGridLayoutManager这两种内置的布局排列方式。
● GridLayoutManager可以用于实现网格布局,
● StaggerdGridLayoutManager可以用于实现瀑布流布局。
1. 修改fruit_item.xml文件
因为要实现瀑布流效果,所以需要对上面的fruit_item.xml文件进行修改:
<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/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="10dp"/>
</LinearLayout>
2. 使用StaggerdGridLayoutManager
public class MainActivity extends AppCompatActivity {
private List<Fruit> mFruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(mFruitList);
recyclerView.setAdapter(adapter);
}
private void initFruits() {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit(getRandomLengthName("Apple"), R.drawable.apple_pic);
mFruitList.add(apple);
Fruit banana = new Fruit(getRandomLengthName("Banana"), R.drawable.banana_pic);
mFruitList.add(banana);
Fruit orange = new Fruit(getRandomLengthName("Orange"), R.drawable.orange_pic);
mFruitList.add(orange);
Fruit watermelon = new Fruit(getRandomLengthName("Watermelon"), R.drawable.watermelon_pic);
mFruitList.add(watermelon);
Fruit pear = new Fruit(getRandomLengthName("Pear"), R.drawable.pear_pic);
mFruitList.add(pear);
Fruit grape = new Fruit(getRandomLengthName("Grape"), R.drawable.grape_pic);
mFruitList.add(grape);
Fruit pineapple = new Fruit(getRandomLengthName("Pineapple"), R.drawable.pineapple_pic);
mFruitList.add(pineapple);
Fruit strawberry = new Fruit(getRandomLengthName("Strawberry"), R.drawable.strawberry_pic);
mFruitList.add(strawberry);
Fruit cherry = new Fruit(getRandomLengthName("Cherry"), R.drawable.cherry_pic);
mFruitList.add(cherry);
Fruit mango = new Fruit(getRandomLengthName("Mango"), R.drawable.mango_pic);
mFruitList.add(mango);
}
}
private String getRandomLengthName(String name) {
Random random = new Random();
int length = random.nextInt(20) + 1;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(name);
}
return builder.toString();
}
}
StaggeredGridLayoutManager的构造函数接受两个参数,参数一:用于指定布局的列数,传入3表示会把布局分为3列;参数二用于指定布局的排列方向
RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法,而是需要我们自己给子项具体的View去注册点击事件。
其实ListView的setOnItemClickListener()方法注册的是子项的点击事件,如果想点击子项里面具体的某一个按钮,ListView实现起来就有点复杂。为此,RecyclerView干脆直接摒弃了子项点击事件的监听器,所有的点击事件都由具体的View去注册。
注册监听
RecyclerView的点击事件是在Adapter中实现的,修改FruitAdapter文件:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> mFruitList;
public FruitAdapter(List<Fruit> fruitList) {
mFruitList = fruitList;
}
//创建ViewHolder实例
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
final ViewHolder holder = new ViewHolder(view);
//给子项最外层布局注册点击事件
holder.fruitView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Toast.makeText(v.getContext(), "you clicked view " + fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
//给子项中的ImageView注册点击事件
holder.fruitImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Toast.makeText(v.getContext(), "you clicked image " + fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
return holder;
}
//对RecyclerView子项的数据进行赋值
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
//RecyclerView有多少子项
@Override
public int getItemCount() {
return mFruitList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitImage;
TextView fruitName;
View fruitView; //子项最外层布局实例
public ViewHolder(View itemView) {
super(itemView);
fruitView = itemView;
fruitImage = ((ImageView) itemView.findViewById(R.id.fruit_image));
fruitName = ((TextView) itemView.findViewById(R.id.fruit_name));
}
}
}
实现步骤:
● 在ViewHolder中添加fruitView变量保存子项最外层布局的实例
● 在onCreateViewHolder中注册相关控件的点击事件