适配器模式实现与研究 - bei1999/work GitHub Wiki
将某个类的接口转换成客户端期望的另一个接口,目的是消除由于接口不匹配所造成的类的兼容性问题。
android 中的listview,recyclerview 中的adapter 的实现
- recyclerview 中的adapter
package com.bei.test.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Switch;
import android.widget.TextView;
import com.bei.test.R;
import com.bei.test.bean.NewsData;
import java.util.ArrayList;
import java.util.IllegalFormatCodePointException;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
/**
* Created by lzb on 16/12/8.
*/
public class CommandNewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context mContext;
private OnClickListener mOnClickListener;
private List<NewsData> newsDataList;
public CommandNewsAdapter(Context context) {
this.mContext = context;
}
public CommandNewsAdapter(Context context, List<NewsData> newsDataList) {
this.mContext = context;
this.newsDataList = new ArrayList<NewsData>();
if (newsDataList != null && !newsDataList.isEmpty()) {
this.newsDataList.addAll(newsDataList);
}
}
public void setOnClickListener(OnClickListener onClickListener) {
this.mOnClickListener = onClickListener;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View listItem = inflater.inflate(R.layout.layout_common_news_list, parent, false);
return new VHNormal(listItem);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof VHNormal) {
VHNormal vhNormal = (VHNormal) holder;
NewsData newsData = getItem(position);
if (newsData != null) {
vhNormal.textView2.setText(newsData.getName());
}
}
}
private NewsData getItem(int position) {
return newsDataList.get(position);
}
@Override
public int getItemCount() {
return newsDataList.size();
}
class VHNormal extends RecyclerView.ViewHolder implements View.OnClickListener {
// @Bind(R.id.button2)
// Button button2;
// @Bind(R.id.textView2)
// TextView textView2;
private Button button02;
TextView textView2;
public VHNormal(View itemView) {
super(itemView);
// ButterKnife.bind(itemView);
button02 = (Button) itemView.findViewById(R.id.button2);
textView2 = (TextView) itemView.findViewById(R.id.textView2);
button02.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (mOnClickListener == null) {
return;
}
// int position = getAdapterPosition();
// if (position == RecyclerView.NO_POSITION) {
// return;
// }
// ProjectTemplateBean bean = getItem(position);
switch (v.getId()) {
case R.id.button2:
mOnClickListener.onActionLiveClick("xxx");
break;
default:
break;
}
}
}
public interface OnClickListener {
void onActionLiveClick(String temp);
}
}
//下面是adapter中的代码实现
* Base class for an Adapter
*
* <p>Adapters provide a binding from an app-specific data set to views that are displayed
* within a {@link RecyclerView}.</p>
*/
public static abstract class Adapter<VH extends ViewHolder> {
private final AdapterDataObservable mObservable = new AdapterDataObservable();
private boolean mHasStableIds = false;
/**
* Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
* an item.
* <p>
* This new ViewHolder should be constructed with a new View that can represent the items
* of the given type. You can either create a new View manually or inflate it from an XML
* layout file.
* <p>
* The new ViewHolder will be used to display items of the adapter using
* {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
* different items in the data set, it is a good idea to cache references to sub views of
* the View to avoid unnecessary {@link View#findViewById(int)} calls.
*
* @param parent The ViewGroup into which the new View will be added after it is bound to
* an adapter position.
* @param viewType The view type of the new View.
*
* @return A new ViewHolder that holds a View of the given view type.
* @see #getItemViewType(int)
* @see #onBindViewHolder(ViewHolder, int)
*/
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
/**
* Called by RecyclerView to display the data at the specified position. This method should
* update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
* position.
* <p>
* Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
* again if the position of the item changes in the data set unless the item itself is
* invalidated or the new position cannot be determined. For this reason, you should only
* use the <code>position</code> parameter while acquiring the related data item inside
* this method and should not keep a copy of it. If you need the position of an item later
* on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
* have the updated adapter position.
*
* Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can
* handle efficient partial bind.
*
* @param holder The ViewHolder which should be updated to represent the contents of the
* item at the given position in the data set.
* @param position The position of the item within the adapter's data set.
*/
public abstract void onBindViewHolder(VH holder, int position);
/**
* Called by RecyclerView to display the data at the specified position. This method
* should update the contents of the {@link ViewHolder#itemView} to reflect the item at
* the given position.
* <p>
* Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
* again if the position of the item changes in the data set unless the item itself is
* invalidated or the new position cannot be determined. For this reason, you should only
* use the <code>position</code> parameter while acquiring the related data item inside
* this method and should not keep a copy of it. If you need the position of an item later
* on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
* have the updated adapter position.
* <p>
* Partial bind vs full bind:
* <p>
* The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or
* {@link #notifyItemRangeChanged(int, int, Object)}. If the payloads list is not empty,
* the ViewHolder is currently bound to old data and Adapter may run an efficient partial
* update using the payload info. If the payload is empty, Adapter must run a full bind.
* Adapter should not assume that the payload passed in notify methods will be received by
* onBindViewHolder(). For example when the view is not attached to the screen, the
* payload in notifyItemChange() will be simply dropped.
*
* @param holder The ViewHolder which should be updated to represent the contents of the
* item at the given position in the data set.
* @param position The position of the item within the adapter's data set.
* @param payloads A non-null list of merged payloads. Can be empty list if requires full
* update.
*/
public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
onBindViewHolder(holder, position);
}
从上面的部分代码可以知道,adapter 为抽象类,必然他的很多方法可以选择实现,通过oncreateview 传入自己的view,通过new vh方式进行了转换引入view,引入的view 内部将以vh类型继续向下使用,vh类猜测所做的事情就是对传入的view进行遍历获得view相关的位置,id等信息,下面的图也说明了这点
public ViewHolder(View itemView) {
if (itemView == null) {
throw new IllegalArgumentException("itemView may not be null");
}
this.itemView = itemView;
}
void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) {
addFlags(ViewHolder.FLAG_REMOVED);
offsetPosition(offset, applyToPreLayout);
mPosition = mNewPosition;
}
void offsetPosition(int offset, boolean applyToPreLayout) {
if (mOldPosition == NO_POSITION) {
mOldPosition = mPosition;
}
if (mPreLayoutPosition == NO_POSITION) {
mPreLayoutPosition = mPosition;
}
if (applyToPreLayout) {
mPreLayoutPosition += offset;
}
mPosition += offset;
if (itemView.getLayoutParams() != null) {
((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true;
}
}
至于如何将数据绑定到viewholder 上的,则是通过操作上面引入的viewholder 和对应位置上的数据进行手动绑定,最终包装好的vh对象将在adapter 中进行了返回和显示
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof VHNormal) {
VHNormal vhNormal = (VHNormal) holder;
NewsData newsData = getItem(position);
if (newsData != null) {
vhNormal.textView2.setText(newsData.getName());
}
}
}
既然提到的recyclerview 那顺便看了下notifyDataSetChanged()为何能刷新数据呢
* @see #notifyItemChanged(int)
* @see #notifyItemInserted(int)
* @see #notifyItemRemoved(int)
* @see #notifyItemRangeChanged(int, int)
* @see #notifyItemRangeInserted(int, int)
* @see #notifyItemRangeRemoved(int, int)
*/
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
//observable 为何物?
public static abstract class Adapter<VH extends ViewHolder> {
private final AdapterDataObservable mObservable = new AdapterDataObservable();
...
}
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
public boolean hasObservers() {
return !mObservers.isEmpty();
}
public void notifyChanged() {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
...
}
当view的数量和位置等改变的同时,利用的java 的观察者模式,进行了消息的分发通知
系统的RecyclerView控件 包含view 和data 两部分,view 和data是绑定关联关系,使用的时候如果所有的实现逻辑全面放到RecyclerView 这个控件上,无用代码会增加,RecyclerView控件巧妙的地方在于将重心任务通过适配器模式产生一个抽象的adapter类,里面的方法用户可以选择实现,里面的绑定逻辑帮系统已经实现了,调用的工作重点在重载对应的方法,创建传入view和绑定view 的操作即可,简化开发。
- java 中的实现 主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
下面以对象的适配器模式举例实现如下:
usb 现在市场分为usb 2.0 和usb 3.0 usb hub 所做的事情类似适配器,具体代码如下:
/**
* author : lzb
* e-mail :
* time : 2017/12/11
* desc : usb 2.0
* version: 1.0
*/
public class USB2 {
public void tranferByTwo(){
//todo
}
}
/**
* author : lzb
* e-mail :
* time : 2017/12/11
* desc : usb 3.0
* version: 1.0
*/
public class USB3 {
public void tranferByTwo() {
}
public void tranferThree(){
//todo
}
}
/**
* author : lzb
* e-mail :
* time : 2017/12/11
* desc : 转接接口
* version: 1.0
*/
public interface HubActionI {
void tranferByTwo();
void tranferByThree();
}
/**
* author : lzb
* e-mail :
* time : 2017/12/11
* desc : usb hub 兼容2.0,3.0
* version: 1.0
*/
public class USBHubAdpter implements HubActionI{
private USB2 usb2;
private USB3 usb3;
public USBHubAdpter(USB2 usb2,USB3 usb3) {
usb2 = usb2;
}
@Override
public void tranferByTwo() {
usb2.tranferByTwo();
}
@Override
public void tranferByThree() {
usb3.tranferThree();
}
}
/**
* author : lzb
* e-mail :
* time : 2017/12/11
* desc : 对象适配器模式
* version: 1.0
*/
public class Test {
public static void main(String[] args) {
USB2 usb2 = new USB2();
USB3 usb3 = new USB3();
//构造适配器
HubActionI hubActionI = new USBHubAdpter(usb2,usb3);
//2.0传输
hubActionI.tranferByTwo();
//3.0传输
hubActionI.tranferByThree();
}
}
一个对象转换成满足另一个新接口的对象时,创建一个Adapter类,持有原类的一个实例,在Adapter类的方法中,调用实例的方法。简单说上面的例子通过一个中间类进行了包装。