基于mvvm打造通用的Adapter

前言

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

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

BaseViewTypeEntity

所有數(shù)據(jù)源單個實體對象的基類,其代碼十分簡單

public class BaseViewTypeEntity {

    public int viewType;

    public BaseViewTypeEntity() {
    }

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

其內(nèi)部只有一個viewType成員變量,用來區(qū)分對應的itemView的類型。

CommonViewHolder

通用的ViewHolder

public class CommonViewHolder extends RecyclerView.ViewHolder {

    private ViewDataBinding mDataBinding;

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

    }

    /**
     * 綁定數(shù)據(jù)
     * 將adapter、entity、position 都設置給相應的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對象,并且將數(shù)據(jù)設置給layout,剩下的事情就交給databingding來處理了。
這里給databingding設置了三個數(shù)據(jù)源(adapter、entity、position),那么對用的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方法返回對用數(shù)據(jù)源BaseViewTypeEntity對象的viewtype;onCreateViewHolder方法根據(jù)viewType生成對應的CommonViewHolder;onBindViewHolder方法則直接調(diào)用CommonViewHolder的convert方法設置變量給layout。

我們在來看一下ViewHolderHelper的代碼

public class ViewHolderHelper {

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

    public static Class enumClazz; //客戶端layout和viewholder清單對應的class

    /**
     * 通過view type來獲取對應的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;

    }
}

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

以上就是我們打造通用Adapter的核心代碼。接下來我們用他來寫一個小demo。

  1. 將以上的代碼作為library引入到你的工程里面,當然也可以直接將代碼直接拷貝到你的工程里。
  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;
    }
}

這個是所有itemview的布局文件和viewType的清單文件,其中l(wèi)ayoutRes對應布局文件,枚舉的索引對應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)建對應的數(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(),那么他就是對應的R.layout.holder_item_test01布局文件。

5.創(chuàng)建包含RecyclerView的界面,并且設置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ā)有以下幾個優(yōu)點:

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

擴展

  1. 如何打造通用的列表fragment。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容