基于mvvm打造通用的Adapter

前言

在手機(jī)軟件的設(shè)計(jì)中踩衩,充斥了大量的列表界面痊班。而在我們通常的開發(fā)過程中淘菩,開發(fā)一個(gè)列表界面需要新建一個(gè)fragment或者activity作為界面載體贮乳,新建一個(gè)layout作為布局文件(內(nèi)包含一個(gè)recyclerview控件),新建一個(gè)adapter和一個(gè)viewholder來綁定數(shù)據(jù)瘾婿。至少需要新建這4個(gè)文件才能完成一個(gè)列表界面的開發(fā)工作蜻牢。

本文旨在減少列表開發(fā)過程中一些不必要的重復(fù)的開發(fā)工作。

BaseViewTypeEntity

所有數(shù)據(jù)源單個(gè)實(shí)體對(duì)象的基類偏陪,其代碼十分簡單

public class BaseViewTypeEntity {

    public int viewType;

    public BaseViewTypeEntity() {
    }

    public BaseViewTypeEntity(int viewType) {
        this.viewType = viewType;
    }
}

其內(nèi)部只有一個(gè)viewType成員變量抢呆,用來區(qū)分對(duì)應(yīng)的itemView的類型。

CommonViewHolder

通用的ViewHolder

public class CommonViewHolder extends RecyclerView.ViewHolder {

    private ViewDataBinding mDataBinding;

    public CommonViewHolder(@NonNull View itemView) {
        super(itemView);
        try {
            // 若itemview不是databing布局笛谦,則會(huì)拋出異常
            mDataBinding = DataBindingUtil.bind(itemView);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 綁定數(shù)據(jù)
     * 將adapter抱虐、entity、position 都設(shè)置給相應(yīng)的view
     * */
    public void convert(CommonAdapter adapter, BaseViewTypeEntity entity, int position) {
        if (mDataBinding != null) {

            mDataBinding.setVariable(ViewHolderHelper.BR_ADAPTER, adapter);

            mDataBinding.setVariable(ViewHolderHelper.BR_ENTITY, entity);

            mDataBinding.setVariable(ViewHolderHelper.BR_POSITION, position);

        }
    }
}

這里主要就是生成databinding對(duì)象饥脑,并且將數(shù)據(jù)設(shè)置給layout恳邀,剩下的事情就交給databingding來處理了。
這里給databingding設(shè)置了三個(gè)數(shù)據(jù)源(adapter灶轰、entity谣沸、position),那么對(duì)用的layout如下

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

    <data>
        <import type="android.view.View"/>
        <variable
            name="adapter"
            type="com.wanggang.library.commonlist.CommonAdapter" />
        <variable
            name="entity"
            type="com.wanggang.commonlist.test.Test04Entity" />
        <variable
            name="position"
            type="Integer" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="@android:color/white">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="@{entity.title}"
            android:textSize="16sp"
            android:textColor="@android:color/black"
            android:paddingLeft="16dp"
            android:paddingRight="16dp"
            android:gravity="center"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingRight="16dp"
            android:textSize="14sp"
            android:gravity="right|center"
            android:text="@{entity.text}"
            android:textColor="@android:color/black"
            android:hint="@{entity.hint}"
            android:drawablePadding="16dp"
            android:drawableRight="@drawable/icon_arrow_right"/>

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:visibility="@{entity.showLine ? View.VISIBLE : View.GONE}"
            android:layout_alignParentBottom="true"
            android:background="#bebebe"/>
    </RelativeLayout>
</layout>

CommonAdapter

最后來看一下adapter的通用寫法

public class CommonAdapter extends RecyclerView.Adapter<CommonViewHolder> {

    /**
     * 數(shù)據(jù)源
     */
    private List<BaseViewTypeEntity> dataSource;

    public CommonAdapter() {
        dataSource = new ArrayList<>();
    }

    @Override
    public int getItemViewType(int position) {
        return dataSource.get(position).viewType;
    }

    @NonNull
    @Override
    public CommonViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
        return ViewHolderHelper.getViewHolderByViewType(viewGroup, viewType);
    }

    @Override
    public void onBindViewHolder(@NonNull CommonViewHolder commonViewHolder, int i) {
        commonViewHolder.convert(this, dataSource.get(i), i);
    }

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

    public void addSource(List<BaseViewTypeEntity> dataList) {
        dataSource.addAll(dataList);
    }

    public void addSource(BaseViewTypeEntity data) {
        dataSource.add(data);
    }

    public void clear() {
        dataSource.clear();
    }
}

其中g(shù)etItemViewType方法返回對(duì)用數(shù)據(jù)源BaseViewTypeEntity對(duì)象的viewtype笋颤;onCreateViewHolder方法根據(jù)viewType生成對(duì)應(yīng)的CommonViewHolder乳附;onBindViewHolder方法則直接調(diào)用CommonViewHolder的convert方法設(shè)置變量給layout。

我們?cè)趤砜匆幌耉iewHolderHelper的代碼

public class ViewHolderHelper {

    private static Object[] viewHolderEnums;
    private static Field layoutField;

    public static Class enumClazz; //客戶端layout和viewholder清單對(duì)應(yīng)的class

    /**
     * 通過view type來獲取對(duì)應(yīng)的viewholder
     */
    public static CommonViewHolder getViewHolderByViewType(ViewGroup viewGroup, int viewType) {

        if (viewHolderEnums == null) {
            // 獲取所有的枚舉類型
            viewHolderEnums = enumClazz.getEnumConstants();
        }
        if (layoutField == null) {
            try {
                layoutField = enumClazz.getField("layoutRes");
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }

        try {

            Object obj = viewHolderEnums[viewType];
            int layoutRes = layoutField.getInt(obj);
            return new CommonViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(layoutRes, viewGroup, false));

        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return null;

    }
}

這個(gè)其實(shí)就是使用java 的反射機(jī)制伴澄,根據(jù)viewType獲取對(duì)應(yīng)的枚舉對(duì)象赋除,然后通過枚舉對(duì)象里面的布局文件layoutRes,生成CommonViewHolder非凌。使用反射機(jī)制的主要原因是為了使以上代碼與業(yè)務(wù)模塊分離举农,業(yè)務(wù)模塊只需要將對(duì)應(yīng)的枚舉類傳遞過來就行了。

以上就是我們打造通用Adapter的核心代碼敞嗡。接下來我們用他來寫一個(gè)小demo并蝗。

  1. 將以上的代碼作為library引入到你的工程里面祭犯,當(dāng)然也可以直接將代碼直接拷貝到你的工程里。
  2. 創(chuàng)建枚舉文件CommonAdapterEnum
public enum CommonAdapterEnum {

    /**
     * 所有item view 的清單
     * */
    TEST01(R.layout.holder_item_test01),
    TEST02(R.layout.holder_item_test02),
    TEST03(R.layout.holder_item_test03),
    TEST04(R.layout.holder_item_test04),
    PADDING12(R.layout.holder_padding12);

    public int layoutRes;

    CommonAdapterEnum(int layoutRes) {
        this.layoutRes = layoutRes;
    }
}

這個(gè)是所有itemview的布局文件和viewType的清單文件滚停,其中l(wèi)ayoutRes對(duì)應(yīng)布局文件,枚舉的索引對(duì)應(yīng)viewType粥惧。

  1. 在Application的onCreate方法里調(diào)用以下方法
    ViewHolderHelper.BR_ADAPTER = BR.adapter;
    ViewHolderHelper.BR_ENTITY = BR.entity;
    ViewHolderHelper.BR_POSITION = BR.position;
    ViewHolderHelper.enumClazz = CommonAdapterEnum.class;

4.創(chuàng)建對(duì)應(yīng)的數(shù)據(jù)模型键畴,例如:

public class Test01Entity extends BaseViewTypeEntity {

    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public Test01Entity(String text) {
        this.text = text;
        viewType = CommonAdapterEnum.TEST01.ordinal();
    }

}

Test01Entity繼承自BaseViewTypeEntity,并且他的viewType = CommonAdapterEnum.TEST01.ordinal()突雪,那么他就是對(duì)應(yīng)的R.layout.holder_item_test01布局文件起惕。

5.創(chuàng)建包含RecyclerView的界面,并且設(shè)置CommonAdapter以及數(shù)據(jù)源咏删。

public class MainActivity extends AppCompatActivity {

    CommonAdapter mAdapter;

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

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

        List<BaseViewTypeEntity> list = new ArrayList<>();
        list.add(new Test02Entity("111111111"));

        Test03Entity test03Entity = new Test03Entity();
        test03Entity.setMenu1("模塊1");
        test03Entity.setMenu2("模塊2");
        test03Entity.setMenu3("模塊3");
        test03Entity.setMenu4("模塊4");
        list.add(test03Entity);

        list.add(new Test01Entity("111111111"));
        list.add(new Test01Entity("222222222"));
        list.add(new Test01Entity("333333333"));
        list.add(new Test01Entity("444444444"));
        list.add(new Test01Entity("555555555"));
        list.add(new Test01Entity("6666666666"));
        list.add(new Test01Entity("777777777"));
        list.add(new Test01Entity("888888888"));

        mAdapter.addSource(list);
        mAdapter.notifyDataSetChanged();
    }
}

總結(jié)

與傳統(tǒng)的寫法相比惹想,使用通用的Adapter開發(fā)有以下幾個(gè)優(yōu)點(diǎn):

  1. 整個(gè)項(xiàng)目只有一個(gè)Adapter和一個(gè)ViewHolder,減少了代碼數(shù)量督函。
  2. 所有的列表item的布局文件全部在一個(gè)枚舉類里面列舉出來了嘀粱,所有的界面布局一目了然,方便代碼的查找和復(fù)用辰狡。
  3. 符合數(shù)據(jù)驅(qū)動(dòng)界面顯示锋叨。

擴(kuò)展

  1. 如何打造通用的列表fragment。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宛篇,一起剝皮案震驚了整個(gè)濱河市娃磺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叫倍,老刑警劉巖偷卧,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吆倦,居然都是意外死亡听诸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門逼庞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛇更,“玉大人,你說我怎么就攤上這事赛糟∨扇危” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵璧南,是天一觀的道長掌逛。 經(jīng)常有香客問我,道長司倚,這世上最難降的妖魔是什么豆混? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任篓像,我火速辦了婚禮,結(jié)果婚禮上皿伺,老公的妹妹穿的比我還像新娘员辩。我一直安慰自己,他們只是感情好鸵鸥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布奠滑。 她就那樣靜靜地躺著,像睡著了一般妒穴。 火紅的嫁衣襯著肌膚如雪宋税。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天讼油,我揣著相機(jī)與錄音杰赛,去河邊找鬼。 笑死矮台,一個(gè)胖子當(dāng)著我的面吹牛乏屯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嘿架,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瓶珊,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了耸彪?” 一聲冷哼從身側(cè)響起伞芹,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝉娜,沒想到半個(gè)月后唱较,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡召川,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年南缓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荧呐。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汉形,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出倍阐,到底是詐尸還是另有隱情概疆,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布峰搪,位于F島的核電站岔冀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏概耻。R本人自食惡果不足惜使套,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一罐呼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侦高,春花似錦嫉柴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至侧馅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呐萌,已是汗流浹背馁痴。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肺孤,地道東北人罗晕。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像赠堵,于是被迫代替她去往敵國和親小渊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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