Android 基礎(三十五)~ RecyclerView多型別Item的正確實現姿勢
阿新 • • 發佈:2019-01-02
簡介
RecyclerView是我們開發過程中經常使用到的一個元素,原生的RecyclerView.Adapter基本上可以滿足一般的需求,關於RecyclerView的基礎介紹請移步:
關於多型別的Item,原生的Adapter可以通過getItemViewType返回對應的ViewHolder型別,然後在onCreateViewHolder傳入的type引數,生成不同的ViewHolder,更要命的是資料繫結過程中onBindViewHolder只傳入ViewHolder物件和position,針對不同的ViewHolder,我們只能通過instanceof和強制型別裝換來對不同的ViewHolder進行不同的資料操作。
這樣做的弊端就是,後續如果需要新增一個型別,幾乎需要修改Adapter類中的每一個方法,不利於維護。
類結構
目的
- 便於維護。增刪Item的型別不需要修改Adapter的程式碼;
- 條例清晰。不同型別的Item對應不同的Bean類,對應不用的ViewHolder,對應不同的layout
item type <----> layout <—> ViewHolder <----> java bean - 工廠介面。抽象出TypeFactory介面,針對同一個Bean,可以使用不同的layout和不同的ViewHolder。
思路
- 所有Bean類實現統一Visitable介面,實現type方法,返回對應的layout id;
- 建立BaseViewHolder使用泛型,T為Bean類。使用SparseArray儲存View,View的id作為唯一標識。
- 具體的ViewHolder類,繼承BaseViewHolder,傳入不同的Bean類,方便不同的ViewHolder中繫結不同的資料。
- Adapter實現中使用Visitable List同一處理列表資料,使用事項TypeFactory介面的例項返回ItemType,viewHolder等
程式碼結構
程式碼內容
Visitable介面
public interface Visitable { int type(TypeFactory typeFactory); }
TypeFactory介面
public interface TypeFactory {
int type(BannerBean bannerBean);
int type(ContentBean contentBean);
int type(SearchBean searchBean);
int type(DividerBean dividerBean);
BaseViewHolder createViewHolder(int type, View itemView);
}
BaseViewHolder抽象類
public abstract class BaseViewHolder<T> extends RecyclerView.ViewHolder{
SparseArray<View> mViews;
View mItemView;
public BaseViewHolder(View itemView) {
super(itemView);
mItemView = itemView;
mViews = new SparseArray<>();
}
public View getView(int resId) {
View view = mViews.get(resId);
if(view== null) {
view = mItemView.findViewById(resId);
mViews.put(resId, view);
}
return view;
}
public abstract void bindViewData(T data);
}
TypeFactory實現類~ItemTypeFactory
public class ItemTypeFactory implements TypeFactory {
public static final int BANNER_ITEM_LAYOUT = R.layout.rv_item_banner;
public static final int CONTENT_ITEM_LAYOUT = R.layout.rv_item_content;
public static final int SEARCH_ITEM_LAYOUT = R.layout.rv_item_search;
public static final int DIVIDER_ITEM_LAYOUT = R.layout.rv_item_divider;
@Override
public int type(BannerBean bannerBean) {
return BANNER_ITEM_LAYOUT;
}
@Override
public int type(ContentBean contentBean) {
return CONTENT_ITEM_LAYOUT;
}
@Override
public int type(DividerBean dividerBean){
return DIVIDER_ITEM_LAYOUT;
}
@Override
public int type(SearchBean dividerBean){
return SEARCH_ITEM_LAYOUT;
}
@Override
public BaseViewHolder createViewHolder(int type, View itemView) {
switch (type) {
case BANNER_ITEM_LAYOUT:
return new BannerViewHolder(itemView);
case SEARCH_ITEM_LAYOUT:
return new SearchViewHolder(itemView);
case CONTENT_ITEM_LAYOUT:
return new ContentViewHolder(itemView);
case DIVIDER_ITEM_LAYOUT:
return new DividerViewHolder(itemView);
default:
return null;
}
}
}
各種Java Bean類
BannerBean
public class BannerBean implements Visitable {
int[] mResIds;
public BannerBean(int[] mResIds) {
this.mResIds = mResIds;
}
public int[] getmResIds() {
return mResIds;
}
public void setmResIds(int[] mResIds) {
this.mResIds = mResIds;
}
@Override
public int type(TypeFactory typeFactory) {
return typeFactory.type(this);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="200dp">
<cn.bingoogolapple.bgabanner.BGABanner
android:id="@+id/bgabanner_header"
android:layout_width="match_parent"
android:layout_height="200dp">
</cn.bingoogolapple.bgabanner.BGABanner>
</LinearLayout>
ContentBean
public class ContentBean implements Visitable{
String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public ContentBean(String content) {
this.content = content;
}
@Override
public int type(TypeFactory typeFactory) {
return typeFactory.type(this);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_content"
android:textStyle="italic"
android:fontFamily="sans-serif-condensed"
android:textSize="16sp"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
DividerBean
public class DividerBean implements Visitable{
@Override
public int type(TypeFactory typeFactory) {
return typeFactory.type(this);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/divider">
</LinearLayout>
SearchBean
public class SearchBean implements Visitable {
@Override
public int type(TypeFactory typeFactory) {
return typeFactory.type(this);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="50dp"
android:padding="6dp">
<EditText
android:layout_marginLeft="30dp"
android:id="@+id/et_search"
android:layout_width="0dp"
android:layout_weight="9"
android:maxLines="1"
android:textSize="10sp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:hint="輸入文字搜尋..."
android:background="@drawable/bg_search"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/bt_search"
android:layout_width="0dp"
android:layout_weight="2"
android:src="@drawable/search"
android:layout_height="match_parent" />
</LinearLayout>
MultiRecyclerAdapter
public class MultiRecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder> {
List<Visitable> mData;
TypeFactory typeFactory;
public MultiRecyclerAdapter(List<Visitable> mData) {
this.mData = mData;
this.typeFactory = new ItemTypeFactory();
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
return typeFactory.createViewHolder(viewType, view);
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
holder.bindViewData(mData.get(position));
}
@Override
public int getItemViewType(int position) {
return mData.get(position).type(typeFactory);
}
@Override
public int getItemCount() {
return (mData != null ? mData.size() : 0);
}
}
示例
public class MultiActivity extends AppCompatActivity {
@BindView(R.id.rv_content)
RecyclerView rvContent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
ButterKnife.bind(this);
List<Visitable> beans = new ArrayList<>();
int[] resIds = {R.mipmap.banner_one, R.mipmap.banner_two, R.mipmap.banner_three, R.mipmap.banner_four, R.mipmap.banner_five};
beans.add(new BannerBean(resIds));
beans.add(new SearchBean());
beans.add(new DividerBean());
beans.add(new ContentBean("one"));
beans.add(new ContentBean("one"));
beans.add(new ContentBean("one"));
beans.add(new DividerBean());
beans.add(new ContentBean("two"));
beans.add(new ContentBean("two"));
beans.add(new ContentBean("two"));
beans.add(new DividerBean());
beans.add(new ContentBean("three"));
beans.add(new ContentBean("three"));
beans.add(new ContentBean("three"));
beans.add(new DividerBean());
beans.add(new ContentBean("four"));
beans.add(new ContentBean("four"));
beans.add(new ContentBean("four"));
beans.add(new DividerBean());
MultiRecyclerAdapter multiRecyclerAdapter = new MultiRecyclerAdapter(beans);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
rvContent.setAdapter(multiRecyclerAdapter);
rvContent.setLayoutManager(linearLayoutManager);
}
}
簡單效果
寫在最後
github上也有很多開源的萬能Adapter,但是個人感覺這個思路雖然實現上程式碼會相對較多,但是層次分析,便於維護,擴充套件性較好。想要新增一個新型別的ItemType,只需要稍作修改即可快速完成,不用修改Adapter的程式碼。