本系列持续更新中.... 参考《第一行代码》java
首先说明一点昨天发了一篇关于 ListView 的使用入门文章,获得了你们的一致调侃。个人想法是这样的,虽然如今 ListView 已经被 RecyclerView 替代了,可是本系列做为入门系列,力求内容完整!仍是有必要说起一下这么重要的控件的,谁能保证老的项目没有 ListView 呢?android
做为入门,一个 Android 开发者不会使用或者根本没有据说过 ListView 说不过去把!markdown
ListView 虽然很强大,可是缺点也很多,好比若是咱们刚刚不给它优化的话,效率就会很低。并且 ListView 的扩展性很差,只能实现数据的纵向滚动效果,若是想要实现横向滚动的话就作不到了。网络
为此 Android 提供了更为强大的控件--RecyclerView。ListView 可以实现的功能它均可以实现,并且还优化了 ListView 的那些不足。还有许多功能是 ListView 所作很少的,就好比横向滑动。app
Android 官方更加推荐使用 RecyclerViewdom
若是你没有使用 androidx
的话,使用 RecyclerView
也是须要引入支持库。ide
complie 'com.android.support.recyclerview-v7:24.2.1'
oop
如今都推荐使用 androidx
库了,能够这样引入布局
compile 'androidx.recyclerview:recyclerview:1.0.0'
性能
而后在 xml 中添加 RecyclerView
<?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="vertical"> <androidx.recyclerview.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/rlv"/> </LinearLayout> 复制代码
下面我就来实现和上面的例子同样的效果。
下面须要给 RecyclerView 准备一个适配器,这个适配器须要继承 RecyclerView.Adapter
,而且将泛型指定为 FruitAdapter.ViewHodler
其中 ViewHolder 是咱们在 FruitAdapter 中定义的一个内部类。代码以下
public class FruitAdapter2 extends RecyclerView.Adapter<FruitAdapter2.ViewHolder> { private List<Fruit> listFruit; public FruitAdapter2(List<Fruit> listFruit){ this.listFruit = listFruit; } static class ViewHolder extends RecyclerView.ViewHolder{ ImageView iv; TextView tv; public ViewHolder(@NonNull View itemView) { super(itemView); iv = itemView.findViewById(R.id.iv); tv = itemView.findViewById(R.id.tv_name); } } @NonNull @Override public FruitAdapter2.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fuit_item,parent,false); ViewHolder viewHolder = new ViewHolder(view); return viewHolder; } @Override public void onBindViewHolder(@NonNull FruitAdapter2.ViewHolder holder, int position) { holder.iv.setImageResource(listFruit.get(position).getImgId()); holder.tv.setText(listFruit.get(position).getName()); } @Override public int getItemCount() { return listFruit.size(); } } 复制代码
代码看上很长,其实很简单,容易理解。首先在内部定义了一个类 ViewHolder
这个类是继承自 RecyclerView.ViewHolder
的,在构造方法中须要传入一个 View 参数,这个参数就是咱们 RecyclerView 的子项的最外层的布局,而后就能够经过 findViewById()
方法来获取内部的各个控件。
FruitAdapter2
也有一个构造方法,须要传入用于展现的数据源,后续在这个数据源的基础上进行。
FruitAdapter2
继承自 RecyclerView.Adaprer
就必需要实现三个方法onCreateViewHolder()、onBindViewHolder() 和 getItemCount()
onCreateViewHolder()
从方法名也很容易能够得出,是用来建立 ViewHolder 的,把此方法内建立的 ViewHolder 经过 return 返回。onBindViewHolder()
就是用于对 RecyclerView 的子项数据绑定到 ViewHolder 上面,这个方法会在每一个子项被滚动到屏幕内的时候执行,经过这里的 position 参数获得当前子项的数据,而后设置到 ViewHolder 中就能够了。getItemCount()
方法很简单就告诉 RecyclerView 一共有多少子项,直接返回数据源的长度就能够了。适配器建立好,就可使用 RecyclerView 了。
public class RecyclerViewActivity extends AppCompatActivity { List<Fruit> list; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 初始化数据源 initData(); setContentView(R.layout.activity_recyclerview); RecyclerView recyclerView = findViewById(R.id.rlv); LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); FruitAdapter2 fruitAdapter2 = new FruitAdapter2(list); recyclerView.setAdapter(fruitAdapter2); } public void initData(){ list = new ArrayList<>(); for (int i=0;i<30;i++){ Fruit fruit = new Fruit("水果"+i,R.mipmap.ic_launcher); list.add(fruit); } } } 复制代码
首先获取了 RecyclerView 的实例,而后建立了一个 LinearlayoutManager
的对象,并将它设置到了 RecyclerView 中。LinearLayoutManager
用于指定 RecyclerView 的布局方式,是线性布局的意思,能够实现和 ListView 一样的效果。而后建立了适配器,将数据传入到适配器中,调用 RecyclerView
的 setAdapter 来完成适配器设置,让 RecyclerView 和 数据产生联系。
能够看到 RecyclerView 实现了和 ListView同样的效果,虽然代码量没有明显减小,可是逻辑更加清晰了。这只是 RecyclerView 的最基本的用法而已,下面来一些 ListView 所实现不了的功能。
Listview 的可扩展性很差,只能实现纵向滚动,若是想要横向滚动的话 ListView 就作不到了。下面用 RecyclerView 来实现横向滚动。
首先对子项布局进行修改一下,目前的布局是水平排列的,不适合水平滚动。
<?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:layout_gravity="center_horizontal" android:layout_marginTop="10dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/iv"/> <TextView android:layout_gravity="center_horizontal" android:layout_marginTop="10dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tv_name" android:layout_marginLeft="10dp"/> </LinearLayout> 复制代码
而后修改 MainActivity
// 只须要插入这么一句就能够了 layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); 复制代码
调用 LinearLayoutManager
的 setOrientation()
方法来设置布局的排列方向,默认是纵向排列的。
为何 ListView 很难实现的效果在 RecyclerView 上这么轻松就实现了呢?
主要缘由是RecyclerView 出色的设计,ListView 的布局排列是又自身去管理的,而 RecyclerView 的布局排列交给了 LayoutManager ,LayoutManager 有一套可扩展布局排列接口,子类只要按照接口的规范来实现,就能够制定各类不一样方式的排列布局了。
除了 LinearLayoutManger ,RecyclerView 还提供了 GridLayoutManager 和 StaggeredGrildLayoutManager 这两种内置的布局排列方式。
GridLayoutManager
能够实现网格布局
StraggeredGridLayoutManager
能够实现瀑布流布局
这里就来实现一下瀑布流
RecyclerView recyclerView = findViewById(R.id.rlv); // LinearLayoutManager layoutManager = new LinearLayoutManager(this); // layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL); // recyclerView.setLayoutManager(layoutManager); recyclerView.setLayoutManager(staggeredGridLayoutManager); FruitAdapter2 fruitAdapter2 = new FruitAdapter2(list); recyclerView.setAdapter(fruitAdapter2); 复制代码
别的地方不改变,只须要在代码中 new 一个瀑布流的布局管理器就能够了,里面穿的参数分别是 3 表明会把不会分红 3 列,第二个参数传入的是布局的排列方向,对于瀑布流来讲通常就是传入 VERTICAL,水平方向没有什么意义。是否是很简单啊。来看一下效果。
你能够看到和网格布局没有什么区别啊,不要着急那是由于咱们数据的缘由,致使了全部的子项高度都同样看上去就和网络布局没有什么区别了。
下面咱们来改变数据。
public void initData(){ list = new ArrayList<>(); Random random = new Random(); for (int i=0;i<30;i++){ int length =random.nextInt(20)+5; StringBuilder stringBuilder = new StringBuilder(); for (int j =0;j<length;j++){ stringBuilder.append("水果").append(i).append("++").append(length); } Fruit fruit = new Fruit(stringBuilder.toString(),R.mipmap.ic_launcher); list.add(fruit); } } 复制代码
这里咱们巧妙的使用了 Random
让它随机产生数字,用来让 name 的数据变得不同,从而出现高度不一样。
须要注意的:
在使用瀑布布局管理器的时候,子项目的布局的宽度是由分的列数来决定的。也就是说如你的子项布局的宽度设置了 match_parent
的话,StraggeredGridLayoutManager
会自动给它按照比例缩小,而不是截取。好比你给它传入了 3 列,则会缩小成 1 行能够容纳 3 个子项View 的宽度。固然若是你的子项布局的宽度设置成很小,那么就不会缩小了,效果就是子View 和 子 View 之间有很大的空隙,致使不美观。
通常作法就是将子View 的宽度设置为 match_parent
而后设置 margin
来让子项之间互留一点间距。
RecyclerView 并无像 ListView 同样提供相似 setOnItemClickListener()
的注册监听的方法。须要咱们本身给子项具体的 View 去注册点击事件,相比 ListView来讲实现起来复杂一些。
那么你会说了,既然 RecyclerView 这个强大了,各个方面都优于 ListView,可是为何点击事件没有处理好呢?
其实不是这样的,ListView 的点击事件上的处理并非那么好,setOnItemClickListener()
方法注册的只是子项的点击事件,若是我想点击子线里面的某一个按钮,经过这种方式就无法直接实现了,虽然 ListView 也能够经过在适配器中作到,可是实现起来就比较麻烦了。为此 RecyclerView 干脆把子项点击事件的监听器给去除了,全部的点击事件都由具体的 View 去注册,更加灵活了。
static class ViewHolder extends RecyclerView.ViewHolder{ View view; ImageView iv; TextView tv; public ViewHolder(@NonNull View itemView) { super(itemView); iv = itemView.findViewById(R.id.iv); tv = itemView.findViewById(R.id.tv_name); view = itemView; } } @NonNull @Override public FruitAdapter2.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fuit_item,parent,false); final ViewHolder viewHolder = new ViewHolder(view); viewHolder.view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int position = viewHolder.getAdapterPosition(); Fruit fruit = listFruit.get(position); Toast.makeText(v.getContext(),fruit.getName(),Toast.LENGTH_SHORT).show(); } }); viewHolder.iv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(v.getContext(),"img",Toast.LENGTH_SHORT).show(); } }); return viewHolder; } 复制代码
注意:
为了优化性能,注册点击事件的时候必定要在 onCreateViewHolder
方法中进行。经过 ViewHolder 的 getAdapaterPositon()
咱们就清楚的指定咱们点击的 View 在 Adapter 中的位置了。