前言
在手機(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并蝗。
- 將以上的代碼作為library引入到你的工程里面祭犯,當(dāng)然也可以直接將代碼直接拷貝到你的工程里。
- 創(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粥惧。
- 在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):
- 整個(gè)項(xiàng)目只有一個(gè)Adapter和一個(gè)ViewHolder,減少了代碼數(shù)量督函。
- 所有的列表item的布局文件全部在一個(gè)枚舉類里面列舉出來了嘀粱,所有的界面布局一目了然,方便代碼的查找和復(fù)用辰狡。
- 符合數(shù)據(jù)驅(qū)動(dòng)界面顯示锋叨。
擴(kuò)展
- 如何打造通用的列表fragment。