10讀書筆記之更強大的滾動控件-----RecyclerView

#更強大的滾動控件-----RecyclerView
ListView由於強大的功能,在過去的Android開發當中可以說是貢獻卓越,直到今天仍然還有不計其數的程序再繼續使用着ListView。不過ListView並不是完全沒有缺點的,比如說如果我們不使用一些技巧來提升它的運行效率,那麼ListView的性能就會非常差,還有,ListView的擴展性也不夠好,它只能實現數據縱向滾動的效果,如果我們想實現橫向滾動的話,ListView是做不到的。
爲此,Android提供了一個更強大的滾動控件------RecyclerView。他可以說是一個增強版的ListView,不僅可以輕鬆實現和ListView同樣的效果,還優化了ListView中存在的各種不足之處。
目前Android官方更加推薦使用RecyclerView,未來也會有更多的程序逐漸從ListView轉向RecyclerView。
首先新建一個RecyclerViewTest項目,並讓Android Studio自動幫我們創建好活動。
##RecyclerView的基本用法。

和百分比佈局類似,RecyclerView也屬於新增的控件,爲了讓RecyclerView在所有的Android版本上都能使用,Android團隊採取了同樣的方式,將RecyclerView定義在了support庫當中。因此,想要使用RecyclerView這個控件,首先需要在項目的build.gradle中添加相應的依賴庫才行。
打開app/build.gradle文件,在dependencies閉包中添加如下內容:

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile'com.android.support:recyclerview-v7:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
}

添加完之後記得要點擊一下Sync Now來進行同步。然後修改antivity_main.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="match_parent">
<android.support.v7.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
</LinearLayout>

在佈局中加入RecyclerView控件也是非常簡單的,先爲RecyclerView指定一個id,然後將寬度和高度都設置爲match_parent,這樣RecyclerView也就佔滿了整個佈局的控件。需要注意的是:由於RecyclerView並不是內置在系統SDK當中的,所以需要把完整的包路徑寫出來。

這裏想要使用RecyclerView來實現和ListView相同的效果,因此就需要準備一份同樣的水果圖片。簡單起見,我們就直接從ListViewTest項目中把圖片複製過來就可以了,另外順便將Fruit類和fruit_item.xml也複製過來,省的將同樣的代碼再寫一遍。
接下來需要爲RecyclerView準備一個適配器,新建FruitAdapter類,讓這個適配器繼承自RecyclerView.Adapter中定義的一個內部類,代碼如下所示:

package net.nyist.lenovo.recyclerviewtest;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;


public class FruitAdapter extends RecyclerView.Adapter <FruitAdapter.ViewHolder>{
private List<Fruit> mFruitList;
static 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();
}
}

雖然這段代碼看上去好像有點長,但是其實它比ListView的適配器更容易理解。這裏我們首先定義了一個內部類ViewHolder,ViewHolder要繼承自RecyclerView.ViewHolder。然後ViewHolder的構造函數中要傳入一個View參數,這個參數通常就是RecyclerView子項的最外層佈局,那麼我們就可以通過findViewById()方法來獲取到佈局中的ImageView和TextView的實例了。
接着往下看,FruitAdapter中也有一個構造函數,這個方法用於把要展示的數據源傳進來,並賦值給一個全局變量mFruitList我們的後續操作都是在這個數據源的基礎上進行的。
繼續往下看,由於FruitAdapter是繼承自RecyclerView.Adapter的,那麼就必須重寫onCreateViewHolder(),onBindViewHolder()和getItemCount()這三個方法。onCreateViewHolder()方法是用於創建一個ViewHolder實例的,我們在這個方法中將fruit_item佈局加載進來,然後創建一個ViewHolder實例,並把加載出來的佈局傳入到構造函數當中,最後將ViewHolder的實例返回。onBindViewHolder()方法是用於對RecyclerView子項的數據進行賦值,的,會在每個子項被滾動到屏幕內的時候執行,這裏我們通過position參數得到當前的Fruit實例,然後再將數據設置到ViewHolder的ImageView和TextView當中即可。getItemCount()方法就非常簡單了,它用於告訴RecyclerView一共有多少子項,直接返回數據源的在長度就可以了。
適配器準備好了之後,我們就可以開始使用RecyclerView了,修改MainActivity中的代碼,如下所示:

package net.nyist.lenovo.recyclerviewtest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutCompat;
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 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(){
    for (int i  = 0;i < 2;i++){
        Fruit apple = new Fruit("Apple",R.drawable.apple_pic);
        fruitList.add(apple);
        Fruit banana = new Fruit("Banana",R.drawable.banana_pic);
        fruitList.add(banana);
        Fruit orange = new Fruit("Orange",R.drawable.orange_pic);
        fruitList.add(orange);
        Fruit watermelon = new Fruit("Watermelon",R.drawable.watermelon_pic);
        fruitList.add(watermelon);
        Fruit pear = new Fruit("Pear",R.drawable.pear_pic);
        fruitList.add(pear);
        Fruit grape = new Fruit("Grape",R.drawable.grape_pic);
        fruitList.add(grape);
        Fruit pineapple = new Fruit("Pineapple",R.drawable.pineapple_pic);
        fruitList.add(pineapple);
        Fruit strawberry = new Fruit("Strawberry",R.drawable.strawberry_pic);
        fruitList.add(strawberry);
        Fruit cherry = new Fruit("Cherry",R.drawable.cherry_pic);
        fruitList.add(cherry);
        Fruit mango = new Fruit("Mango",R.drawable.mango_pic);
        fruitList.add(mango);

    }
}
}

圖片暫時沒有。不過已經運行出來。

##實現橫向滾動和瀑布流佈局
我們已經知道,ListView的擴展性並不好,他只能實現縱向滾動的效果,如果想進行橫向滾動的話,ListView就做不到了。那麼RecyclerView就能做了。不僅可以做到,還非常簡單。
首先要對fruit_item佈局進行修改,因爲目前這個佈局裏面的元素是水平排列的,適用於縱向滾動的場景,而如果我們要實現橫向滾動的話,應該把fruit_item裏的元素改成垂直排列才比較合適。修改fruit_item.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="100dp"
android:layout_height="wrap_content">

<ImageView
    android:id="@+id/fruit_image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    />
<TextView
    android:id="@+id/fruit_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中的代碼,如下所示:

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);
    layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
    recyclerView.setLayoutManager(layoutManager);
    FruitAdapter adapter = new FruitAdapter(fruitList);
    recyclerView.setAdapter(adapter);
}
······

MainActivity中只加入了一行代碼,調用LinearLayoutManager的setOrientation()方法來設置佈局的排列方向,默認是縱向排列,我們傳入LinearLayoutManager.HORIZONTAL表示讓佈局橫向排列,這樣RecyclerView就可以橫向滾動了。

這裏寫圖片描述

除了LinearLayoutManager之外,RecyclerView還給我們提供了GridLayoutManger和StaggeredGridLayoutManager可以用於實現瀑布流佈局。現在開始實現一下。
首先還是修改一下fruit_item.xml中的代碼,如下所示:

package net.nyist.lenovo.recyclerviewtest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutCompat;
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 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);
    StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
    recyclerView.setLayoutManager(layoutManager);
    FruitAdapter adapter = new FruitAdapter(fruitList);
    recyclerView.setAdapter(adapter);
}
private void initFruits(){
    for (int i  = 0;i < 2;i++){
        Fruit apple = new Fruit(getRandomLengthName("Apple"),R.drawable.apple_pic);
        fruitList.add(apple);
        Fruit banana = new Fruit(getRandomLengthName("banana"),R.drawable.banana_pic);
        fruitList.add(banana);
        Fruit orange = new Fruit(getRandomLengthName("orange"),R.drawable.orange_pic);
        fruitList.add(orange);
        Fruit watermelon = new Fruit(getRandomLengthName("watermelon"),R.drawable.watermelon_pic);
        fruitList.add(watermelon);
        Fruit pear = new Fruit(getRandomLengthName("Pear"),R.drawable.pear_pic);
        fruitList.add(pear);
        Fruit grape = new Fruit(getRandomLengthName("Grape"),R.drawable.grape_pic);
        fruitList.add(grape);
        Fruit pineapple = new Fruit(getRandomLengthName("Pineapple"),R.drawable.pineapple_pic);
        fruitList.add(pineapple);
        Fruit strawberry = new Fruit(getRandomLengthName("Strawberry"),R.drawable.strawberry_pic);
        fruitList.add(strawberry);
        Fruit cherry = new Fruit(getRandomLengthName("Cherry"),R.drawable.cherry_pic);
        fruitList.add(cherry);
        Fruit mango = new Fruit(getRandomLengthName("Mango"),R.drawable.mango_pic);
        fruitList.add(mango);

    }
}
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也必須要能影響點擊事件纔可以,不然的話就沒有什麼實際用途了,不過不同於ListView的是,RecyclerView並沒有提供類似於setOnItemClickListener()這樣的註冊監聽器方法,而是需要我們自己給子項具體的View去註冊點擊事件,相比於ListView來說,實現起來要複雜一些。
ListView在點擊事件上的處理並不人性化,setOnItemClickListener()方法註冊的子項的點擊事件,但如果我想點擊的是子項裏具體的某一個按鈕呢?雖然ListView也是能做到的,但是實現起來就相對比較麻煩了。爲此,RecycylerView乾脆直接摒棄了子項點擊事件的監聽器,所有的點擊事件都由具體的View去註冊,就再沒有這個困擾了。
下面開始如何在RecyclerView中註冊點擊事件,修改FruitAdapter中的代碼:如下所示:
package net.nyist.lenovo.recyclerviewtest;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
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> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder{
    View fruitView;
    ImageView fruitImage;
    TextView fruitName;
    public ViewHolder(View view){
        super(view);
        fruitView = 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);
    final ViewHolder holder = new ViewHolder(view);
    holder.fruitView.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View v) {
            int position = holder.getAdapterPosition();
            Fruit fruit = mFruitList.get(position);
            Toast.makeText(v.getContext(),"you clicked view"+fruit.getName(),Toast.LENGTH_SHORT).show();
        }
    });
    holder.fruitImage.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View v) {
            int position = holder.getAdapterPosition();
            Fruit fruit = mFruitList.get(position);
            Toast.makeText(v.getContext(),"you clicked image"+fruit.getName(),Toast.LENGTH_SHORT).show();
        }
    });
    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();
}
}

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章