極簡的Android RecyclerView Adapter(使用DataBinding)

来源:http://www.cnblogs.com/DoNetCoder/archive/2017/07/27/7243878.html
-Advertisement-
Play Games

使用Android DataBinding簡化Adapter的開發 ...


閱讀本篇文章需要讀者對Android Databinding和RecyclerView有一定的瞭解。

簡介

我們知道,DataBinding的核心理念是數據驅動。數據驅動驅動的目標就是View,使用DataBinding,我們通過添加、修改、刪除數據源,View就會自動予以相關變化。

Android RecyclerView的Adapter起的作用就是連接數據和View

一個最簡單的RecyclerView Adapter可能是下麵這個樣子的:

public class UserAdapter extends RecyclerView.Adapter
{
    @Override
    public int getItemCount()
    {
        return 0;
    }
    
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
    {

    }
}

通過getItemsCount(), RecyclerView知道了所有子項的數量。

通過onCreateViewHolder(), RecyclerView知道了每一個子項長什麼樣子。

通過onBindViewHolder(),讓每個子項得以顯示正確的數據。

可以看到,Adapter起的作用和DataBinding是非常類似的,使用DataBinding,可以使Adapter的編寫顯得更加簡單。

DataBinding簡單使用

接下來看一個簡單的例子。這個例子創建了一個簡單的列表,效果如下:

image

我們看看,使用DataBinding該如何實現它。

Model類:

public class User
{
    private String name;
    private int age;

    public User(String name, int age)
    {
        this.name = name;
        this.age = age;
    }

    public String getName()
    {
        return name;
    }

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

    public int getAge()
    {
        return age;
    }

    public void setAge(int age)
    {
        this.age = age;
    }
}

View xml

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <import type="cn.zmy.databindingadapter.model.User"/>
        <variable name="model"
                  type="User"/>
    </data>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="60dp"
                  android:layout_marginBottom="10dp"
                  android:background="@android:color/darker_gray"
                  android:gravity="center_vertical"
                  android:orientation="vertical">
        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{model.name}"/>
        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{String.valueOf(model.age)}"/>
    </LinearLayout>
</layout>

Adapter

public class UserAdapter extends RecyclerView.Adapter
{
    private Context context;
    private List<User> items;

    public UserAdapter(Context context)
    {
        this.context = context;
        this.items = new ArrayList<User>()
        {{
            add(new User("張三", 18));
            add(new User("李四", 28));
            add(new User("王五", 38));
        }};
    }

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

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        ItemUserBinding binding = DataBindingUtil.inflate(LayoutInflater.from(this.context), R.layout.item_user, parent, false);
        return new UserViewHolder(binding.getRoot());
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
    {
        ItemUserBinding binding = DataBindingUtil.getBinding(holder.itemView);
        binding.setModel(this.items.get(position));
        binding.executePendingBindings();
    }

    static class UserViewHolder extends RecyclerView.ViewHolder
    {
        public UserViewHolder(View itemView)
        {
            super(itemView);
        }
    }
}

Activity

public class MainActivity extends AppCompatActivity
{

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new UserAdapter(this));
    }
}

可以看到,使用了DataBinding之後,我們在onBindViewHolder中,無需再寫一些類似於holder.view.setXXX()的代碼,因為這些在Xml中就已經完成了。

優化

上面的Adapter還能不能更簡單呢?

優化ViewHolder

我們發現,Adapter中的UserViewHolder幾乎沒有做任何事。事實上,我們聲明它完全是由於AdapteronCreateViewHolder需要這麼一個返回值。

我們可以把ViewHolder提出來,這樣所有Adapter都可以使用而無需在每個Adapter中都聲明一個ViewHolder。

取名就叫BaseBindingViewHolder

public class BaseBindingViewHolder extends RecyclerView.ViewHolder
{
    public BaseBindingViewHolder(View itemView)
    {
        super(itemView);
    }
}

優化getItemCount

getItemCount返回了子項的數量。

由於幾乎每個Adapter都會存在一個List用於保存所有子項的數據,我們完全可以創建一個Adapter基類,然後在基類中實現getItemCount

優化onCreateViewHolder&onBindViewHolder

onCreateViewHolder代碼如下:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
    ItemUserBinding binding = DataBindingUtil.inflate(LayoutInflater.from(this.context), R.layout.item_user, parent, false);
    return new BaseBindingViewHolder(binding.getRoot());
}

可以看到,這個方法裡面唯一的“變數”就是“R.layout.item_user”這個layout。

onBindViewHolder代碼如下:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
    ItemUserBinding binding = DataBindingUtil.getBinding(holder.itemView);
    binding.setModel(this.items.get(position));
    binding.executePendingBindings();
}

可以看到,這個方法先獲取到View的Binding,然後給Binding的Data賦值。Binding從哪裡來?都是通過DataBindingUtil.getBinding(holder.itemView)獲取到的。

本著不寫重覆代碼,能封裝就封裝的原則,我們來創建Adapter基類。代碼如下:

public abstract class BaseBindingAdapter<M, B extends ViewDataBinding> extends RecyclerView.Adapter
{
    protected Context context;
    protected List<M> items;

    public BaseBindingAdapter(Context context)
    {
        this.context = context;
        this.items = new ArrayList<>();
    }

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

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        B binding = DataBindingUtil.inflate(LayoutInflater.from(this.context), this.getLayoutResId(viewType), parent, false);
        return new BaseBindingViewHolder(binding.getRoot());
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
    {
        B binding = DataBindingUtil.getBinding(holder.itemView);
        this.onBindItem(binding, this.items.get(position));
    }

    protected abstract @LayoutRes int getLayoutResId(int viewType);

    protected abstract void onBindItem(B binding, M item);
}

然後使UserAdapter繼承自上面封裝的BaseBindingAdapter,代碼如下:

public class UserAdapter extends BaseBindingAdapter<User, ItemUserBinding>
{
    public UserAdapter(Context context)
    {
        super(context);
        items.add(new User("張三", 18));
        items.add(new User("李四", 28));
        items.add(new User("王五", 38));
    }

    @Override
    protected int getLayoutResId(int viewType)
    {
        return R.layout.item_user;
    }

    @Override
    protected void onBindItem(ItemUserBinding binding, User user)
    {
        binding.setModel(user);
        binding.executePendingBindings();
    }
}

可以看到,優化後的Adapter除去初始化User數據源的那部分代碼,實際上的核心代碼就寥寥數行。

通過getLayoutResId我們告訴了RecyclerView子項長什麼樣子。

通過onBindItem我們給具體的每個子項綁定了合適的數據。

至於具體的綁定過程,是放在佈局的xml文件中的。

優化數據源

我們的數據源是在構造函數中這樣添加的:

items.add(new User("張三", 18));
items.add(new User("李四", 28));
items.add(new User("王五", 38));

在實際開發過程中,我們極少這麼做。因為通常在構造Adapter的時候,我們並未得到任何有效的數據。數據源可能是通過網路請求從伺服器得來,也可能是通過查詢本地資料庫表得來。我們在構造Adapter之後,可能還需要較長的時間去獲取有效的數據源,這就要求必須在Adapter構造完成之後,外部調用者還可以修改的數據源。

我們可以這樣做:

adapter.items.add(XXX);
adapter.notifyItemInserted();

這樣我們新增數據源之後,adapter也知道我們修改了數據源,進而View也就能隨之變化。

不過有了DataBinding,我們可以更為巧妙的實現上述操作。

ObservableArrayList

ObservableArrayList是Android DataBinding庫中的一個類。

public class ObservableArrayList<T> extends ArrayList<T> implements ObservableList<T>
{
    ...
}

ObservableArrayList實現了ObservableList介面。通過ObservableList,我們可以為ObservableArrayList添加一個或多個Listener。當ObservableArrayList中的數據發生變化時(添加了一個或多個元素、刪除了其中某個或某些元素時),這些Listener或收到數據源發生改變的通知。

其實ObservableArrayList的實現並不複雜,只需要重寫addaddAllremove等等等等這些可能造成集合發生變化的方法就可以實現上述效果。

雖然實現不複雜,但是ObservableArrayList卻可以解決我們上面遇到的修改數據源的問題。

我們只需要在集合發生改變時,調用adapter.notifyXXX()等方法就可以實現當數據源發生變化時,View也可以自動發生變化,而外部卻無需調用adapter.notifyXXX()了。

代碼實現

我們再次修改BaseBindingAdapter的代碼,使之支持數據源發生變化時,自動更新View。

public abstract class BaseBindingAdapter<M, B extends ViewDataBinding> extends RecyclerView.Adapter
{
    protected Context context;
    protected ObservableArrayList<M> items;
    protected ListChangedCallback itemsChangeCallback;

    public BaseBindingAdapter(Context context)
    {
        this.context = context;
        this.items = new ObservableArrayList<>();
        this.itemsChangeCallback = new ListChangedCallback();
    }

    public ObservableArrayList<M> getItems()
    {
        return items;
    }

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

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        B binding = DataBindingUtil.inflate(LayoutInflater.from(this.context), this.getLayoutResId(viewType), parent, false);
        return new BaseBindingViewHolder(binding.getRoot());
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
    {
        B binding = DataBindingUtil.getBinding(holder.itemView);
        this.onBindItem(binding, this.items.get(position));
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView)
    {
        super.onAttachedToRecyclerView(recyclerView);
        this.items.addOnListChangedCallback(itemsChangeCallback);
    }

    @Override
    public void onDetachedFromRecyclerView(RecyclerView recyclerView)
    {
        super.onDetachedFromRecyclerView(recyclerView);
        this.items.removeOnListChangedCallback(itemsChangeCallback);
    }

    //region 處理數據集變化
    protected void onChanged(ObservableArrayList<M> newItems)
    {
        resetItems(newItems);
        notifyDataSetChanged();
    }

    protected void onItemRangeChanged(ObservableArrayList<M> newItems, int positionStart, int itemCount)
    {
        resetItems(newItems);
        notifyItemRangeChanged(positionStart,itemCount);
    }

    protected void onItemRangeInserted(ObservableArrayList<M> newItems, int positionStart, int itemCount)
    {
        resetItems(newItems);
        notifyItemRangeInserted(positionStart,itemCount);
    }

    protected void onItemRangeMoved(ObservableArrayList<M> newItems)
    {
        resetItems(newItems);
        notifyDataSetChanged();
    }

    protected void onItemRangeRemoved(ObservableArrayList<M> newItems, int positionStart, int itemCount)
    {
        resetItems(newItems);
        notifyItemRangeRemoved(positionStart,itemCount);
    }

    protected void resetItems(ObservableArrayList<M> newItems)
    {
        this.items = newItems;
    }
    //endregion

    protected abstract @LayoutRes int getLayoutResId(int viewType);

    protected abstract void onBindItem(B binding, M item);

    class ListChangedCallback extends ObservableArrayList.OnListChangedCallback<ObservableArrayList<M>>
    {
        @Override
        public void onChanged(ObservableArrayList<M> newItems)
        {
            BaseBindingAdapter.this.onChanged(newItems);
        }

        @Override
        public void onItemRangeChanged(ObservableArrayList<M> newItems, int i, int i1)
        {
            BaseBindingAdapter.this.onItemRangeChanged(newItems,i,i1);
        }

        @Override
        public void onItemRangeInserted(ObservableArrayList<M> newItems, int i, int i1)
        {
            BaseBindingAdapter.this.onItemRangeInserted(newItems,i,i1);
        }

        @Override
        public void onItemRangeMoved(ObservableArrayList<M> newItems, int i, int i1, int i2)
        {
            BaseBindingAdapter.this.onItemRangeMoved(newItems);
        }

        @Override
        public void onItemRangeRemoved(ObservableArrayList<M> sender, int positionStart, int itemCount)
        {
            BaseBindingAdapter.this.onItemRangeRemoved(sender,positionStart,itemCount);
        }
    }
}

然後我們修改Activity的代碼:

public class MainActivity extends AppCompatActivity
{

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        UserAdapter adapter = new UserAdapter(this);
        recyclerView.setAdapter(adapter);

        adapter.getItems().add(new User("張三", 18));
        adapter.getItems().add(new User("李四", 28));
        adapter.getItems().add(new User("王五", 38));
    }
}

可以看到,外部僅僅將數據添加到了數據源中,而沒有做任何其他操作。不過我們的View還是更新了,效果和上面是一樣的。這也符合DataBinding的核心原則:數據驅動。使用DataBinding,我們關心的只有數據源,只要數據源發生改變,View就應隨之發生改變。

Demo

文章中的代碼已整理上傳至Github。
鏈接:https://github.com/a3349384/DataBindingAdapter
博客:https://www.zhoumingyao.cn/


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 準備前提:已經搭建好angular-cli環境,還未搭建好的請參見三少的博客(開發基礎分類) 1 新建一個文件夾 該文件夾用來存放所有利用angular-cli搭建的web前端項目 2 啟動命令視窗,併進入該文件夾 3 創建新項目 ng new 項目名稱 註意:項目名稱最好全部用字母 3.1 到文件 ...
  • 1、獲取滑鼠位置 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http:// ...
  • 今天要給大家說的是兩個不同頁面之間的通信,通過一個拖拽demo來模擬; 首先,寫好基礎的拖拽代碼: 這是將div拖動變化的值存入到本地儲存localstorage,用JSON.stringify將其轉為字元串形式: 然後新建一個新建一個demo,獲取本地的數據: 通過onstorage這個方法實現兩 ...
  • HTML 超文本標記語言 html5 建立一個HTML文件:文件名 . 尾碼(html) 解析:就是去識別 註釋:就是給開發人員開的批註 瀏覽器不去解析(不去輸出) HTML的整體框架 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <tit ...
  • 數組去重+快速排序 //老數組var arr1 = [1,2,2,3,8,6,6,4,5,5];//定義新數組var arr2 = [ ];內部可運用布爾型進行判斷數組的唯一性 for(i=0;i<arr1.length;i++){ var status = 0;(true) for(j=0;j<a ...
  • 今早,上IT修真園裡,看到師兄大娃很負責任的將我任務里的項目的排版,3,6,7的列了出來。 謝謝師兄,那麼負責任的照看師弟。 言歸正傳,我一開始,直接按照師兄的指示,選擇性的優先修改底部。效果也達到了預期的效果。後來我為了查看我的項目跟psd圖的差別。就直接上我們的IT修真園的首頁,查看它的代碼了。 ...
  • 1. 如何創建嵌套的過濾器 //允許你減少集合中的匹配元素的過濾器, //只剩下那些與給定的選擇器匹配的部分。在這種情況下, //查詢刪除了任何沒(:not)有(:has) //包含class為“selected”(.selected)的子節點。 .filter(":not(:has(.select ...
  • Atwood’s Law是Jeff Atwood在2007年提出的:“any application that can be written in JavaScript, will eventually be written in JavaScript.”。據說,這隻是當時開的一個玩笑。不過,這個玩 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...