先附上我的GitHub项目地址:
https://github.com/Skymqq/RecyclerViewTest.git
我们已经知道,ListView的拓展性不好,它只能实现纵向的滚动的效果,如果想进行横向滚动的效果,ListView就做不到了,毫无疑问,RecyclerView是可以做到的。
fruit_item.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/iv_name"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp" />
</LinearLayout>
可以看到,我们将LinearLayout改成垂直方向排列,并且把宽度设置为100dp。这里将宽度指定为固定值是因为每种水果的文字长度不一致,如果用wrap_content,RecyclerView的子项就会有长有短,非常不美观;而如果使用match_parent,就会导致宽度过长,一个子项占满了整个屏幕。
然后将ImageView和TextView都设置成了在布局中水平居中,并且使用layout_marginTop属性让文字和图片之间保持一些距离。
MainActivity.java代码:
package com.example.administrator.activitydemo;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private String[] data = {"Apple", "Banana", "Orange", "Watermelon",
"Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango",
"Apple", "Banana", "Orange", "Watermelon",
"Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango"
};
private int[] res = {R.drawable.apple, R.drawable.banana, R.drawable.orange, R.drawable.watermelon,
R.drawable.pear, R.drawable.grape, R.drawable.pineapple, R.drawable.strawberry, R.drawable.cherry, R.drawable.mango,
R.drawable.apple, R.drawable.banana, R.drawable.orange, R.drawable.watermelon,
R.drawable.pear, R.drawable.grape, R.drawable.pineapple, R.drawable.strawberry, R.drawable.cherry, R.drawable.mango};
private List<Fruit> fruitList;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();//初始化UI控件
initData();//初始化数据
}
private void initView() {
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
}
private void initData() {
initFruits();//初始化水果数据
LinearLayoutManager layoutManager = new LinearLayoutManager(MainActivity.this);//实例化布局管理器
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);//为布局管理器设定线性方向
recyclerView.setLayoutManager(layoutManager);//为RecyclerView控件设置布局管理器
FruitAdapter adapter = new FruitAdapter(fruitList);//实例化适配器
recyclerView.setAdapter(adapter);//为RecyclerView控件设置适配器
}
private void initFruits() {
fruitList = new ArrayList<>();
for (int i = 0; i < data.length; i++) {
Fruit fruit = new Fruit(data[i], res[i]);
fruitList.add(fruit);
}
}
}
效果图:
MainActivity.java中加入了一行代码,调用LinearLayoutManager的setOrientation()方法来设置布局的排列方向,默认是纵向排列的,我们传入的LinearLayoutManager.HORIZONTAL表示让布局横行排列,这样RecyclerView就可以横向滚动了。
为什么ListView很难或者根本无法实现的效果在RecyclerView上这么轻松就能实现了呢?
这主要得益于RecyclerView出色的设计。ListView的布局排列是由自身去管理的,而RecyclerView则将这个工作交给了LayoutManager,LayoutManager中制定了一套可拓展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局了。
除了LinearLayoutManager之外,RecyclerView还给我们提供了GridLayoutManager和StaggeredGridLayoutManager这两种内置的布局排列方式。GridLayoutManager可以用于实现网络布局,StaggeredGridLayoutManager可以用于实现瀑布流布局。这里我们来实现一下效果更加酷炫的瀑布流布局。
fruit_item.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:layout_margin="5dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_name"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="10dp" />
</LinearLayout>
这里做了几处小的调整,首先将LinearLayout的宽度由100dp改成了match_parent,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的,而不是一个固定值。另外我们使用了layout_margin属性来让子项之间互留一点间距,这样就不至于所有子项都紧贴在一起。还有就是将TextView的对其属性改成了居左对齐,因为待会我们会将文字的长度变长,如果还是居中就会很怪。
MainActivity.java代码:
package com.example.administrator.activitydemo;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private String[] data = {"Apple", "Banana", "Orange", "Watermelon",
"Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango",
"Apple", "Banana", "Orange", "Watermelon",
"Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango"
};
private int[] res = {R.drawable.apple, R.drawable.banana, R.drawable.orange, R.drawable.watermelon,
R.drawable.pear, R.drawable.grape, R.drawable.pineapple, R.drawable.strawberry, R.drawable.cherry, R.drawable.mango,
R.drawable.apple, R.drawable.banana, R.drawable.orange, R.drawable.watermelon,
R.drawable.pear, R.drawable.grape, R.drawable.pineapple, R.drawable.strawberry, R.drawable.cherry, R.drawable.mango};
private List<Fruit> fruitList;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();//初始化UI控件
initData();//初始化数据
}
private void initView() {
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
}
private void initData() {
initFruits();//初始化水果数据
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);//3列,垂直
recyclerView.setLayoutManager(layoutManager);//为RecyclerView控件设置布局管理器
FruitAdapter adapter = new FruitAdapter(fruitList);//实例化适配器
recyclerView.setAdapter(adapter);//为RecyclerView控件设置适配器
}
private void initFruits() {
fruitList = new ArrayList<>();
for (int i = 0; i < data.length; i++) {
Fruit fruit = new Fruit(getRandomLengthName(data[i]), res[i]);
fruitList.add(fruit);
}
}
private String getRandomLengthName(String name) {
Random random = new Random();
int length = random.nextInt(20) + 1;//取0~20之间的一个随机数
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(name);//字符串拼接
}
return builder.toString();
}
}
效果图:
这里的水果的名字长短都是随机生成的,所以当我们再次运行一次程序,效果图又会不一样的。
RecyclerView的点击事件:
和ListView一样,RecyclerView也必须要能响应点击事件才可以,不然的话就没什么实际用途了。不过不同于ListView的是,RecyclerView并没有提供类似于setOnClickListener()这样的注册监听方法,而是需要我们自己给子项具体的View去注册点击事件,相比于ListView来说,实现起来要复杂一些。
那么你可能就有疑问了,为什么RecyclerView在各个方面的设计都要优于ListView,偏偏在点击事件上却没有处理得非常好呢?其实不是这样的,ListView在点击事件上的处理并不人性化,setOnClickListener()方法注册的是子项的点击事件,但如果我想点击的是子项里具体的某一个按钮呢?虽然ListView也是能够做到的,但是实现起来就比较麻烦了。为此,RecyclerView干脆直接摒弃了子项点击事件的监听器,所有的点击事件都由具体的View去注册,就再没有这个困扰了。
下面我们来具体学习一下如何在RecyclerView中注册点击事件,修改FruitAdapter中的代码,如下所示:
package com.example.administrator.activitydemo;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
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 android.widget.Toast;
import java.util.List;
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> fruitList;
public FruitAdapter(List<Fruit> fruitList) {
this.fruitList = fruitList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.fruit_item, viewGroup, false);
final ViewHolder viewHolder = new ViewHolder(view);
viewHolder.iv_name.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = viewHolder.getAdapterPosition();
Fruit fruit = fruitList.get(position);
Toast.makeText(v.getContext(), "you clicked ImageView: " + fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
viewHolder.tv_name.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = viewHolder.getAdapterPosition();
Fruit fruit = fruitList.get(position);
Toast.makeText(v.getContext(), "you clicked TextView: " + fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
Fruit fruit = fruitList.get(i);
viewHolder.iv_name.setImageResource(fruit.getImageId());
viewHolder.tv_name.setText("" + fruit.getName());
}
@Override
public int getItemCount() {
return fruitList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
View fruitView;
ImageView iv_name;
TextView tv_name;
public ViewHolder(@NonNull View itemView) {
super(itemView);
fruitView = itemView;
iv_name = (ImageView) itemView.findViewById(R.id.iv_name);
tv_name = (TextView) itemView.findViewById(R.id.tv_name);
}
}
}
我们先是修改了内部类ViewHolder,在这个内部类中声名了一个View类的变量fruitView,并且在其构造函数里面为fruitView初始化了,其次我们在onCreateView()函数中通过获得的View实例来获得我们点击屏幕适配器当前的位置,然后根据适配器的位置,在List列表中找到对应的Fruit实例,最后通过Fruit实例获得水果的名称。最终,分别为ImageView和TextView设置点击监听,Toast提示出水果的名称,这样也可以根据Toast提示,很清晰地分辨我们点击的是文字还是图片。
点击图片后的效果图:
点击文字后的效果图: