创建自定义控件
常用控件和布局的继承结构
View是Android中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在View的基础上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器
- 所有控件都是直接或间接继承自View的,
- 所有布局都是直接或间接继承自ViewGroup的
当系统自带的控件并不能满足我们的需求时,可以利用上面的继承结构来创建自定义控件。
自定义布局
- android:background (即可用于为布局,也可用于控件) 指定一个背景,可以使用颜色或图片来进行填充
- android:layout_margin 可以指定控件在上下左右方向上的间距
- android:layout_marginLeft / android:layout_marginTop 等来单独指定控件在某个方向上的间距
自定义一个标题的通用布局title.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"
android:background="@drawable/title_bg">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/titleBack"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/back_bg"
android:text="Back"
android:textColor="#fff"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/titleText"
android:layout_gravity="center"
android:layout_weight="1"
android:text="Title"
android:textColor="#fff"
android:textSize="24dp"
android:gravity="center"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/titleEdit"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/edit_bg"
android:text="Edit"
android:textColor="#fff"
/>
</LinearLayout>
在LinearLayout中分别加入了两个Button和一个TextView,左边的Button可用于返回,右边的Button可用于编辑,中间的TextView则可以显示一段标题文本。
title_bg.png、back_bg.png和edit_bg.png分别用于作为标题栏布局的按钮、返回按钮和编辑按钮的背景。效果如下图所示:
引入布局
- 在activity_main.xml中通过一行include语句引入标题栏布局
</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>
- 在MainActivity中将系统自带的标题栏隐藏掉
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
左图为未去掉自带标题栏时的效果,右图为去掉了自带标题栏的效果。
自定义控件
引入布局的技巧确实解决了重复编写布局代码的问题,但是如果布局中有一些控件要求能够响应事件,我们还是需要在每个Activity中为这些控件单独编写一次事件注册的代码。比如标题栏中的返回按钮,其实不管是在哪一个Activity中,这个按钮的功能都是相同的,即销毁当前Activity。而如果在每一个Activity中都需要重新注册一遍返回按钮的点击事件,无疑会增加很多重复代码,这种情况最好是使用自定义控件的方式来解决。
新建TitleLayout继承自LinearLayout,让它成为我们自定义的标题栏控件,代码如下所示:
- 新建一个类 TitleLayout 继承 LinearLayout 类,并补全构造方法。
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
}
}
重写了TitleLayout中带两个参数的构造函数,在布局中引用TitleLayout会调用这个构造函数。在构造函数中需要对标题栏进行动态加载,这就要借助LayoutInflater来实现。调用LayoutInflater 的静态方法from() 创建一个LayoutInflater对象,然后调用对象的**inflater()**方法就可以动态加载一个布局文件。inflater()接受两个参数,第一个是布局文件的id,另外一个是为加载的布局添加一个父布局,因为我们是想在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>
添加自定义控件和添加普通控件的方式基本是一样的,只不过在添加自定义控件的时候,我们需要指明控件的完整类名,包名在这里是不可以省略的。
- 补全该控件中的各类注册点击事件,修改Layout中的代码
TitleLayout
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
}
}
ListView
ListView简单用法
新建ListViewTest项目
新建main_activity.xml布局文件,在布局中加入ListView控件,为ListView控件指定id,将宽度和高度都设置为,这样ListView就占满了整个布局空间。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listView" />
</LinearLayout>
然后再修改MainActivity, 既然ListView是用于展示大量数据的,那我们就应该先将数据提供好。这些数据可以从网上下载,也可以从数据库中读取,应该视具体的应用程序场景而定。这里我们就简单使用一个data集合来进行测试,里面包含了很多水果的名称。
集合中的数据是无法直接传递给ListView的,我们还需要借助适配器来完成。Android中提供了很多适配器的实现类,其中我认为最好用的就是ArrayAdapter。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。ArrayAdapter有多个构造函数的重载,你应该根据实际情况选择最合适的一种。
我们这里提供的数据都是字符串,因此将ArrayAdapter的泛型指定为String,然后在ArrayAdapter的构造函数中依次传入Activity的实例、ListView子项布局的id,以及数据源。android.R.layout.simple_list_item_1作为ListView子项布局的id,这是一个Android内置的布局文件,里面只有一个TextView,可用于简单地显示一段文本。这样适配器对象就构建好了。最后,还需要调用ListView的 setAdapter() 方法,将构建好的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。
MainActivity
package com.example.listviewtest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import java.sql.Array;
import java.util.ArrayList;
import java.util.List;
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);
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_list_item_1,
data);
ListView listView = findViewById(R.id.listView);
listView.setAdapter(arrayAdapter);
}
}
效果如下图所示,创建了一个滑动的列表,显示数据里的内容。
自定义ListView
- 首先需要准备好一组图片资源,分别对应一种水果,
- 接着定义一个实体类,作为ListView适配器的适配类型。新建Fruit类,类中只有两个字段:name表示水果的名字,imageId表示水果对应图片的资源id:
package com.example.listviewtest;
public class Fruit {
private String name;
private int imageId;
public Fruit(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
- 然后需要 为ListView的子项指定一个我们自定义的布局 ,在layout目录下新建fruit_item.xml,在这个布局中,我们定义了一个ImageView用于显示水果的图片,又定义了一个TextView用于显示水果的名称,并让ImageView和TextView都在垂直方向上居中显示。代码如下所示:
<?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">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/fruit_image"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/fruit_name"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
/>
</LinearLayout>
-
接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类。新建类FruitAdapter,FruitAdapter定义了一个主构造函数,用于将Activity的实例、ListView子项布局的id和数据源传递进来。另外又重写了**getView()**方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。
在getView()方法中,首先使用LayoutInflater来为这个子项加载我们传入的布局。LayoutInflater的**inflate()**方法接收3个参数,前两个参数我们已经知道是什么意思了,第一个是布局文件的id,另外一个是为加载的布局添加一个父布局,第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局。因为一旦View有了父布局之后,它就不能再添加到ListView中了。这是ListView中的标准写法。
接下来调用View的findViewById()方法分别获取到ImageView和TextView的实例,然后通过getItem()方法得到当前项的Fruit实例,并分别调用它们的setImageResource()和setText()方法设置显示的图片和文字,最后将布局返回,这样我们自定义的适配器就完成了。
代码如下所示:
package com.example.listviewtest;
import android.content.Context;
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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int layoutId;
public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
super(context, resource, objects);
layoutId = resource;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position);
View view = LayoutInflater.from(getContext()).inflate(layoutId, parent,false);
ImageView imageView = view.findViewById(R.id.fruit_image);
TextView textView = view.findViewById(R.id.fruit_name);
imageView.setImageResource(fruit.getImageId());
textView.setText(fruit.getName());
return view;
}
}
-
最后修改MainActivity中的代码,可以看到,这里添加了一个initFruits()方法,用于初始化所有的水果数据。Fruit类的构造函数中将水果的名字和对应的图片id传入,然后把创建好的对象添加到水果列表中。另外,我们使将所有的水果数据添加了5遍,这是因为如果只添加一遍的话,数据量还不足以充满整个屏幕。
接着在onCreate()方法中创建了FruitAdapter对象,并将它作为适配器传递给ListView,这样定制ListView界面的任务就完成了。
package com.example.listviewtest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import java.sql.Array;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<Fruit>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruitList();
FruitAdapter fruitAdapter = new FruitAdapter(this, R.layout.fruit_item, fruitList);
ListView listView = findViewById(R.id.listView);
listView.setAdapter(fruitAdapter);
}
public void initFruitList() {
for (int i = 0; i < 5; 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 cherry = new Fruit("cherry", R.drawable.cherry_pic);
fruitList.add(cherry);
Fruit grape = new Fruit("grape", R.drawable.grape_pic);
fruitList.add(grape);
Fruit mango = new Fruit("mango", R.drawable.mango_pic);
fruitList.add(mango);
Fruit pineapple = new Fruit("pineapple", R.drawable.pineapple_pic);
fruitList.add(pineapple);
Fruit strawberry = new Fruit("straberry", R.drawable.strawberry_pic);
fruitList.add(strawberry);
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 orange = new Fruit("orange", R.drawable.orange_pic);
fruitList.add(orange);
}
}
}
现在重新运行程序,效果如下图:
目前定制的界面还很简单,但是只要修改fruit_item.xml中的内容,就可以定制出各种复杂的界面了。
提升ListView的运行效率
ListView这个控件有很多细节可以优化,比如运行效率。目前我们ListView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。
getView()方法中还有一个 convertView 参数,这个参数用于将之前加载好的布局进行缓存,以便之后进行重用,我们可以借助这个参数来进行性能优化。在getView()方法中进行了判断:如果convertView为null,则使用LayoutInflater去加载布局;如果不为null,则直接对convertView进行重用。这样就大大提高了ListView的运行效率,在快速滚动的时候可以表现出更好的性能。修改FruitAdapter中的代码,如下所示:
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view;
if (convertView != null) {
view = convertView;
} else {
view = LayoutInflater.from(getContext()).inflate(layoutId, parent,false);
}
ImageView imageView = view.findViewById(R.id.fruit_image);
TextView textView = view.findViewById(R.id.fruit_name);
Fruit fruit = getItem(position);
imageView.setImageResource(fruit.getImageId());
textView.setText(fruit.getName());
return view;
}
虽然现在已经不会再重复去加载布局,但是每次在getView()方法中仍然会调用View的findViewById()方法来获取一次控件的实例。我们可以借助一个ViewHolder来对这部分性能进行优化,新增一个内部类ViewHolder对控件进行缓存
修改FruitAdapter中的代码,如下所示:
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view;
ViewHolder viewHolder;
if (convertView != null) {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
} else {
view = LayoutInflater.from(getContext()).inflate(layoutId, parent,false);
viewHolder = new ViewHolder();
viewHolder.fruitImage = view.findViewById(R.id.fruit_image);
viewHolder.fruitName = view.findViewById(R.id.fruit_name);
view.setTag(viewHolder);
}
Fruit fruit = getItem(position);
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
我们新增了一个内部类ViewHolder,用于对ImageView和TextView的控件实例进行缓存,当convertView为null的时候,创建一个ViewHolder对象,并将控件的实例存放在ViewHolder里,然后调用View的 setTag() 方法,将ViewHolder对象存储在View中。当convertView不为null的时候,则调用View的 getTag() 方法,把ViewHolder重新取出。这样所有控件的实例都缓存在了ViewHolder里,就没有必要每次都通过findViewById()方法来获取控件实例了。
ListView的点击事件
使用setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中的任何一个子项时,就会回调onItemClick()方法,这里我们可以通过参数 i 判断用户点击的是哪一个子项,然后获取到相应的水果,并通过Toast将水果的名字显示出来。
更改MianActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruitList();
FruitAdapter fruitAdapter = new FruitAdapter(this, R.layout.fruit_item, fruitList);
ListView listView = findViewById(R.id.listView);
listView.setAdapter(fruitAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Fruit fruit = fruitList.get(i);
Toast.makeText(MainActivity.this, "You Clicked "+fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
}
RecyclerView
RecyclerView基本用法
新建RecyclerViewTest项目
-
打开app/build.gradle文件,在dependencies闭包中添加如下内容:
implementation 'androidx.recyclerview:recyclerview:1.2.1'
上述代码就表示将RecyclerView库引入我们的项目当中,其中除了版本号部分可能会变化,其他部分是固定不变的,然后点击Sync Now同步
-
修改activity_main.xml中的代码
先为RecyclerView指定一个id,然后将宽度和高度都设置为match_parent,这样RecyclerView就占满了整个布局的空间。需要注意的是,由于RecyclerView并不是内置在系统SDK当中的,所以需要把完整的包路径写出来
<?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" >
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler_view" />
</LinearLayout>
- 从ListViewTest项目中把图片、Fruit类和fruit_item.xml复制过来
- 为RecyclerView准备一个适配器,新建FruitAdapter类,让这个适配器继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder。其中,ViewHolder是我们在FruitAdapter中定义的一个内部类,代码如下所示:
package com.example.recyclerviewtest;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> fruitList;
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitImage;
TextView fruitName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
fruitImage = itemView.findViewById(R.id.fruit_image);
fruitName = itemView.findViewById((R.id.fruit_name));
}
}
public FruitAdapter(List<Fruit> fruitList) {
this.fruitList = fruitList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fruit_item, parent,false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Fruit fruit = fruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return fruitList.size();
}
}
- 首先定义了一个内部类ViewHolder,它要继承自RecyclerView.ViewHolder。然后ViewHolder的主构造函数中要传入一个View参数,这个参数通常就是RecyclerView子项的最外层布局,那么我们就可以通过findViewById()方
法来获取布局中ImageView和TextView的实例了.- FruitAdapter中也有一个主构造函数,它用于把要展示的数据源传进来,我们后续的操作都将在这个数据源的基础上进行。
- FruitAdapter是继承自RecyclerView.Adapter的,那么就须重写onCreateViewHolder()、onBindViewHolder()和getItemCount()这3个方法。
- onCreateViewHolder() 在该方法中将fruit_item布局加载进来创建了一个View实例,然后把该实例传入ViewHolder的构造函数中创建了一个ViewHolder实例,最后将ViewHolder的实例返回
- onBindViewHolder() 方法用于对RecyclerView子项的数据进行赋值,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可
- getItemCount() 方法用于告诉RecyclerView一共有多少子项,直接返回数据源的长度。
- 修改Activity.xml的代码如下:
使用了一个同样的initFruits()方法,用于初始化所有的水果数据。接着在onCreate()方法中先创建了一个LinearLayoutManager对象,并利用setLayoutManager() 方法将它设置到RecyclerView当中。LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。接下来我们创建了FruitAdapter的实例,并将水果数据传入FruitAdapter的构造函数中,最后调用RecyclerView的setAdapter() 方法来完成适配器设置,这样RecyclerView和数据之间的关联就建立完成了。
package com.example.recyclerviewtest;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<Fruit>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruitList();
RecyclerView recyclerView = findViewById(R.id.recycler_view);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(fruitAdapter);
recyclerView.setLayoutManager(linearLayoutManager);
}
public void initFruitList() {
for (int i = 0; i < 5; 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 cherry = new Fruit("cherry", R.drawable.cherry_pic);
fruitList.add(cherry);
Fruit grape = new Fruit("grape", R.drawable.grape_pic);
fruitList.add(grape);
Fruit mango = new Fruit("mango", R.drawable.mango_pic);
fruitList.add(mango);
Fruit pineapple = new Fruit("pineapple", R.drawable.pineapple_pic);
fruitList.add(pineapple);
Fruit strawberry = new Fruit("straberry", R.drawable.strawberry_pic);
fruitList.add(strawberry);
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 orange = new Fruit("orange", R.drawable.orange_pic);
fruitList.add(orange);
}
}
}
最终效果如下,和ListView几乎一模一样的效果:
实现横向滚动
ListView的扩展性并不好,它只能实现纵向滚动的效果,如果想进行横向滚动的话,ListView就做不到了。RecyclerView能做到
- 对fruit_item布局进行修改
- 要实现横向滚动的话,应该把fruit_item里的元素改成垂直排列,并把宽度设为80 dp。这里将宽度指定为固定值是因为每种水果的文字长度不一致,如果用wrap_content的话,RecyclerView的子项就会有长有短,非常不美观,而如果用match_parent的话,就会导致宽度过长,一个子项占满整个屏幕。
- 将ImageView和TextView都设置成了在布局中水平居中,并且使用layout_marginTop属性让文字和图片之间保持一定距离。
fruit_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:orientation="vertical" >
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/fruit_image"
android:layout_gravity="center_horizontal"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/fruit_name"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
/>
</LinearLayout>
- 修改MainActivity中的代码
MainActivity中只加入了一行代码,调用LinearLayoutManager的setOrientation()方法设置布局的排列方向。默认是纵向排列的,我们传入RecyclerView.HORIZONTAL表示让布局横行排列,这样RecyclerView就可以横向滚动了。
运行代码,效果如下:
实现瀑布流布局
除了LinearLayoutManager之外,RecyclerView还给我们提供了GridLayoutManager和StaggeredGridLayoutManager这两种内置的布局排列方式。GridLayoutManager可以用于实现网格布局,StaggeredGridLayoutManager可以用于实现瀑布流布局。
首先更改fruit_item.xml,
- 将LinearLayout的宽度改成了match_parent,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的,而不是一个固定值。
- 使用了layout_margin属性来让子项之间互留一点间距,这样就不至于所有子项都紧贴在一些
<?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="vertical"
android:layout_margin="5dp">
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:id="@+id/fruit_image"
android:layout_gravity="center_horizontal"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/fruit_name"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
/>
</LinearLayout>
修改MainActivity中的代码,
在onCreate()方法中,我们创建了一个StaggeredGridLayoutManager的实例。StaggeredGridLayoutManager的构造函数接收两个参数:第一个参数用于指定布局的列数,传入3表示会把布局分为3列;第二个参数用于指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示会让布局纵向排列。最后把创建好的实例
设置到RecyclerView当中就可以了.
运行代码效果如图:
RecyclerView的点击事件
不同于ListView的是,RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法,而是需要我们自己给子项具体的View去注册点击事件。
修改ViewHolder,在ViewHolder中添加了fruitView变量来保存外层子项布局itemView,然后在onCreateViewHolder()方法中注册点击事件。这里分别为最外层布局以及IamgeView注册了点击事件。在两个点击事件中,分别通过position来获取相应的Fruit实例,再使用Toast分别弹出两种不同的内容以示区别。