前言
在手機軟件的設計中,充斥了大量的列表界面。而在我們通常的開發(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。
- 將以上的代碼作為library引入到你的工程里面,當然也可以直接將代碼直接拷貝到你的工程里。
- 創(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。
- 在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)點:
- 整個項目只有一個Adapter和一個ViewHolder,減少了代碼數(shù)量。
- 所有的列表item的布局文件全部在一個枚舉類里面列舉出來了,所有的界面布局一目了然,方便代碼的查找和復用。
- 符合數(shù)據(jù)驅(qū)動界面顯示。
擴展
- 如何打造通用的列表fragment。