一行Java代碼實現(xiàn)RecyclerView的Adapter?一行都不需要!

本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨家發(fā)布

前言

聽說這種【一行代碼實現(xiàn)xxx】用爛的標(biāo)題總是能夠吸引到更多的關(guān)注。

在批判筆者這種行為之前,我們先來總結(jié)一下目前Android開發(fā)中通過RecyclerView列表的幾種常見實現(xiàn)方式。

  • 1.直接使用原生RecyclerView提供的API,自己實現(xiàn)RecyclerView的Adapter和ViewHolder。
  • 2.使用網(wǎng)上比較火的三方庫,類似一行代碼實現(xiàn)上拉加載更多,下拉刷新,xxx,xxx的RecyclerViewAdapter;或者個人開發(fā)者基于此類,再度封裝的BaseAdapter。
  • 3.使用Databinding,寫一個一勞永逸的Adapter,從此告別Adapter的多次實現(xiàn)。

筆者闡述一下個人對于上述3種列表的實現(xiàn)方式的評價:

1.直接使用原生RecyclerView提供的API,自己實現(xiàn)RecyclerView的Adapter和ViewHolder。

簡單而又直接,無論是列表的實現(xiàn)者,還是后來代碼的維護者,都能第一時間理解代碼的意圖,但是弊端很明顯,那就是Adapter類和ViewHolder類過于繁多,每一個列表都需要一個對應(yīng)的Adapter和ViewHolder。

對于偷懶的程序員來講,這種重復(fù)性的行為顯然是難以令人接受的。

2.使用網(wǎng)上比較火的三方庫,類似一行代碼實現(xiàn)上拉加載更多,下拉刷新,xxx,xxx的RecyclerViewAdapter;或者個人開發(fā)者基于此類,再度封裝的BaseAdapter。

事實上,現(xiàn)在網(wǎng)絡(luò)上越來越多出現(xiàn)別人封裝好的RecyclerViewAdapter或其他工具,恕筆者直言,大多數(shù)都略有嘩眾取寵之嫌,很多都不可避免出現(xiàn)了 過度封裝 的情況:它也許能夠涵括大多數(shù)的需求,但是這也恰恰是它致命的弊端,在涉及一些新的功能時,它也許會突然無能為力——過度的封裝帶來了嚴(yán)重的耦合,這種問題是架構(gòu)級的。

一個良好的設(shè)計需要更多的思考和嘗試,更重要的也許是靈活,高度的可拓展性。

在這里筆者推薦一個已經(jīng)使用了很久的庫:drakeet大神【MultiType】:An Android library to create multiple item types list views easily and flexibly

3.使用Databinding,寫一個一勞永逸的Adapter,從此告別Adapter的多次實現(xiàn)

DataBinding,google推出的大名鼎鼎的庫,也是Android開發(fā)中MVVM架構(gòu)中不可或缺的基礎(chǔ)組件之一,它的定義很純粹,那就是

數(shù)據(jù)驅(qū)動視圖

很遺憾的是,因為MVP模式的便利和簡單(是簡單而不是簡潔,事實上,MVP開發(fā)模式的強大也是掣肘它的原因之一,一個單純的界面至少需要Contract+MVP多個文件進行配置,還不算dagger2Component+Module文件的配置,隨著開發(fā)時間的延長,這種疑問逐漸在我腦海中浮現(xiàn)),MVP的擁護者確實比MVVM多一些,DataBinding并未大面積在Android開發(fā)中拓展開來,也是因此,筆者也很少看到關(guān)于DataBinding的實踐和總結(jié)的文章。

通過DataBinding實現(xiàn)列表,這種需求的實現(xiàn)已經(jīng)不是什么難事,網(wǎng)上一搜文章一大堆,但是仍然略微有些復(fù)雜。

筆者今天嘗試將個人開發(fā)過程中,對通過DataBinding實現(xiàn)RecyclerView列表的方式所得進行一次簡單的分享,不足之處,歡迎拍磚。

注意,本文需要讀者有一定的DataBinding的使用基礎(chǔ)。

DataBinding簡介

如果您對于DataBinding并不是很熟悉,不用擔(dān)心,接下來我將盡量用簡潔的語言對DataBinding進行簡單的介紹,讓客官最快了解DataBinding的基本語法和其優(yōu)勢。
如果您已經(jīng)對DataBinding有過一定的學(xué)習(xí),可跳過本小節(jié),直接閱讀[正文]部分。

1.DataBinding是什么

Data Binding Library (數(shù)據(jù)綁定庫),旨在減少綁定應(yīng)用程序邏輯和布局所需的一些耦合性代碼。
DataBinding最低支持Android 2.1 (API Level 7)。

2.添加DataBinding的依賴

在build.gradle添加如下聲明,添加DataBinding的依賴:

android {
     ....
    dataBinding {
        enabled = true
    }
}

3.在你的布局文件中綁定數(shù)據(jù)

我們將activity_main.xml的布局文件進行這樣的配置:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>  //綁定一個User類型的數(shù)據(jù)
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>  //TextView,顯示的內(nèi)容為user.firstName
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>   //TextView,顯示的內(nèi)容為user.lastName
   </LinearLayout>
</layout>

以上文demo為例,在<data>標(biāo)簽中 <variable>描述了一個變量user,它對應(yīng)的類型為”com.example.User”
布局中使用“@ { }”語法來書寫表達式。如為TextView的文本設(shè)置成user. firstName。

這看起來就好像是,我們將一個user對象,傳給了xml布局文件,布局文件中的控件根據(jù)這個對象的對應(yīng)屬性,顯示對應(yīng)的數(shù)據(jù)。

順便提供一下User類:

public class User {
   public String firstName;
   public String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

4.如何綁定數(shù)據(jù)

從3的代碼中,衍生出一個問題,我們?nèi)绾螌ser對象傳給activity_main的布局文件呢?

我們來看代碼,我們在MainActivity中進行這樣的配置,以代替Activity傳統(tǒng)的setContentView(layoutRes)方法:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   //MainActivity綁定activity_main布局,類似setContentView()方法
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
  //初始化User并賦值給Binding(你也可以先暫時理解為賦值給xml布局文件)
   User user = new User("Test", "User");
   binding.setUser(user);
}

好了,配置到這里,我們就可以運行demo,然后觀察到,MainActivity界面的兩個TextView,都成功顯示了User的對應(yīng)屬性了!

這種通過數(shù)據(jù)綁定,從而控制控件顯示的方式,相比傳統(tǒng)的TextView.setText(user.name),好處在哪呢,最直觀的好處是:

  • 避免Activity中多次使用findViewById,損害了應(yīng)用性能且令人厭煩。
  • 傳統(tǒng)的方式,更新UI數(shù)據(jù)需切換至UI線程,并將數(shù)據(jù)分解映射到各個view(比如TextView.setText()),而DataBinding避免了這種麻煩的行為。

5.DataBinding的更多支持

除了上文的說到功能,DataBinding還提供了更多優(yōu)秀的特性支持,對此請參考官方文檔說明(本小節(jié)的示例代碼也是節(jié)選自官方文檔),篇幅所限,無法一一舉例,還望諒解。

https://developer.android.google.cn/topic/libraries/data-binding/index.html

看到這里,雖然你可能還是對DataBinding一頭霧水——僅僅是看懂了最基本的使用方式,而沒有理解到DataBinding絕對的優(yōu)勢在哪里。

沒有關(guān)系,來看一看,筆者在目前項目中,通過DataBinding對RecyclerView的一種“新的”實現(xiàn)方式,相信有了剛才的趁熱打鐵,即使您沒有使用過DataBinding,對接下來的代碼也能夠大概看明白。

這之后,如果您對DataBinding感興趣,再嘗試花時間去慢慢研究和使用它。

正文

如何一行Java代碼都不要,實現(xiàn)RecyclerView列表呢?

真的不需要Java代碼就能實現(xiàn)列表嗎?

對不起朋友們,我騙了你們,是需要代碼的。

打死標(biāo)題黨!

請在您揮舞下手中40米長的大刀之前,來看一下筆者實現(xiàn)列表的代碼:

單類型列表的實現(xiàn)

先看下MainActivity的java代碼

public class MainActivity extends AppCompatActivity {
    
   //要展示的數(shù)據(jù)源
    public final ObservableArrayList<Student> showDatas = new ObservableArrayList<>();
   
    {
        //初始化數(shù)據(jù)源
        for (int i = 0; i < 20; i++) {
            students.add(new Student("學(xué)生:" + i));
        }
        showDatas.addAll(students);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //完成數(shù)據(jù)和布局的綁定
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setActivity(this);
    }

    public void onBindItem(ViewDataBinding binding, Object data, int position) {
        binding.getRoot().setOnClickListener(v -> Toast.makeText(this, data.toString(), Toast.LENGTH_SHORT).show());
    }

    //數(shù)據(jù)的實體類
    public class Student {
          public String name;
          public Student(String name) {
            this.name = name;
          }
      }

}

筆者保證,除了MainActivity.java類外,不再有任何MainActivity相關(guān)的Java文件(比如MainPresenter ,MainModel , MainActivityListAdapter等)。

運行App,讓我們來看一下MainActivity的UI:

SingleType.png

現(xiàn)在我們點擊單個Item,Item還會響應(yīng)對應(yīng)的點擊事件——彈出一個toast,并打印對應(yīng)的Student對象。

熟悉DataBinding的朋友們肯定有一些猜測了,我們來看一下對應(yīng)的activity_main.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="activity"
            type="com.qingmei2.simplerecyclerview.MainActivity" />
    </data>

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:items="@{activity.showDatas}"
        app:layoutManager="@string/linear_layout_manager"
        app:itemLayout="@{@layout/item_student_list}"
        app:onBindItem="@{activity::onBindItem}" />
</layout>

以及對應(yīng)的item的layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="data"
            type="com.qingmei2.simplerecyclerview.Student" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:orientation="vertical">
        
         <!--顯示人名的TextView-->
         <TextView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:padding="8dp"
            android:text="@{data.name}"
            tools:text="小明" />

        <!--Item下方灰色的分割線-->
        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:background="#ccc" />

    </LinearLayout>
</layout>

不可否認(rèn)的是,作為MainActivity的一個列表,筆者確實沒有使用Java代碼實現(xiàn)RecyclerView的Adapter和ViewHolder,以及設(shè)置LayoutManager,哪怕一行都沒有。

我們先來看一下魔法的根源,即activity_main.xml文件的RecyclerView的配置,一切的實現(xiàn)都來源于下面的四條屬性:

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:items="@{activity.showDatas}"  //要顯示的數(shù)據(jù)源
        app:layoutManager="@string/linear_layout_manager"  //指定LayoutManager
        app:itemLayout="@{@layout/item_student_list}"  //數(shù)據(jù)展示在哪個布局上
        app:onBindItem="@{activity::onBindItem}" />  //更多配置,比如我想設(shè)置點擊事件,或者引用Context

我們拋開怎么實現(xiàn),先闡述這四條屬性,為何就能展示一個完整的列表呢?

//1.要顯示的數(shù)據(jù)源
app:items="@{activity.showDatas}"

我們從MainActivity中可以看到,activity.showDatas實際上就是ObservableArrayList<Student>類型的List, ObservableArrayList本身就是ArrayList的子類,這個屬性的意義在于,告訴RecyclerView:

你需要展示的列表所需要的數(shù)據(jù)都在這里了,這個List有多少條數(shù)據(jù),你就展示多少個item。

顯然,我們在代碼中,通過模擬網(wǎng)絡(luò)請求的結(jié)果,給list初始化了20條Student數(shù)據(jù),因此,RecyclerView知道,需要展示20條數(shù)據(jù),并為其創(chuàng)建20條item展示出來。

那么數(shù)據(jù)有了,問題來了,數(shù)據(jù)如何展示給用戶呢?

因此我們需要配置item對應(yīng)的layout文件:

//2.數(shù)據(jù)展示在哪個布局上
app:itemLayout="@{@layout/item_student_list}"

我們將item_student_list.xml——item的布局文件傳給了RecyclerView,RecyclerView就知道了如何將數(shù)據(jù)展示在item上。

那么,數(shù)據(jù)如何展示在item上的呢?請往上翻,我們可以看到,item的layout文件中,也已經(jīng)將我們要展示的Student作為data傳進了item的LayoutBinding中,layout的子控件就會知道,該如何展示student的數(shù)據(jù)了。比如,將student的name展示在TextView上:

         <!--顯示人名的TextView-->
         <TextView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:padding="8dp"
            android:text="@{data.name}"
            tools:text="小明" />

現(xiàn)在數(shù)據(jù)和布局都有了,RecyclerView還需要知道,如何布局?是LinearLayout還是GridLayout呢?

很簡單,我們傳進來就可以了:

//3.指定LayoutManager
app:layoutManager="@string/linear_layout_manager"

簡單明了,我們指定使用了LinearLayoutManager

其實按理說,上述3條屬性已經(jīng)夠用了,但是我們還需要考慮到一些拓展的需求,比如點擊事件,或者和Activity/Fragment的聯(lián)動?

//4 更多配置的回調(diào)
app:onBindItem="@{activity::onBindItem}"

我們聲明了一個回調(diào),并在MainActivity中實現(xiàn)了這個回調(diào):

 public void onBindItem(ViewDataBinding binding, Object data, int position) {
        binding.getRoot().setOnClickListener(v -> Toast.makeText(this, data.toString(), Toast.LENGTH_SHORT).show());
    }

demo中很簡單,我們只聲明了一個點擊事件。事實上,也許有更多的需求,比如根據(jù)item中控件狀態(tài)的變更(比如checkbox等),來做出對應(yīng)的行為,我們在回調(diào)中聲明了3個參數(shù):

  • ViewDataBinding binding:item的Binding,通過向下轉(zhuǎn)型即可獲得對應(yīng)的Binding對象,比如本文的ItemStudentListBinding
  • Object data : item對應(yīng)的數(shù)據(jù),通過向下轉(zhuǎn)型即可獲得對應(yīng)的對象,比如本文中可以轉(zhuǎn)換為Student
  • int position:很明顯,就是item在list中的索引

示例代碼:

    public void onBindItem(ViewDataBinding binding, Object data, int position) {
        ItemStudentListBinding bind = (ItemStudentListBinding) binding;
        Student student = (Student) data;
        //點擊item,toast,打印學(xué)生的name
        bind .getRoot().setOnClickListener(v -> Toast.makeText(this, student.name, Toast.LENGTH_SHORT).show());
    }

看起來很像RecyclerView的Adapter的onBindViewHolder方法?原理也確實如此,只不過是將這個接口暴漏出來,方便開發(fā)者進行特殊處理。

相信,這四個屬性的提供,足以實現(xiàn)各種各樣 單類型列表 的需求了。

成功男人背后的女人

如果您的項目中使用了DataBinding,從此之后,您項目中的RecyclerView都將是這么的簡潔:

<android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:items="@{activity.showDatas}"
        app:layoutManager="@string/linear_layout_manager"
        app:itemLayout="@{@layout/item_student_list}"
        app:onBindItem="@{activity::onBindItem}" />

在此之前,您需要進行一些配置,這些配置我已經(jīng)連同本文的Demo一起放在了我的github上,供您參考:

本文的Demo源碼:MultiTypeBindings

請先將目光轉(zhuǎn)回到本文中,我們一起實現(xiàn)幾個簡單的類:

1.添加MultiType的依賴

正如前言所說, MultiType是一個靈活且可以高度拓展的庫,本文的demo也是基于其進行的開發(fā):

    implementation 'me.drakeet.multitype:multitype:3.3.0'
    implementation 'com.annimon:stream:1.1.9'

同時,為了代碼簡潔,我添加了Java8的StreamAPI的向下兼容庫的依賴,您也可以選擇不添加,只需要將對應(yīng)的Java8方法轉(zhuǎn)換為普通的方法即可,而不會影響對應(yīng)的功能。

當(dāng)然,我們不要忘記在android的目錄下添加databinding的支持:

android {
    dataBinding {
        enabled = true
    }
}

2.實現(xiàn)DataBindingItemViewBinder和DataBindingViewHolder

public class DataBindingItemViewBinder<T, DB extends ViewDataBinding>
        extends ItemViewBinder<T, DataBindingViewHolder<DB>> {

    private final Delegate<T, DB> delegate;

    public DataBindingItemViewBinder(Delegate<T, DB> delegate) {
        this.delegate = delegate;
    }

    public DataBindingItemViewBinder(BiFunction<LayoutInflater, ViewGroup, DB> factory,
                                     OnBindItem<T, DB> binder) {
        this(new SimpleDelegate<>(factory, binder));
    }

    public DataBindingItemViewBinder(@LayoutRes int resId, OnBindItem<T, DB> binder) {
        this((inflater, parent) -> DataBindingUtil.inflate(inflater, resId, parent, false), binder);
    }

    @NonNull
    @Override
    protected DataBindingViewHolder<DB> onCreateViewHolder(@NonNull LayoutInflater inflater,
                                                           @NonNull ViewGroup parent) {
        return new DataBindingViewHolder<>(delegate.onCreateDataBinding(inflater, parent));
    }

    @Override
    protected void onBindViewHolder(@NonNull DataBindingViewHolder<DB> holder, @NonNull T item) {
        final DB binding = holder.dataBinding;
        binding.setVariable(BR.data, item);//數(shù)據(jù)綁定對應(yīng)的item layout
        delegate.onBind(binding, item, holder.getAdapterPosition());//回調(diào)
        binding.executePendingBindings();
    }

    public interface Delegate<T, DB extends ViewDataBinding> {
        DB onCreateDataBinding(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent);

        void onBind(@NonNull DB dataBinding, @NonNull T item, int position);
    }

    public interface OnBindItem<T, DB extends ViewDataBinding> {
        void bind(DB dataBinding, T data, int position);
    }

    private static class SimpleDelegate<T, DB extends ViewDataBinding> implements Delegate<T, DB> {
        private final BiFunction<LayoutInflater, ViewGroup, DB> factory;
        private final OnBindItem<T, DB> binder;

        SimpleDelegate(BiFunction<LayoutInflater, ViewGroup, DB> factory, OnBindItem<T, DB> binder) {
            this.factory = factory;
            this.binder = binder;
        }

        @Override
        public DB onCreateDataBinding(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
            return factory.apply(inflater, parent);
        }

        @Override
        public void onBind(@NonNull DB dataBinding, @NonNull T item, int position) {
            binder.bind(dataBinding, item, position);
        }
    }
}
public class DataBindingViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {
    public final T dataBinding;

    public DataBindingViewHolder(T binding) {
        super(binding.getRoot());

        dataBinding = binding;
    }
}

這兩個類的作用就是通過代理的方式實現(xiàn)了通用的Adapter和ViewHolder,我們實現(xiàn)了它們,只要不是過于復(fù)雜的列表,我們都不再需要實現(xiàn)RecyclerView的Adapter和ViewHolder了。

我將不會對這兩個核心類有過多的講解,因為它們對于熟悉Databinding的使用者來說,并不難以理解。

如果您對于DataBinding并不是很熟悉,筆者建議您暫時先新建這兩個類,并將代碼復(fù)制上去——當(dāng)您能夠駕輕就熟地使用這個工具后,再嘗試研究它的原理,相信我,它的原理本身也并不復(fù)雜。

3.實現(xiàn)對應(yīng)的BindingAdapter和Linker類

public class RecyclerViewBindingAdapter {

    public static class BindableVariables extends BaseObservable {
        @Bindable
        public Object data;
    }

    @BindingAdapter({"itemLayout", "onBindItem"})
    public static void setAdapter(RecyclerView view, int resId, DataBindingItemViewBinder.OnBindItem onBindItem) {
        final MultiTypeAdapter adapter = getOrCreateAdapter(view);
        //noinspection unchecked
        adapter.register(Object.class, new DataBindingItemViewBinder(resId, onBindItem));
    }

    private static MultiTypeAdapter getOrCreateAdapter(RecyclerView view) {
        if (view.getAdapter() instanceof MultiTypeAdapter) {
            return (MultiTypeAdapter) view.getAdapter();
        } else {
            final MultiTypeAdapter adapter = new MultiTypeAdapter();
            view.setAdapter(adapter);
            return adapter;
        }
    }

    @BindingAdapter({"linkers", "onBindItem"})
    public static void setAdapter(RecyclerView view, List<Linker> linkers, DataBindingItemViewBinder.OnBindItem onBindItem) {
        final MultiTypeAdapter adapter = getOrCreateAdapter(view);
        //noinspection unchecked
        final ItemViewBinder[] binders = Stream.of(linkers)
                .map(Linker::getLayoutId)
                .map(v -> new DataBindingItemViewBinder(v, onBindItem))
                .toArray(ItemViewBinder[]::new);
        //noinspection unchecked
        adapter.register(Object.class)
                .to(binders)
                .withLinker(o -> Stream.of(linkers)
                        .map(Linker::getMatcher)
                        .indexed()
                        .filter(v -> v.getSecond().apply(o))
                        .findFirst()
                        .map(IntPair::getFirst)
                        .orElse(0));
    }

    @BindingAdapter("items")
    public static void setItems(RecyclerView view, List items) {
        final MultiTypeAdapter adapter = getOrCreateAdapter(view);
        adapter.setItems(items == null ? Collections.emptyList() : items);
        adapter.notifyDataSetChanged();
    }
}

public class Linker {
    private final Function<Object, Boolean> matcher;
    private final int layoutId;

    public static Linker of(Function<Object, Boolean> matcher, int layoutId) {
        return new Linker(matcher, layoutId);
    }

    public Linker(Function<Object, Boolean> matcher, int layoutId) {
        this.matcher = matcher;
        this.layoutId = layoutId;
    }

    public Function<Object, Boolean> getMatcher() {
        return matcher;
    }

    public int getLayoutId() {
        return layoutId;
    }
}

DataBinding提供了@BindingAdapter注解,用于綁定View拓展對應(yīng)的行為,關(guān)于這個注解,我們通過百度或者谷歌,都能搜到大量的學(xué)習(xí)資料,在此也不多贅述。

我們可以看到,RecyclerViewBindingAdapter 這個類中,聲明了我們剛剛認(rèn)識并了解了的幾個屬性,比如“itemLayout”,“onBindItem”,“items”等屬性,聲明了這些屬性的靜態(tài)方法,作用也就是自動創(chuàng)建對應(yīng)的Adapter,然后進行數(shù)據(jù)與視圖的綁定。

我們可以看到除了幾個熟悉的屬性,我們還聲明了"linkers"屬性,以及聲明了一個Linker類,它們的作用是用來實現(xiàn)多類型列表,我們下文將會提到。

4.其他的配置

1 2 3 的步驟已經(jīng)將核心的類都聲明完畢,接下來我們需要在attr.xml文件中聲明我們需要用到的屬性:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="RecyclerView">
        <attr name="items" format="reference" />
        <attr name="itemLayout" format="reference" />
        <attr name="linkers" format="reference" />
        <attr name="layoutManager" format="reference" />
        <attr name="onBindItem" format="reference" />
    </declare-styleable>

</resources>

這樣,我們在xml文件中,直接通過代碼提示的功能,為RecyclerView賦予對應(yīng)的配置了。

然后,在values.xml文件中,聲明好我們要引用的layoutManager:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="linear_layout_manager">android.support.v7.widget.LinearLayoutManager</string>
    <string name="grid_layout_manager">android.support.v7.widget.GridLayoutManager</string>
</resources>

配置到這里,上面demo中我們實現(xiàn)的功能就已經(jīng)可以實現(xiàn)了,我們的這些配置類,都是一次聲明,之后項目中無需再進行處理的,也就是說,隨著項目中列表越來越多,我們將會節(jié)省越來越多的代碼。

最后,不管再多的RecyclerView,我們都只需要配置好xml文件中RecyclerView對應(yīng)的四條屬性,然后,告別繁多的Adapter,LayoutManager和ViewHolder,and enjoy coding!

(PS,對于Activity的onBindItem的回調(diào)方法,復(fù)雜的需求也許會導(dǎo)致很臃腫,比如狀態(tài)的判斷處理,這也是一直在思考能否再簡化的地方,有思路的朋友望請不吝賜教!)

多類型列表需要幾行代碼?

大概,也是0行吧。

一個簡單的demo:

multitype.png

這仍然是一個RecyclerView列表,不同的是,它需要展示Teacher和Student兩種數(shù)據(jù)(因為筆者懶,所以2種數(shù)據(jù)沒有打亂排列,但是請相信,他們?nèi)蕴幱谕粋€RecyclerView,并對應(yīng)不同的布局和邏輯處理)。

讓我們看一看代碼:

public class MainActivity extends AppCompatActivity {
    //要展示數(shù)據(jù)源
    public final ObservableArrayList<Object> showDatas = new ObservableArrayList<>();
    //Linker對象的list,用來管理item展示的邏輯
    public final ObservableArrayList<Linker> linkers = new ObservableArrayList<>();

    public final List<Student> students = new ArrayList<>();
    public final List<Teacher> teachers = new ArrayList<>();


    {
        for (int i = 0; i < 20; i++) {
            students.add(new Student("學(xué)生:" + i));
        }
        for (int j = 0; j < 5; j++) {
            teachers.add(new Teacher("教師:" + j, "年齡:" + (20 + j)));
        }
        linkers.add(
                new Linker(
                        o -> o instanceof Student,
                        R.layout.item_student_list
                )//如果item的數(shù)據(jù)是Student類型,使用item_student_list布局
        );
        linkers.add(
                new Linker(    
                        o -> o instanceof Teacher,
                        R.layout.item_teacher_list
                )//如果item的數(shù)據(jù)是Teacher類型,使用item_teacher_list布局
        );
        showDatas.addAll(students);
        showDatas.addAll(teachers);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setActivity(this);
    }

    public void onBindItem(ViewDataBinding binding, Object data, int position) {
        binding.getRoot().setOnClickListener(v -> Toast.makeText(MainActivity.this, data.toString(), Toast.LENGTH_SHORT).show());
    }
}

我們看到,依然沒有Adapter,ViewHolder(如果是常規(guī)實現(xiàn)方式,這里應(yīng)該是2種ViewHolder的類),以及LayoutManager。

看一下布局文件:

        <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:items="@{activity.showDatas}"
            app:layoutManager="@string/linear_layout_manager"
            app:linkers="@{activity.linkers}"  //請注意這行
            app:onBindItem="@{activity::onBindItem}" />

和單類型列表相比,我們少了

app:itemLayout="@{@layout/item_student_list}"

多了

app:linkers="@{activity.linkers}"

很好理解,對于多類型列表的展示,我們會定義多個不同item的layout布局文件,因此我們不能單純的為RecyclerView賦予固定的布局,而是賦予其不同item的所有l(wèi)ayout文件

R.layout.item_student_list
R.layout.item_teacher_list

接下來需要思考的問題是,我們?nèi)绾蔚弥恳粋€item需要使用哪種類型的布局呢?

我們可以通過一個函數(shù),來判斷item數(shù)據(jù)的類型,如果是Student類型,就使用R.layout.item_student_list ,如果是Teacher類型,就使用R.layout.item_teacher_list。

因此我們衍生出了Linker類(見上文),它包含了一個LayoutRes屬性和一個Function<Object, Boolean>函數(shù),我們初始化時,根據(jù)數(shù)據(jù)對應(yīng)的類型進行判斷,如果函數(shù)的返回值為true,就使用其內(nèi)部的LayoutRes并進行展示:

linkers.add(new Linker(
                        o -> o instanceof Student,
                        R.layout.item_student_list
                )//如果item的數(shù)據(jù)是Student類型,使用item_student_list布局
);
linkers.add(new Linker(    
                        o -> o instanceof Teacher,
                        R.layout.item_teacher_list
                )//如果item的數(shù)據(jù)是Teacher類型,使用item_teacher_list布局
);

小結(jié)

在使用MVVM模式進行項目開發(fā)的大半年里,收獲良多,在此尤其感謝同事Z0君對自己的很多指點(事實上,本文的實現(xiàn)完全是來源于TA的思路,筆者只不過照搬,理解和闡述分享而已),同時感謝項目中共同一起開發(fā)的小伙伴們,共勉。

在本文的標(biāo)題選擇上,筆者選擇了這么強目的性的標(biāo)題,也確實希望能夠有更多朋友能夠一起打開本文,閱讀并共同探討,希望以文章的內(nèi)容能夠表達筆者對此的歉意。

真誠地感謝,您能夠堅持閱讀到這里,對文章內(nèi)容的肯定,就是對作者最大的鼓勵。

本文Demo的源碼傳送門,有興趣的朋友可以拉下來運行一下,希望能夠提供一定的思路:

https://github.com/qingmei2/MultiTypeBindings

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內(nèi)容