從0系統學Android--3.6 RecyclerView

從0系統學Android--更強大的滾動控件---RecyclerView

本系列文章目錄更多精品文章分類

本系列持續更新中.... 參考《第一行代碼》java

首先說明一點昨天發了一篇關於 ListView 的使用入門文章,獲得了你們的一致調侃。個人想法是這樣的,雖然如今 ListView 已經被 RecyclerView 替代了,可是本系列做爲入門系列,力求內容完整!仍是有必要說起一下這麼重要的控件的,誰能保證老的項目沒有 ListView 呢?android

做爲入門,一個 Android 開發者不會使用或者根本沒有據說過 ListView 說不過去把!markdown

3.6 更強大的滾動控件---RecyclerView

ListView 雖然很強大,可是缺點也很多,好比若是咱們剛剛不給它優化的話,效率就會很低。並且 ListView 的擴展性很差,只能實現數據的縱向滾動效果,若是想要實現橫向滾動的話就作不到了。網絡

爲此 Android 提供了更爲強大的控件--RecyclerView。ListView 可以實現的功能它均可以實現,並且還優化了 ListView 的那些不足。還有許多功能是 ListView 所作很少的,就好比橫向滑動。app

Android 官方更加推薦使用 RecyclerViewdom

3.6.1 RecyclerView 的基本用法

若是你沒有使用 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 所實現不了的功能。

3.6.2 實現橫向滾動和瀑布流佈局

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);
複製代碼

調用 LinearLayoutManagersetOrientation() 方法來設置佈局的排列方向,默認是縱向排列的。

爲何 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 來讓子項之間互留一點間距。

3.6.3 RecyclerView 的點擊事件

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 中的位置了。

相關文章
相關標籤/搜索