1. 程式人生 > >極簡的Android RecyclerView Adapter(使用DataBinding)

極簡的Android RecyclerView Adapter(使用DataBinding)

簡介

我們知道,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("張三", 
            
           

相關推薦

Android RecyclerView Adapter使用DataBinding

簡介我們知道,DataBinding的核心理念是資料驅動。資料驅動驅動的目標就是View,使用DataBinding,我們通過新增、修改、刪除資料來源,View就會自動予以相關變化。Android RecyclerView的Adapter起的作用就是連線資料和View。一個最

多檢視幾何3基於基本矩陣計算攝像機矩陣

MVG: p256 假設F為基本矩陣,S為任意反對稱矩陣,則可以將一對攝像機矩陣定義為: 其中e’為極點, 因為只有在如下條件滿足時,P’的秩為3, 因此S可以表示為極點的反對稱矩陣的形式 進而得到如下求解公式:

Android RecyclerView使用 -給Item新增點選事件

在上一篇部落格Android RecyclerView使用(一)中介紹了一些基本用法,但是RecyclerView沒有提供Item的點選事件,這裡需要我們自己去實現。 方法一、直接在Adapter中的onBindViewHolder()方法中實現點選事件

Android開發筆記12——ListView & Adapter

dba 只顯示一行 -1 ngs 而已 整理 adapt array xxx 轉載請註明:http://www.cnblogs.com/igoslly/p/6947225.html 下一章是關於ListFragment的內容,首先先介紹ListView的相關配置,理解L

如何在安卓原生專案中加入mui相關檔案進行開發使用新版本5+SDK建立最Android原生工程Android studio

緣起 最近在搭一個android開發的demo,專案使用androidstudio作為開發工具,使用混合式開發,途中需要整合mui.但是網上找了很多教程都是老版本的,以至於MUI官網提供的demo已經不符合教程了。後來進過多方努力終於找到了新版的教程,特此記錄。 新版教程連結點這

網站伺服器的製作與搭建HTML5+CSS+javascript+NodeJS

本文致力於使得較有經驗的程式設計師能夠在一天之內瞭解網站搭建制作的基本知識,儘快製作出可以展示用的demo。 具體地,筆者為了完成大作業,需要搭建一個搜尋引擎的網站,僅僅作為展示的demo,並不對原創性和執行效能有過多要求,所以目標是儘快製作出差不多的網站用

Android學習筆記--RecyclerView擴充套件下拉重新整理與左滑刪除

今天在使用QQ的時候就想到製作一個訊息列表的類似效果,可以實現下拉重新整理和左滑刪除效果,於是就抽空試了試。先上效果圖。 這是正在重新整理的時候。然後就會增添一個item(那個重新整理的圈是會轉的然後還可以變顏色我不會截動圖)。見下圖。 Recycl

Android學習筆記--ListView與RecyclerView

在學習了幾天Android之後,打算寫個簡單的app來試試手,於是就想寫一個“便籤”。在寫列表的時候自然的想到了用一個ListView然後配置一個adpter來顯示資訊,但是呢,在逛論壇的時候發現現在使用RecyclerView的比較多,於是就看了幾個demo,

Android-UI佈局---RecyclerView學習在介面卡中自定義長按和點選事件

該系列文章  如果想全方面學習,建議參考這個大牛的文章,寫的真可以。 地址:http://blog.csdn.net/lmj623565791/article/details/45059587 因為RecyclerView沒有點選、長按事件,需要自己寫 實現的方式比較多,

Coco2d-x 塔防遊戲“賊來了”之開發檔 之 三完結

遊戲開發 uml 塔防遊戲 賊來了 原來的教程為《塔防遊戲之賊來了》(這是我之前學習Cocos2d-x時候,看到的一個比較好的教程)原文地址目前只在泰然網看到,http://www.tairan.com/archives/6413 ,原作者為任珊。本文是基於這個教程,而編寫的遊戲開發簡檔,有了這些圖表,

android權限permission大全

wid 刪除 tro ica guard 格式 時區 禁用 經緯度 此文章由情緒控撰寫,轉載請註明此處!!! 1.android.permission.WRITE_USER_DICTIONARY 同意應用程序向用戶詞典中寫入新詞 2.android.permissi

Android個人理解】跨應用調用不同組件的方法

返回 使用 turn 數字 現實 rpc 文件夾 cas rgb 如果情景: 創建兩個應用appA和appB,appA包括一個Service,此Service有一個堵塞方法每隔10秒鐘產生一個隨機數字,例如以下: public int getRandom

Android 編輯框EditText屬性學習

藍色 區域 password border limit 文字 all 動作 方式 EditText繼承關系:View-->TextView-->EditText  EditText的屬性非常多,這裏介紹幾個: android:hint=&qu

Android基礎筆記十三- 內容提供者原理和簡單使用

暴露 tel java 四大組件 per 存儲 建數據庫 開發 fun 為什麽要有內容提供者 內容提供者的工作原理 使用內容解析者對內容提供者進行增刪改查操作 利用內容提供者和內容解析者備份手機短信 利用內容提供者插入短信 為什麽要有內容

Android 插件之類加載器

load ron 概念 基本概念 android oid ont 基本上 style 1、類加載器基本概念   類加載器(class loader)用來加載 Java 類到 Java 虛擬機中。一般來說,Java 虛擬機使用 Java 類的方式如下:Java 源程序(.j

Android學習心得13 --- Android代碼混淆1

簽名 ref nes 三分 pen key this tool prop 我在博客上發表一些我的Android學習心得,希望對大家能有幫助。 這一篇我們講述一下最新的ADT環境下怎樣進行Android混淆 在新版本號的ADT創建項目時。混碼的文

android事件分發

sim tdi p s oat front rac ram addclass framework 非常早之前寫過一篇android事件分發的博客,主要寫的是它是怎樣分發的,具體非常多原理的東西都沒有涉及到。今天就從源代碼看android怎樣控制它的分發機

從零開始搭建android框架系列

bsp andro hup 開始 blank class and lan com 網址:從零開始搭建android框架系列 githup:https://github.com/CameloeAnthony/Ant從零開始搭建android框架系列(轉)

無限分類原理與實現

轉換 完成 外灘 獲得 意思 容易 set 導航 另一個   前言   無限極分類是我很久前學到知識,今天在做一個項目時,發現對其概念有點模糊,所以今天就來說說無限極分類。   首先來說說什麽是無限極分類。按照我的理解,就是對數據完成多次分類,如同一棵樹一樣,從根開始,

【轉】Android開發筆記寫在前面的目錄

animator 進程間通信 scrip cst 調用 receiver 手勢 打包 數據庫基礎 原文:http://blog.csdn.net/aqi00/article/details/50012511 知識點分類 一方面寫寫自己走過的彎路掉進去的坑,避免以後