提升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分别弹出两张不同的内容。

点击图片

从零开始打卡:第四天

点击文字:

从零开始打卡:第四天

相关文章: