第十三章 強(qiáng)大的滾動(dòng)控件——RecyclerView

1. 引言

??上兩章簡(jiǎn)單講了下ListView的一些基本用法。ListView由于其強(qiáng)大的功能,在Android 開發(fā)的過程中貢獻(xiàn)卓越。但是由于它自身的部分缺點(diǎn),不使用一些技巧來(lái)提高它的運(yùn)行效率的話,它的性能體驗(yàn)會(huì)非常的差。還有就是ListView的擴(kuò)展性不夠完美,我們?cè)趯?shí)現(xiàn)數(shù)據(jù)縱向排列的同時(shí),還需要數(shù)據(jù)的橫向排列。
?RecyclerView可以說是ListView的增強(qiáng)版,不僅可以輕松實(shí)現(xiàn)和ListView同樣的效果,還會(huì)優(yōu)化ListView存在的各種問題。目前官方更推薦RecyclerView,相信未來(lái)也會(huì)有更多的程序逐漸從ListView轉(zhuǎn)向Recyclerview。

2. RecyclerView的基本用法

??RecyclerView也屬于新增的控件,所以為了能夠使用RecyclerView,首先得在項(xiàng)目的build.gradle的dependencies中添加相應(yīng)的依賴庫(kù)。


dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    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.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:recyclerview-v7:25.3.1'
}

添加完依賴庫(kù)之后,Sync Now來(lái)進(jìn)行同步,修改activity_main.xml文件中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.demo.recyclerviewdemo.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

在你添加完了RecyclerView的依賴包之后,添加依賴控件的,記得顯示完整的包名。為了和之前的ListView的項(xiàng)目有一個(gè)對(duì)比,這里用同樣的圖和布局模式。
1.item_list.xml ,展示子項(xiàng)目的布局

<?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="100dp"
    android:gravity="center_vertical"
    android:padding="10dp">

    <ImageView
        android:id="@+id/iv_head"
        android:layout_width="80dp"
        android:layout_height="match_parent"
        android:src="@mipmap/ic_launcher"
        android:scaleType="centerCrop"/>
    <TextView
        android:id="@+id/tv_student_name"
        android:layout_width="wrap_content"
        android:layout_height="80dp"
        android:text="學(xué)生姓名"
        android:layout_gravity="center"
        android:gravity="center"/>

</LinearLayout>

2.Student.java 學(xué)生的實(shí)體類


public class Student {
    private  String name;
    private  int  imageId;

    public Student(String name, int imageId) {
        this.name = name;
        this.imageId = imageId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getImageId() {
        return imageId;
    }

    public void setImageId(int imageId) {
        this.imageId = imageId;
    }
}


  1. 自定義一個(gè)適配器,讓這個(gè)適配器繼承RecyclerView.Adapter ,并將泛型指定為StudentAdapter.ViewHolder,其中ViewHolder是我們自己定義的一個(gè)內(nèi)部類。

public class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.ViewHolder> {

    private List<Student> mStudentList;

    public StudentAdapter(List<Student> mStudentList) {
        this.mStudentList = mStudentList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list,parent,false);
        ViewHolder holder=new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Student student=mStudentList.get(position);
        holder.studentImage.setImageResource(student.getImageId());
        holder.studentname.setText(student.getName());

    }

    @Override
    public int getItemCount() {
        return mStudentList.size();
    }

    static  class  ViewHolder extends  RecyclerView.ViewHolder{

        private ImageView studentImage;
        private TextView  studentname;
        public ViewHolder(View itemView) {
            super(itemView);
            studentImage= (ImageView) itemView.findViewById(R.id.iv_head);
            studentname= (TextView) itemView.findViewById(R.id.tv_student_name);
        }
    }
}

??可能剛開始看的時(shí)候有點(diǎn)暈,哪里好理解了?我發(fā)四沒有騙你,你等我慢慢給你分析下。首先我們定義了一個(gè)內(nèi)部類 ViewHolder 繼承RecyclerView.ViewHolder,然后ViewHolder下面會(huì)有一個(gè)構(gòu)造函數(shù)ViewHolder(itemView)需要讓你實(shí)現(xiàn),你發(fā)現(xiàn)沒有,你在構(gòu)造函數(shù)傳的這個(gè)View參數(shù),其實(shí)就是RecyclerView中的子項(xiàng)的最外層布局。所以我們就可以通過findviewByid的方法來(lái)獲取布局中實(shí)例。
??然后我們接下來(lái)繼續(xù)分析,StudentAdapter 中也有一個(gè)構(gòu)造函數(shù),這個(gè)方法是用于將要展示的數(shù)據(jù)源傳進(jìn)來(lái),并賦值給全局變量,后續(xù)的操作都是圍著這個(gè)操作進(jìn)行的。
??繼續(xù)往下看,由于StudentAdapter 是繼承RecyclerView.Adapter的,所以必須重寫 onCreateViewHolder(), onBindViewHolder(),getItemCount()三個(gè)方法。 onCreateViewHolder()這個(gè)方法是創(chuàng)建ViewHolder 實(shí)例,然后將item_list.xml這個(gè)子布局也裝載進(jìn)去,并把加載出來(lái)的布局傳入到構(gòu)造函數(shù)當(dāng)中,最后返回ViewHolder的實(shí)例。onBindViewHolder()這個(gè)方法是對(duì)RecyclerView的子項(xiàng)的數(shù)據(jù)進(jìn)行賦值,會(huì)在子項(xiàng)被滾動(dòng)到屏幕中的時(shí)候被執(zhí)行。我們通過get(position)的方法得到當(dāng)前項(xiàng)的Student的實(shí)例。getItemCount(),告訴RecyclerView,這個(gè)集合有多少個(gè)子項(xiàng)。直接返回?cái)?shù)據(jù)的長(zhǎng)度。

4.開始使用RecyclerView了。


public class MainActivity extends AppCompatActivity {
    private List<Student> studentList=new ArrayList<>();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initStudent();//初始化學(xué)生數(shù)據(jù)
        RecyclerView  recyclerView= (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);
        recyclerView.setLayoutManager(linearLayoutManager);
        StudentAdapter adapter=new StudentAdapter(studentList);
        recyclerView.setAdapter(adapter);


    }

    private void initStudent() {
        for (int i = 0; i < 2; i++) {
            Student student1=new Student("學(xué)生一",R.mipmap.a1);
            studentList.add(student1);
            Student student2=new Student("學(xué)生二",R.mipmap.a2);
            studentList.add(student2);
            Student student3=new Student("學(xué)生三",R.mipmap.a3);
            studentList.add(student3);
            Student student4=new Student("學(xué)生四",R.mipmap.a4);
            studentList.add(student4);
            Student student5=new Student("學(xué)生五",R.mipmap.a5);
            studentList.add(student5);
            Student student6=new Student("學(xué)生六",R.mipmap.a6);
            studentList.add(student6);
            Student student7=new Student("學(xué)生七",R.mipmap.a7);
            studentList.add(student7);
            Student student8=new Student("學(xué)生八",R.mipmap.a8);
            studentList.add(student8);
            Student student9=new Student("學(xué)生九",R.mipmap.a9);
            studentList.add(student9);
            Student student10=new Student("學(xué)生十",R.mipmap.a10);
            studentList.add(student10);
            Student student11=new Student("學(xué)生十一",R.mipmap.a11);
            studentList.add(student11);
            Student student12=new Student("學(xué)生十二",R.mipmap.a12);
            studentList.add(student12);
            Student student13=new Student("學(xué)生十三",R.mipmap.a13);
            studentList.add(student13);

        }
    }

}

?可以看到我們使用同樣的辦法initStudent()初始化所有的學(xué)生對(duì)象,接著在OnCreate()方法中線獲取到RecyclerView的實(shí)例。然后創(chuàng)建一個(gè)LinearLayoutManager對(duì)象,將它設(shè)置到RecyclerView當(dāng)中,這里使用LinearLayoutManager是線性布局的意思??梢詫?shí)現(xiàn)和ListView類似的效果。然后是創(chuàng)建Studentadapter的實(shí)例,將studentList實(shí)例數(shù)據(jù)傳到StudentAdapter的構(gòu)造函數(shù)當(dāng)中。最后調(diào)用setAdapter()方法完成適配器的設(shè)置。這樣RecyclerView和數(shù)據(jù)之間的關(guān)聯(lián)就建立完成了。效果如下圖所示:

recyclerviewdemo

3.實(shí)現(xiàn)橫向滾動(dòng)

?之前說了ListView不能設(shè)置橫向排列,RecyclerView可以很好的彌補(bǔ)這個(gè)不足,使用方法很簡(jiǎn)單,在MainActivity加入一行代碼就可以了。

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initStudent();//初始化學(xué)生數(shù)據(jù)
        RecyclerView  recyclerView= (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);

        linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);

        recyclerView.setLayoutManager(linearLayoutManager);
        StudentAdapter adapter=new StudentAdapter(studentList);
        recyclerView.setAdapter(adapter);
    }

發(fā)現(xiàn)沒有,就多了一行代碼 linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);設(shè)置布局的橫向排列。默認(rèn)是縱向排列的。顯示效果如圖所示:

橫向排列

為啥子ListView不能橫向滑動(dòng),而RecyclerView可以很輕松的實(shí)現(xiàn)呢?這主要的得益于RecyclerView出色的設(shè)計(jì)。ListView的布局排列是由自身去管理的,而RecyclerView將這個(gè)工作給了LayoutManager,LayoutManager中制定了一套可擴(kuò)展的布局排列接口,子類按照這個(gè)借口的規(guī)范來(lái)實(shí)現(xiàn),這樣就可以制定出各種不同的排列的方式的布局。

4.瀑布流布局

RecyclerView除了提供了LinearLayoutManager線性布局排列以外,還提供了GridLayoutManager(網(wǎng)格布局)和StaggeredGridLayoutManager(瀑布流布局)。
這里稍稍修改了下item_list的布局:

<?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="wrap_content"
    android:orientation="vertical"
    android:padding="5dp"
    >

    <ImageView
        android:id="@+id/iv_head"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"
        android:scaleType="centerCrop"
        android:layout_gravity="center_horizontal"/>
    <TextView
        android:id="@+id/tv_student_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:text="學(xué)生姓名"
        android:layout_marginTop="10dp"/>

</LinearLayout>

代碼也更改了一下,仔細(xì)看:

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initStudent();//初始化學(xué)生數(shù)據(jù)
        RecyclerView recyclerView= (RecyclerView) findViewById(R.id.recycler_view);

        StaggeredGridLayoutManager staggeredGridLayoutManager=new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);

        recyclerView.setLayoutManager(staggeredGridLayoutManager);
        StudentAdapter adapter=new StudentAdapter(studentList);
        recyclerView.setAdapter(adapter);


    }

對(duì)比下前面的發(fā)現(xiàn)就改變了一句是不是:
StaggeredGridLayoutManager staggeredGridLayoutManager=new StaggeredGridLayoutManager (3, StaggeredGridLayoutManager.VERTICAL);
StaggeredGridLayoutManager 的參數(shù):

  1. 第一個(gè)參數(shù),是設(shè)置多少列,我這里設(shè)為3列
  2. 第二個(gè)參數(shù) ,是指定布局的排列方向
    實(shí)現(xiàn)的效果如下圖所示:
瀑布流布局

5.網(wǎng)格布局

在經(jīng)過了這個(gè)多的布局之后,下面的的網(wǎng)格布局應(yīng)該也猜到了,也是修改其中的LayoutManager。

  @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initStudent();//初始化學(xué)生數(shù)據(jù)
        RecyclerView recyclerView= (RecyclerView) findViewById(R.id.recycler_view);
        GridLayoutManager gridLayoutManager=new GridLayoutManager(this,4);
        gridLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(linearLayoutManager);
        StudentAdapter adapter=new StudentAdapter(studentList);
        recyclerView.setAdapter(adapter);
    }

GridLayoutManager的兩個(gè)參數(shù):
1、第一個(gè)參數(shù),是設(shè)置上下文對(duì)象
2、第二個(gè)參數(shù)是設(shè)置排列的行數(shù)
還有最后設(shè)置好排列的方向


網(wǎng)格布局

6.RecyclerView的點(diǎn)擊事件

??正如ListView那樣,RecyclerView也必須滿足點(diǎn)擊這個(gè)硬性要求的。但是與ListView不同的是,RecyclerView并沒有提供類似setOnItemClickLisenter()這樣的注冊(cè)監(jiān)聽器的方法,而是需要我們自己給子項(xiàng)具體的view去注冊(cè)點(diǎn)擊事件。相比較實(shí)現(xiàn)起來(lái),可能會(huì)稍微復(fù)雜一點(diǎn)。
??大兄弟,看到這里你是不是又有點(diǎn)暈了???都說了是比ListView更優(yōu)秀,那搞個(gè)點(diǎn)擊事件怎么實(shí)現(xiàn)起來(lái)還復(fù)雜了呢?...... 大哥,我真的沒騙你,拳頭放下來(lái)。因?yàn)長(zhǎng)istView的點(diǎn)擊處理,是點(diǎn)擊一整個(gè)子項(xiàng)目,然后出發(fā)點(diǎn)擊事件的是不,哪如果我只想子項(xiàng)目里面的某一個(gè)控件出發(fā)自己的點(diǎn)擊事件呢?ListView并沒有給出人性化的處理,處理起來(lái)比較麻煩。所以RecyclerView就放棄了子項(xiàng)的點(diǎn)擊監(jiān)聽事件的監(jiān)聽器。所有的點(diǎn)擊事件都由具體的View來(lái)處理,這樣是不是沒有這個(gè)煩惱了啊。
??這里講一下它的點(diǎn)擊事件,需要修改StudentAdapter的代碼。我們先是修改了ViewHolder,在ViewHolder 添加studentView 變量來(lái)保存子項(xiàng)最外層的布局的實(shí)例。然后在onCreateViewHolder()方法中注冊(cè)點(diǎn)擊事件就可以了。這里分別為最外層的布局和ImageViewd都注冊(cè)了點(diǎn)擊事件。(意思是,它不僅給每個(gè)item都設(shè)置了點(diǎn)擊事件,連item里面的imageview也設(shè)置了點(diǎn)擊事件)。通過holder 獲取子項(xiàng)的view,然后設(shè)置點(diǎn)擊事件。通過holder獲取子項(xiàng)的view的position。這樣就拿到了子項(xiàng)的Student的數(shù)據(jù)。設(shè)置一個(gè)點(diǎn)擊事件,來(lái)顯示具體的數(shù)據(jù)值。在點(diǎn)擊文字的時(shí)候,我們沒有給文字設(shè)置點(diǎn)擊事件,但是給studentView設(shè)置了點(diǎn)擊事件,所以還是被最外層的布局捕獲到,彈出Toast。

ublic class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.ViewHolder> {

    private List<Student> mStudentList;

    public StudentAdapter(List<Student> mStudentList) {
        this.mStudentList = mStudentList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list,parent,false);
        final ViewHolder holder=new ViewHolder(view);

        holder.studentView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = holder.getAdapterPosition();
                Student student = mStudentList.get(position);
                Toast.makeText(v.getContext(), "你點(diǎn)擊了"+student.getName(), Toast.LENGTH_SHORT).show();

            }
        });
        holder.studentImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = holder.getAdapterPosition();
                Student student = mStudentList.get(position);
                Toast.makeText(v.getContext(), "你點(diǎn)擊了"+student.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Student student=mStudentList.get(position);
        holder.studentImage.setImageResource(student.getImageId());
        holder.studentname.setText(student.getName());

    }

    @Override
    public int getItemCount() {
        return mStudentList.size();
    }

    static   class  ViewHolder extends  RecyclerView.ViewHolder{

        private ImageView studentImage;
        private TextView  studentname;
          View  studentView;

          public ViewHolder(View itemView) {
            super(itemView);
            studentView=itemView;
            studentImage= (ImageView) itemView.findViewById(R.id.iv_head);
            studentname= (TextView) itemView.findViewById(R.id.tv_student_name);
        }
    }


}

效果如圖:

recyclerview的點(diǎn)擊事件

項(xiàng)目代碼github地址:https://github.com/wangxin3119/recyclerview

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容