快速開發偷懶必備,搞定所有ViewGroup的Adapter . 支援自定義ViewGroup

mcxtzhang發表於2016-12-12

概述

開發中,經常會用到動態在ScrollView、LinearLayout裡addView的事,尤其是ItemView一樣時,每次都要寫一大堆程式碼 inflater 動態addView,很煩。

還有就是在巢狀ListView、ScrollView時,想採用LinearLayout替代(這樣效能更佳,不明白的看一個控制元件搞定巢狀ListView),但動態addView步驟神煩。

這個時候就開始期待,能不能有一種快速為任意ViewGroup新增子View的東西。

我之前為此事特意寫過一篇LinearLayout封裝博文,封裝了一個控制元件使用。試圖一個控制元件搞定巢狀ListView。但是後來發現,採用繼承某個ViewGroup做這個事情不夠優雅 ,對程式碼有侵入性,如果有其他ViewGroup需要動態addView,就會寫重複的程式碼

前幾天有人在群裡問,如何方便的給ScrollView動態新增不同種型別的childView,類似RecyclerView那樣。我之前的封裝由於內部有一個簡單的重用機制,只支援單一ItemType,也不支援多種型別的childView

那麼需求就來了:

  • 快速簡單使用
  • 支援任意ViewGroup
  • 無耦合
  • 無侵入性
  • Item支援多種型別

除此之外,我還加入:

  • 為ItemView設定OnItemClickListener
  • 為ItemView設定OnItemLongClickListener

本文就封裝了這麼一個東西。

核心:

  • 利用Adapter模式封裝getView的操作
  • 搭配一個工具類,為所有ViewGroup addView。
  • 再封裝出兩個使用快速簡單的Adapter 分別用於新增 單一Item佈局、多種Item佈局。

PS:所以本文也算是填了之前的一個坑,在之前介面卡模式博文文末,我就提到要寫一篇為流式佈局增加Adapter的文章,作為Adapter的實戰演練。使用本文封裝的Adapter自然可以達到這一點。

由於採用Adapter隔離ViewGroup和ItemView,在切換ViewGroup時,十分方便。
如:在需求讓你把一個HorizontalScrollView包裹的水平標籤轉換成流式佈局時,只需要在xml替換控制元件即可。Adapter將自動完成適配的工作。其他程式碼一句不用修改。

不BB了,先看看以後如何使用吧,夠不夠簡單粗暴

轉載請標明出處:
gold.xitu.io/post/584d52…
本文出自:【張旭童的稀土掘金】(gold.xitu.io/user/56de21…)
程式碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/a…

使用預覽

單一Item型別:

Adapter泛型傳入JavaBean,建構函式傳入資料集和layout佈局,一句程式碼搞定:

        //單一ItemView
        ViewGroupUtils.addViews(mLinearLayout, new SingleAdapter<TestBean>(this, mDatas, R.layout.item_test) {
            @Override
            public void onBindView(ViewGroup parent, View itemView, TestBean data, int pos) {
                Glide.with(LinearLayoutActivity.this)
                        .load(data.getAvatar())
                        .into((ImageView) itemView.findViewById(R.id.ivAvatar));
                ((TextView) itemView.findViewById(R.id.tvName)).setText(data.getName());
            }
        });複製程式碼

效果:

快速開發偷懶必備,搞定所有ViewGroup的Adapter . 支援自定義ViewGroup

以前會用ScrollView巢狀ListView,現在只要用ScrollView套LinearLayout即可,效能更佳。

多種Item型別:

多種Item型別分兩種情況:

資料結構相同:

資料結構相同依然可以給Adapter傳入泛型,避免強轉:

        //多種ItemViewType,但是資料結構相同,可以傳入資料結構泛型,避免強轉
        ViewGroupUtils.addViews(linearLayout, new MulTypeAdapter<MulTypeBean>(this, initDatas()) {
            @Override
            public void onBindView(ViewGroup parent, View itemView, MulTypeBean data, int pos) {
                ((TextView) itemView.findViewById(R.id.tvWords)).setText(data.getName() + "");
                Glide.with(MulTypeActivity.this)
                        .load(data.getAvatar())
                        .into((ImageView) itemView.findViewById(ivAvatar));
            }
        });複製程式碼

效果:

快速開發偷懶必備,搞定所有ViewGroup的Adapter . 支援自定義ViewGroup
多種Item,資料結構相同。

資料結構不同:

如果資料結構不同,則不用傳入泛型,但是使用時需要強轉:

        //多種Item型別:資料結構不同 不傳泛型了 使用時需要強轉javaBean,判斷ItemLayoutId
        ViewGroupUtils.addViews((ViewGroup) findViewById(R.id.activity_mul_type_mul_bean), new MulTypeAdapter(this, datas) {
            @Override
            public void onBindView(ViewGroup parent, View itemView, IMulTypeHelper data, int pos) {
                switch (data.getItemLayoutId()) {
                    case R.layout.item_mulbean_1:
                        MulBean1 mulBean1 = (MulBean1) data;
                        Glide.with(MulTypeMulBeanActivity.this)
                                .load(mulBean1.getUrl())
                                .into((ImageView) itemView);
                        break;
                    case R.layout.item_mulbean_2:
                        MulBean2 mulBean2 = (MulBean2) data;
                        TextView tv = (TextView) itemView;
                        tv.setText(mulBean2.getName());
                }
            }
        });複製程式碼

資料結構:

public class MulBean1 implements IMulTypeHelper {
    private String url;
    @Override
    public int getItemLayoutId() {
        return R.layout.item_mulbean_1;
    }
}複製程式碼
public class MulBean2 implements IMulTypeHelper {
    private String name;
    @Override
    public int getItemLayoutId() {
        return R.layout.item_mulbean_2;
    }
}複製程式碼

Item1佈局是一個ImageView,Item2佈局是一個TextView
效果:

快速開發偷懶必備,搞定所有ViewGroup的Adapter . 支援自定義ViewGroup
這次用橫向展示 多種Item,資料結構不同。

Item點選事件

item的點選和長按等事件,有兩種方法設定,這裡以點選事件為例,長按事件同理:

Adapter.onBindView()裡設定

Adapter.onBindView()方法裡能拿到ItemView,自然就可以設定各種事件。類似RecyclerView。

在這裡設定優先順序更高。原因後文會提到。

@Override
            public void onBindView(ViewGroup parent, View itemView, final MulTypeBean data, int pos) {
                ....
                itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(mContext, "onBindView裡設定:文字是:" + data.getName(), Toast.LENGTH_SHORT).show();
                    }
                });
            }複製程式碼

通過ViewGroupUtils設定

可以在ViewGroupUtils.addViews直接作為引數傳入.

也可以用ViewGroupUtils.setOnItemClickListener()設定 。

優先順序比Adapter.onBindView()裡設定低,原因後文會提到。

        //設定OnItemClickListener
        OnItemClickListener onItemClickListener = new OnItemClickListener() {
            @Override
            public void onItemClick(ViewGroup parent, View itemView, int position) {
                Toast.makeText(MulTypeActivity.this, "通過OnItemClickListener設定:" + position, Toast.LENGTH_SHORT).show();
            }
        };
        //可以在`ViewGroupUtils.addViews`直接作為引數傳入.\
        ViewGroupUtils.addViews(linearLayout, adapter ,onItemClickListener);
        //或者 也可以用`ViewGroupUtils.setOnItemClickListener()`設定
        ViewGroupUtils.setOnItemClickListener(linearLayout,onItemClickListener);複製程式碼

看起來還是挺好的,嗯~至少我自己這麼覺得,我個人比較喜歡這種0耦合,每一個庫都像可組裝拆卸的機關槍一樣,拿起來就用。而不是笨重功能繁多的重灌坦克。
搭配我的得意之作,每次必安利的史上整合最簡單側滑選單控制元件
效果如下:

快速開發偷懶必備,搞定所有ViewGroup的Adapter . 支援自定義ViewGroup
文首提到的,一開始是個水平ScrollView

快速開發偷懶必備,搞定所有ViewGroup的Adapter . 支援自定義ViewGroup
替換成流式佈局

無特殊設定,僅僅替換ViewGroup為流式佈局,替換Item根佈局為我擼的側滑選單庫,能感受到這種0耦合的庫的魅力了麼。23333333 。

設計思路

下面就讓我手摸手帶大家實現它。
先看類圖。

UML類圖:

快速開發偷懶必備,搞定所有ViewGroup的Adapter . 支援自定義ViewGroup

先簡要概括

  • 我們的頂層介面IViewGroupAdapter暴露出兩個方法供ViewGroup使用。

  • ViewGroupUtils 是為任意ViewGroup 動態addView的工具類,只依賴於 IViewGroupAdapter 介面即可完成工作。

  • BaseAdapter是第二層,在這一層引入了資料集,用List<T>儲存。實現IViewGroupAdapter的方法,過載一個三引數的getView()方法,供子類去實現。

  • SingleAdapter是第三層,一個簡化的Adapter,只支援單種Item,以LayoutId 構建View。實現getView()方法,並暴露出onBindView()供使用者快速使用。

  • MulTypeAdapter也同處第三層,一個支援多種Item的Adapter。依賴IMulTypeHelper介面,利用其getItemLayoutId() 方法去實現getView()方法,並暴露出onBindView()供使用者快速使用。

頂層介面設計

頂層介面,即IViewGroupAdapter

根據迪米特法則(最少知道原則),我們應該抽象出一個頂層的介面,對ViewGroup暴露出最少的方法供使用。

我們想一下,對於ViewGroup,它最少只需要哪些就能完成我們的需求。

  • ChildView是什麼---> View
  • 有多少ChildView 需要 新增--->count
    所以,我們的最頂層介面如下編寫:
public interface IViewGroupAdapter {
    /**
     * ViewGroup呼叫獲取ItemView
     *
     * @param parent
     * @param pos
     * @return
     */
    View getView(ViewGroup parent, int pos);

    /**
     * ViewGroup呼叫,得到ItemCount
     *
     * @return
     */
    int getCount();
}複製程式碼

ok,程式碼寫到這裡,後面的我們暫且不提,我們就可以寫動態addView的工具類了。因為我們的ViewGroup依賴的所有資訊都由IViewGroupAdapter這個介面提供了。

工具類

ViewGroupUtils 是為任意ViewGroup 動態addView的工具類,不考慮點選事件的情況下,只依賴於 IViewGroupAdapter 介面即可完成工作。
如下編寫:

    /**
     * 為任意ViewGroup 新增ItemViews.
     *
     * @param viewGroup               必傳
     * @param adapter                 必傳,至少提供要add的View和需要add的count
     * @param removeViews             是否需要remove掉之前的Views
     */
    public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter
            , boolean removeViews) {
        if (viewGroup == null || adapter == null) {
            return;
        }
        //如果需要remove掉之前的Views
        if (removeViews && viewGroup.getChildCount() > 0) {
            viewGroup.removeAllViews();
        }
        //開始新增子Views,通過Adapter獲得需要新增的Count
        int count = adapter.getCount();
        for (int i = 0; i < count; i++) {
            //通過Adapter獲得ItemView
            View itemView = adapter.getView(viewGroup, i);
            viewGroup.addView(itemView);

        }
    }複製程式碼

如此即可完成 動態給任意ViewGroup addView 的工作。

不過我們開頭提過,我還是想引入ItemView的點選和長按事件的。但是關於這兩個ItemListener,還是有一些東西要考慮的。

ItemListener的設計

為ViewGroup提供OnItemClickListener,有個問題需要考慮:
如果使用者呼叫了setOnItemClickListener,且在Adapter裡自己又對ItemView設定了OnClickListener,那麼究竟該觸發哪個Listener,即它們的優先順序。

我們不應該自己靠腦子想答案,還是參照系統原有的設計比較好。既然ListView提供了OnItemClickListener,那麼我們參照它的設計來就行。

先說結論:通過參照ListView的原始碼,以及實驗得知,對ItemViewOnClickListener優先順序 > ViewGroup的OnItemClickListener

為什麼?答案在原始碼中,感興趣看,不感興趣直接跳過。

從AbsListView的onTouchEvent()->onTouchUp()->PerformClick->performItemClick-> AdapterView.performItemClick() -> AdapterView.mOnItemClickListener,即可找到答案。

這裡不詳細分析原始碼,不是本文重點,簡單的說,從入口處是AbsListView的onTouchEvent() ,我們可以知道,ListView本身並沒有干預ItemView的點選事件(即沒有為其設定OnClickListener),是在ItemView不消耗Touch事件時 才進行Item點選事件的觸發。
因此若ItemView設定了OnClickListener,AbsListView的onTouchEvent()將收不到MotionEvent.ACTION_UP事件,因而也不會觸發OnItemClickListener。所以這決定了ItemViewOnClickListener優先順序高。

還有一個問題,我們可以通過View.hasOnClickListeners()這個方法來判斷View是否設定了OnClickListener,但是這個方法在API15才加入,為了能相容低版本,我採用了另一種方法判斷,itemView.isClickable(),如果true,我當做有點選事件,如果false,我當做沒有。

其實在ListView中這個問題也是一樣的,itemView.isClickable()為true的話,點選事件就被攔截住,不會分發至AbsListView的onTouchEvent()裡了。所以我們這麼寫是沒問題,並且是正確的。

ItemListener的完整實現:

既然如此,那麼我們在程式中,應該如下編寫:

        for (int i = 0; i < count; i++) {
            View itemView = adapter.getView(viewGroup, i);
            viewGroup.addView(itemView);
            //新增點選事件
            if (null != onItemClickListener && !itemView.isClickable()) {
                final int finalI = i;
                itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        onItemClickListener.onItemClick(viewGroup, view, finalI);
                    }
                });
            }
            //新增點選事件
            if (null != onItemLongClickListener && !itemView.isLongClickable()) {
                final int finalI = i;
                itemView.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View view) {
                        return onItemLongClickListener.onItemLongClick(viewGroup, view, finalI);
                    }
                });
            }
        }複製程式碼

所以完整的addViews()如下:

    /**
     * 為任意ViewGroup 新增ItemViews.
     *
     * @param viewGroup               必傳
     * @param adapter                 必傳,至少提供要add的View和需要add的count
     * @param removeViews             是否需要remove掉之前的Views
     * @param onItemClickListener     Item點選事件
     * @param onItemLongClickListener Item長按事件
     */
    public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter
            , boolean removeViews
            , final OnItemClickListener onItemClickListener
            , final OnItemLongClickListener onItemLongClickListener) {
        if (viewGroup == null || adapter == null) {
            return;
        }
        //如果需要remove掉之前的Views
        if (removeViews && viewGroup.getChildCount() > 0) {
            viewGroup.removeAllViews();
        }
        //開始新增子Views,通過Adapter獲得需要新增的Count
        int count = adapter.getCount();
        for (int i = 0; i < count; i++) {
            //通過Adapter獲得ItemView
            View itemView = adapter.getView(viewGroup, i);
            viewGroup.addView(itemView);
            //新增點選事件,itemView之前沒有點選事件才會去設定
            if (null != onItemClickListener && !itemView.isClickable()) {
                final int finalI = i;
                itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        onItemClickListener.onItemClick(viewGroup, view, finalI);
                    }
                });
            }
            //新增長按事件itemView之前沒有長按事件才會去設定
            if (null != onItemLongClickListener && !itemView.isLongClickable()) {
                final int finalI = i;
                itemView.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View view) {
                        return onItemLongClickListener.onItemLongClick(viewGroup, view, finalI);
                    }
                });
            }
        }
    }複製程式碼

實際中,我們可能不需要設定Listener,為了快速使用,我又提供了兩個過載方法:

    /**
     * 為任意ViewGroup 新增ItemViews.
     * 並且會清除掉之前所有add過的View
     *
     * @param viewGroup 必傳
     * @param adapter   必傳,至少提供要add的View和需要add的count
     */
    public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter) {
        addViews(viewGroup, adapter, true, null, null);
    }

    /**
     * 為任意ViewGroup 新增ItemViews.
     * 並且會清除掉之前所有add過的View
     *
     * @param viewGroup           必傳
     * @param adapter             必傳,至少提供要add的View和需要add的count
     * @param onItemClickListener Item點選事件
     */
    public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter
            , final OnItemClickListener onItemClickListener) {
        addViews(viewGroup, adapter, true, onItemClickListener, null);
    }複製程式碼

若不在addViews()裡設定ItemListener,也可以通過setOnItemClickListener()setOnItemLongClickListener() 設定,不過這兩個方法必須在addViews()方法之後呼叫:

    /**
     * 為任意ViewGroup設定OnItemClickListener.
     * 該方法必須在addViews()方法之後呼叫,否則無效。
     * 因為ItemView 必須被新增在ViewGroup裡才能遍歷到。
     * 建議直接在addViews()方法裡傳入OnItemClickListener進行設定,效能更高
     *
     * @param viewGroup
     * @param onItemClickListener
     */
    public static void setOnItemClickListener(final ViewGroup viewGroup, final OnItemClickListener onItemClickListener) {
        if (viewGroup == null || onItemClickListener == null) {
            return;
        }
        int childCount = viewGroup.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View itemView = viewGroup.getChildAt(i);
            //itemView之前沒有點選事件才會去設定
            if (null != itemView && !itemView.isClickable()) {
                final int finalI = i;
                itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        onItemClickListener.onItemClick(viewGroup, itemView, finalI);
                    }
                });
            }
        }
    }


    /**
     * 為任意ViewGroup設定OnItemLongClickListener.
     * 該方法必須在addViews()方法之後呼叫,否則無效。
     * 因為ItemView 必須被新增在ViewGroup裡才能遍歷到。
     * 建議直接在addViews()方法裡傳入OnItemLongClickListener進行設定,效能更高
     *
     * @param viewGroup
     * @param onItemLongClickListener
     */
    public static void setOnItemLongClickListener(final ViewGroup viewGroup, final OnItemLongClickListener onItemLongClickListener) {
        if (viewGroup == null || onItemLongClickListener == null) {
            return;
        }
        int childCount = viewGroup.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View itemView = viewGroup.getChildAt(i);
            //itemView之前沒有長按事件才會去設定
            if (null != itemView && !itemView.isLongClickable()) {
                final int finalI = i;
                itemView.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View view) {
                        return onItemLongClickListener.onItemLongClick(viewGroup, itemView, finalI);
                    }
                });
            }
        }
    }複製程式碼

Adapter

終於到了我們的重頭戲,Adapter。

BaseAdapter

BaseAdapter是第二層,在這一層引入了資料集,用List<T>儲存。實現IViewGroupAdapter的方法,過載一個三引數的getView()方法,供子類去實現。

它和我們平時寫的ListView、RecyclerView的Adapter就比較像了,我也是參照平時的寫法。

核心就是實現IViewGroupAdaptergetView(ViewGroup parent, int pos)方法,增加一個資料,工作轉交給三引數的getView(ViewGroup parent, int pos, T data)方法。

子類應該 實現 getView(ViewGroup parent, int pos, T data)方法,在其中inflate or new 出 ItemView,並繫結資料

public abstract class BaseAdapter<T> implements IViewGroupAdapter {
    protected List<T> mDatas;
    protected Context mContext;
    protected LayoutInflater mInflater;

    /**
     * ViewGroup呼叫獲取ItemView,create bind一起做
     *
     * @param parent
     * @param pos
     * @return
     */
    @Override
    public View getView(ViewGroup parent, int pos) {
        return getView(parent, pos, mDatas.get(pos));
    }

    /**
     * 實際的createItemView的地方
     *
     * @param parent
     * @param pos
     * @param data
     * @return
     */
    public abstract View getView(ViewGroup parent, int pos, T data);

    /**
     * ViewGroup呼叫,得到ItemCount
     *
     * @return
     */
    @Override
    public int getCount() {
        return mDatas != null ? mDatas.size() : 0;
    }

}複製程式碼

SingleAdapter

SingleAdapter是第三層,一個簡化的Adapter,只支援單種Item,以LayoutId 構建View。實現getView()方法,並暴露出onBindView()供使用者快速使用。

使用時,一般將資料結構的泛型傳入,配合建構函式傳入的ItemLayoutId使用。

它繼承自BaseAdapter,所以它了實現getView(ViewGroup parent, int pos, T data)方法。在根據傳入的itemLayoutId inflate出ItemView後,會回撥onBindView(ViewGroup parent, View itemView, T data, int pos)方法,並返回ItemView供ViewGroup使用。

onBindView(ViewGroup parent, View itemView, T data, int pos)方法裡,我們完成資料繫結的工作。例如載入圖片,也可以設定點選事件。


public abstract class SingleAdapter<T> extends BaseAdapter<T> {

    private int mItemLayoutId;

    @Override
    public View getView(ViewGroup parent, int pos, T data) {
        //實現getView
        View itemView = /*onCreateView(parent, pos)*/mInflater.inflate(mItemLayoutId, parent, false);
        onBindView(parent, itemView, data, pos);
        return itemView;
    }

    /**
     * 暴漏這個 讓外部bind資料
     *
     * @param parent
     * @param itemView
     * @param data
     * @param pos
     */
    public abstract void onBindView(ViewGroup parent, View itemView, T data, int pos);

}複製程式碼

MulTypeAdapter

MulTypeAdapter也同處第三層,一個支援多種Item的Adapter。依賴IMulTypeHelper介面,利用其getItemLayoutId() 方法去實現getView()方法,並暴露出onBindView()供使用者快速使用。

public abstract class MulTypeAdapter<T extends IMulTypeHelper> extends BaseAdapter<T> {

    @Override
    public View getView(ViewGroup parent, int pos, T data) {
        View itemView = mInflater.inflate(data.getItemLayoutId(), parent, false);
        onBindView(parent, itemView, data, pos);
        return itemView;
    }

    /**
     * 暴漏這個 讓外部bind資料
     *
     * @param parent
     * @param itemView
     * @param data
     * @param pos
     */
    public abstract void onBindView(ViewGroup parent, View itemView, T data, int pos);
}複製程式碼

IMulTypeHelper介面,同樣簡單:

public interface IMulTypeHelper {
    int getItemLayoutId();
}複製程式碼

總結

程式碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/a…

因為想支援任意ViewGroup,對ViewGroup程式碼無侵入性,因而對部分功能進行了取捨,例如設定OnItemXXXListener,如果採用繼承ViewGroup,嵌入程式碼,可以做到不強制addViews()setOnItemClickListener()順序。

通過上文我們能感受到一些面向介面程式設計的奧義,例如我們只需要設計好頂層的介面IViewGroupAdapter,在沒寫剩下的xxxAdapter時,工具類都可以寫好,編譯通過。
這在以後擴充套件時,例如,我資料集不能用List來儲存了,我可能是多個List or Map 等等,只需要實現IViewGroupAdapter介面即可定製一個Adapter。其他程式碼不需任何修改。

有人說過,設計模式怎麼學,就是先學完一遍所有的設計模式。然後再全部忘掉他們,只要記住SOLID原則即可。
我覺得很有道理,就像我之前一直在糾結代理模式和裝飾者模式的區別,後來我想,編碼時,我管它是什麼模式,只要寫出來的是易維護的程式碼即可。

to do list

  • 考慮加入複用快取池
  • 考慮替換onBindView()ItemView->通用的ViewHolder,這樣可以少寫一些findViewById()程式碼
  • 整合DataBinding 的通用Adapter入庫。
  • 整合 RecyclerView、ListView的通用Adapter入庫。
  • 加入一些自定義ViewGroup入庫,例如流式佈局,九宮格,Banner輪播圖。

下文預告:

一個用ScrollView很容易實現,但RecyclerView、ListView就無法實現的動畫效果,仿淘寶會員中心等級動畫。凸顯為所有ViewGroup增加Adapter模式的重要性。

DataBinding篇隆重登場

感興趣可以去閱讀
gold.xitu.io/post/584fbd…

轉載請標明出處:
gold.xitu.io/post/584d52…
本文出自:【張旭童的稀土掘金】(gold.xitu.io/user/56de21…)
程式碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/a…

相關文章