提升ListView的运行效率
目前我们ListView的运行效率是很低的。因为在FruitAdapter的getView()方法中,每次都要重新加载一遍布局。
当ListView快速滚动的时候,就会使效率降低。
getView()方法中还有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用,
修改FruitAdapter中的代码:
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position);//获取当前的Fruit实例
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
view.setTag(viewHolder);//将ViewHolder存储在View中
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();//重新获取ViewHolder
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
}
原代码如下:
@Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { Fruit fruit = getItem(position);//获取当前的Fruit实例 View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false); ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image); TextView fruitName = (TextView) view.findViewById(R.id.fruit_name); fruitImage.setImageResource(fruit.getImageId()); fruitName.setText(fruit.getName()); return view; } }
对比一下,我们在getView()方法中进行了判断,如果convertView为null,则使用LayoutInflater去加载布局。如果不为null,则直接对convertView进行重用。这样就大大提高了ListView的运行效率。
我们新增一个内部类ViewHolder,用于对控件的实例进行缓存。当convertView为null的时候,创建一个ViewHolder对象并将控件的实例都存放在ViewHolder里,然后调用View的setTag()方法,将ViewHolder对象存储在View中。当convertView不为null时则调用View的getTag()方法,把ViewHolder重新取出,这样所有的控件实例都缓存在ViewHolder中了。没必要每次都通过findViewById()来获取控件实例。
ListView的点击事件
修改MainActivity中的代码:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();//初始化水果数据
FruitAdapter adapter=new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);
ListView listView=(ListView)findViewById(R.id.list_view);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Fruit fruit=fruitList.get(position);
Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
}
});
}
使用setOnItemClickListener()方法为ListView注册一个监听器,当用户点击ListView中的任何一个子项,
就会回调onItemClick()方法,在此方法中通过position参数来判断用户点击的哪一个子项,获取相应的水果
并通过Toast将名字显示出来。
运行结果如下:
更强大的滚动控件,RecyclerView
ListView只能实现数据的纵向滚动效果。
RecyclerView是一个增强版的ListView,不仅可以实现和ListView同样的效果,还优化了ListView中存在的各种不足。
基本用法:
首先要添加依赖,
这里用RecyclerView实现ListView的效果。与ListView一样,需要fruit_item来设置行布局,需要一个Fruit实体类,以及一个FruitAdapter适配器。前两个代码与ListView代码相同。
新建FruitAdapter类,继承RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder,其中,ViewHolder是我们在FruitAdapter,中定义的一个内部类,代码如下;
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> mFruitList;
class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View view) {
super(view);
fruitImage=(ImageView) view.findViewById(R.id.fruit_image);
fruitName=(TextView)view.findViewById(R.id.fruit_name);
}
}
public FruitAdapter(List<Fruit> fruitList){
mFruitList=fruitList;
}
@Override
public ViewHolder onCreateViewHolder(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(ViewHolder holder, int position) {
Fruit fruit=mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}
这里我们首先定义一个内部类,ViewHolder,ViewHolder要继承自RecyclerView.ViewHolder.然后ViewHolder
的构造函数中要传入一个View函数。这个参数通常就是RecyclerView子项最外层布局。那么我们就可以通过findViewById()
方法来获取布局中的ImageView和TextView实例。
FruitAdapter中也有一个构造函数,该方法用于把要展示的数据传进来,并赋值给一个全局变量mFruitList.我们后续的操作都讲在这个数据源的基础上进行。
由于FruitAdapter是继承自RecyclerView.Adapter的,那么就必须重写onCreateViewHolder(),onBindViewHolder(),和getItemCount()这3个方法。onCreateViewHolder()方法是用于创建ViewHolder实例。在此方法中,将fruit_item布局加载进来,然后创建一个ViewHolder实例,并把加载出来的实例传入到构造函数中,最后将ViewHolder的实例返回。onBindViewHolder()方法是用于对RecyclerView子项的数据进行赋值,会在每个子项滚到屏幕内的时候执行,这里我们通过position参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView中即可,getItemCount()用于告诉我们RecyclerView一共有多少子项,直接返回数据源的长度即可。
MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity {
private List<Fruit>fruitList=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);
LinearLayoutManager layoutManager=new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter=new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
private void initFruits() {
Fruit apple=new Fruit("Apple",R.mipmap.ic_launcher);
fruitList.add(apple);
}
}
我们使用同样的initFruits()方法,用于初始化所有的水果数据,接着在onCreate()方法中我们先获取到RecyclerView的实例,然后创建一个LinearLayoutManager对象,并将它设置到RecyclerView中。LayoutManager用于指定RecyclerView的布局方式,这里LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。接下来我们创建了FruitAdapter的实例,并将水果数据传入到FruitAdapter的构造函数中,最后调用RecyclerView的setAdapter()方法来完成适配器设置。这样RecyclerView和数据之间的关联就建立完成了。
运行结果如下:
实现横向滑动和瀑布流布局
首先更改fruit_item中的布局:
其次在MainActivity中添加一串代码:
运行结果如下:
可实现左右滑动
除了LinearLayoutManager之外,RecyclerView还提供了GridLayoutManager【网格布局】
和StaggeredGridLayoutManager【瀑布流布局】
这里实现以下瀑布流布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:orientation="vertical"
android:layout_height="match_parent">
<ImageView
android:layout_width="180dp"
android:layout_height="100dp"
android:layout_margin="10dp"
android:layout_gravity="center_horizontal"
android:id="@+id/fruit_image"/>
<TextView
android:layout_width="180dp"
android:layout_height="wrap_content"
android:id="@+id/fruit_name"
android:textSize="30sp"
android:layout_gravity="left"
android:layout_marginTop="10dp"
android:gravity="center"/>
MainActivity中的代码如下:
在onCreate()方法中,我们创建一个StaggeredGridLayoutManager的实例,该构造函数接收两个参数,1st用于指定布局的列数,2ed用于指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示会让该布局纵向排列,最后再把创建好的实例设置到RecyclerView中就可以了。
仅仅修改一行代码就足够了,但瀑布流需要各子项高度不一样,于是构造了getRandomLengthName()方法,该方法使用random对象创造1到20随机数然后将参数中传入的字符串重复随机遍。
代码如下:
private void initFruits() {
Fruit apple=new Fruit(getRandomLengthName("Apple"),R.mipmap.ic_launcher);
fruitList.add(apple);
}
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();
}
运行结果如下:
RecyclerView的点击事件
不同于ListView,RecyclerView并没有提供类似于setOnItemClickListener()这样的监听方法,需要我们自己给子项具体的View去注册点击事件。
FruitAdapter中的代码如下:
@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 view) {
int position=holder.getAdapterPosition();
Fruit fruit=mFruitList.get(position);
Toast.makeText(view.getContext(),"you clicked view"+fruit.getName(),
Toast.LENGTH_SHORT).show();
}
});
holder.fruitImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position=holder.getAdapterPosition();
Fruit fruit=mFruitList.get(position);
Toast.makeText(view.getContext(),"you clicked image"+fruit.getName(),
Toast.LENGTH_SHORT).show();
}
});
return holder;
}
我们先是修改了ViewHolder。在ViewHolder中添加了fruitView变量来保存子项最外层布局的实例,然后在
onCreateViewHolder()方法中注册点击事件。这里为最外层的布局和ImageView都注册了点击事件,
RecyclerView的强大之处在于轻松的实现子项中任意控件或布局的点击事件。我们在两个点击事件中先获取
了用户点击的position,然后通过position拿到相应的Fruit实例,再使用Toast分别弹出两张不同的内容。
点击图片
点击文字: