編輯:Android資訊
雖然現在很多新的項目都在使用RecyclerView,但是很多開發者在一些場景中還是傾向使用ListView或者GridView,然後就是需要寫許多的Adapter。一次項目組在新啟動一個新項目的時候,有個同事拿來了一個網上說的萬能Adapter,在使用的時候發現即使在單個視圖類型一旦邏輯判斷比較復雜情況下非常不方便,更不用說在適配器Adapter中使用多視圖類型了,這裡僅是個人觀點,也許沒有掌握到精華,這是有關萬能適配器Adapter的一片博文 Android 快速開發系列 打造萬能的ListView GridView 適配器 。
當然了隨著RecyclerView的使用,網上也有很多有關對RecyclerView多視圖類型Adapter封裝的博客,MultiType 3.0是一個大神寫的比較全面的Adapter,這篇博客Android 復雜的多類型列表視圖新寫法:MultiType 3.0有詳細的用法。萬能適配器Adapter自己使用不是很方便,於是就參看RecyclerView中Adapter的實現方式進行對BaseAdapter進行了簡單的封裝,封裝的目一是為了少寫代碼,另外一個就是讓邏輯看上去更清晰一些。我們知道在RecyclerView的Adapter實現中它將視圖創建與數據綁定進行了分離,同時將對View的查找創建也剝離開來了,本文就主要介紹如何將BaseAdapter的使用封裝為跟RecyclerView的Adapter使用方式一致。由於很多時候在Adapter中我們都是使用的簡單的視圖類型,即單類型視圖,因此本文將單視圖類型的Adapter單獨封裝了一下,比使用多視圖類型的Adapter使用了更嚴格的數據類型檢查,同時在使用上也方便了許多。


在使用RecyclerView的Adapter的時候我們首先需要繼承RecyclerView的一個靜態內部類Adapter,然後重寫三個方法,實際上下面三個方法是必須要重寫的,因為都是抽象方法。
一般情況下重寫上面三個方法就可以,但是如果存在多視圖類型,在第三個方法
onCreateViewHolder()方法中我們也可以看到有一個參數是viewType,該參數作用就是針對不同的viewType需要創建不同的ViewHolder,因此還需要重寫一個方法getItemViewType(int position),針對多視圖類型同BaseAdapter實現方式倒是很像,在BaseAdapter中這是需要除此之外還要重寫一個方法getViewTypeCount(),但是在RecyclerView的Adapter中不需要該方法。
private class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
@Override
public int getItemCount() {
return COUNT;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.textView.setText("TEXT_" + position);
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_text, parent, false);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
}
private static class MyViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.textView);
}
}
private class MyAdapter extends RecyclerView.Adapter<ViewHolder> {
@Override
public int getItemCount() {
return COUNT;
}
@Override
public int getItemViewType(int position) {
return position % 2 == 0 ? TYPE_IMAGE : TYPE_TEXT;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
int type = getItemViewType(position);
switch (type) {
case TYPE_TEXT:
((MyTextHolder) holder).textView.setText("TEXT_" + position);
break;
case TYPE_IMAGE:
((MyImageHolder) holder).imageView.setImageResource(R.drawable.image);
break;
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
ViewHolder holder = null;
switch (viewType) {
case TYPE_TEXT:
view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_text, parent, false);
holder = new MyTextHolder(view);
break;
case TYPE_IMAGE:
view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_image, parent, false);
holder = new MyImageHolder(view);
break;
}
return holder;
}
}
private class MyTextHolder extends RecyclerView.ViewHolder {
private TextView textView;
public MyTextHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.textView);
}
}
private class MyImageHolder extends ViewHolder {
private ImageView imageView;
public MyImageHolder(View itemView) {
super(itemView);
imageView = (ImageView) itemView.findViewById(R.id.imageView);
}
}
在自定義基類之前,先簡單分析一下,我們需要自定義一個支持單種視圖的Adapter,還要自定義一個支持多種視圖類型的Adapter,兩個類都要繼承BaseAdapter,先將兩個類都公用的部分抽取出來定義為MyAdapter。
public abstract class MyAdapter<T> extends BaseAdapter {
protected List<T> dataList = new ArrayList<>();
protected Context context;
protected LayoutInflater inflater;
public MyAdapter(Context context) {
this.context = context;
inflater = LayoutInflater.from(context);
}
public void setDataList(List<T> dataList) {
this.dataList = dataList;
notifyDataSetChanged();
}
@Override
public int getCount() {
if (null == dataList) {
return 0;
}
return dataList.size();
}
@Override
public T getItem(int position) {
return dataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
}
在RecyclerView的Adapter實現中是沒有getView()方法的,下面我們就分析一下getView()方法如何拆分,一般情況下我們在實現getView()方法都是如下流程。
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (null == convertView) {
//填充布局
convertView=inflater.inflate(R.layout.item_layout, parent,false);
holder = new ViewHolder();
//通過ID查詢控件
holder.textView=(TextView)convertView.findViewById(R.id.textView);
holder.imageView=(ImageView)convertView.findViewById(R.id.imageView);
convertView .setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//賦值邏輯
return convertView;
}
//一個空的ViewHolder
public static class ViewHolder{
TextView textView;
ImageView imageView;
}
Java編程比較流行的一種編程方式不是說面向接口編程嗎,在Android開發中也有一個開發方式叫做面向Holder的編程,上面代碼是傳統的實現ViewHolder的方式,說句實現話就沒做什麼事,就是作為一個載體承載著我們需要的控件。我們讓ViewHolder多做一些事情,讓它在convertView==null情況下需要做的多數邏輯都放到ViewHolder中去。
public class ViewHolder {
private final View itemView;
public ViewHolder(View itemView) {
if (null == itemView) {
throw new IllegalArgumentException("itemView must not be null");
} else {
this.itemView = itemView;
itemView.setTag(this);
}
}
public View getItemView() {
return itemView;
}
}
在ViewHolder中的itemView就是getView()方法中的convertView,這裡剛好是條目的根View,類似RecyclerView中ViewHolder構造方法中itemView。由於不同的視圖需要創建不同的ViewHolder,因此我們可以將創建ViewHolder的方法設置為抽象的方法暴露出去,另外賦值的時候我們也需要根據具體的業務進行賦值,同樣設置一個抽象方法。
public abstract class SimpleAdapter<T,VH extends ViewHolder> extends MyAdapter<T> {
public SimpleAdapter(Context context) {
super(context);
}
public View getView(int position, View convertView, ViewGroup parent) {
VH holder = null;
if (null == convertView) {
holder = onCreateViewHolder(parent);
convertView = holder.getItemView();
} else {
holder = (VH) convertView.getTag();
}
onBindViewHolder(holder, position);
return convertView;
}
public abstract void onBindViewHolder(VH holder, int position);
public abstract VH onCreateViewHolder(ViewGroup parent);
}
在設置多視圖類型的Adapter的時候只需要在創建ViewHolder的時候多傳入一個viewType的參數即可。
public abstract class MultiAdapter<T> extends MyAdapter<T> {
public MultiAdapter(Context context) {
super(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (null == convertView) {
holder = onCreateViewHolder(parent, getItemViewType(position));
convertView = holder.getItemView();
} else {
holder = (ViewHolder) convertView.getTag();
}
onBindViewHolder(holder, position);
return convertView;
}
public abstract void onBindViewHolder(ViewHolder holder, int position);
public abstract ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
}
public class TextAdapter extends SimpleAdapter<String, TextAdapter.TextHolder> {
public TextAdapter(Context context) {
super(context);
}
@Override
public void onBindViewHolder(TextHolder holder, int position) {
holder.textView.setText(getItem(position));
}
@Override
public TextHolder onCreateViewHolder(ViewGroup parent) {
View convertView=inflater.inflate(R.layout.item_text, parent, false);
return new TextHolder(convertView);
}
static class TextHolder extends ViewHolder{
public TextView textView;
public TextHolder(View itemView) {
super(itemView);
textView=(TextView) itemView.findViewById(R.id.textView);
}
}
}
這裡我們使用了兩個泛型,一個是ViewHolder中支持的數據類型String,另外一個就是我們需要創建的ViewHolder,這樣在onCreateViewHolder方法的返回值就會自動返回我們自定義的ViewHolder,有關泛型更多的知識可以參看Java泛型使用解析,單視圖類型Adapter的使用比RecyclerView的Adapter還要方便許多。
public class RichAdapter extends MultiAdapter<String> {
private static final int TEXT = 0;
private static final int PIC = 1;
public RichAdapter(Context context) {
super(context);
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
if (position % 3 == 0) {
return PIC;
} else {
return TEXT;
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
switch (getItemViewType(position)) {
case TEXT:
TextHolder textHolder=(TextHolder) holder;
textHolder.textView.setText(getItem(position));
break;
case PIC:
ImageHolder imageHolder=(ImageHolder) holder;
imageHolder.imageView.setImageResource(R.drawable.image);
break;
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = null;
ViewHolder holder = null;
switch (viewType) {
case TEXT:
itemView = inflater.inflate(R.layout.item_text, parent, false);
holder = new TextHolder(itemView);
break;
case PIC:
itemView = inflater.inflate(R.layout.item_image, parent, false);
holder = new ImageHolder(itemView);
break;
}
return holder;
}
private static class TextHolder extends ViewHolder {
TextView textView;
public TextHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.textView);
}
}
private static class ImageHolder extends ViewHolder {
ImageView imageView;
public ImageHolder(View itemView) {
super(itemView);
imageView = (ImageView) itemView.findViewById(R.id.imageView);
}
}
}
這裡的使用情況跟RecyclerView的使用幾乎是一模一樣,唯一不一樣的地方就是多寫了一個getViewTypeCount()方法,在ListView或者GridView使用BaseAdapter實現多種類型視圖的時候該方法必須要重寫。
Android性能優化之內存篇
Google近期在Udacity上發布了Android性能優化的在線課程,分別從渲染,運算與內存,電量幾個方面介紹了如何去優化性能,這些課程是Google之前在Y
Android 7.0 應用啟動流程分析
最近在為自己Moto G定制Rom,順便重新讀了一遍Android 7.0的相關源碼,特此記錄當做筆記. 在開始正文之前,首先要明白冷啟動和熱啟動.所謂冷啟動就是
Android View 自定義 RangeSeekBar 范圍選擇器
前段時間群裡兄弟項目中有類似這樣的需求 我看到兄弟受苦受難,於心不忍。又因事不關己,打算高高掛起。正在愛恨糾結之時,日神對我說:沒事多造點輪子,你的人生會有很多
Windows下搭建React Native Android開發環境詳解
最近看到React Native好像好厲害的樣子,好奇心驅使之下體驗了一下並將在Window下搭建React Natvie Android環境的步驟記錄下來,並