我们先讲适配器模式,后面的装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式,都是依赖于适配器模式中的对象的适配器模式为起源的。
适配器模式,简单来讲,就是某个类的接口和另一个接口不匹配,将某个类的接口转换成客户端期望的另一个接口表示。目的是消除由于接口不匹配所造成的类的兼容性问题。
我们还是模拟一个场景,市电都是220V的交流电,但是手机充电是5V的直流电,电脑运行的是12V的直流电,这里我们就需要两个不同的充电器,一个将220V的交流电转化为5V直流,一个需要将220V直流电转化为12V直流。这两个充电器就是我们的适配器了。
原始市电:
/**
* @author: hx
* @Time: 2019/5/7
* @Description: Volt220
*/
public class Volt220 {
public int output220vAC(){
System.out.println("输出220v交流电");
return 220;
}
}
下面是需要转换成的目标接口:
/**
* @author: hx
* @Time: 2019/5/7
* @Description: Volt5
*/
public interface Volt5 {
/**
* 获取5v的直流电
*/
int output5vDC();
}
/**
* @author: hx
* @Time: 2019/5/7
* @Description: Volt12
*/
public interface Volt12 {
/**
* 输出12v直流电
* @return
*/
int output12vDC();
}
接下来是所需的适配器:
/**
* @author: hx
* @Time: 2019/5/7
* @Description: AdapterVolt220ACTo5DC
*/
public class AdapterVolt220ACTo5DC extends Volt220 implements Volt5 {
/**
* 转化电流
* @return
*/
private int volt220ACTo5DC(){
int dc = output220vAC()/44;
System.out.println("将220V交流电转化为5V直流");
return dc;
}
@Override
public int output5vDC() {
return volt220ACTo5DC();
}
}
/**
* @author: hx
* @Time: 2019/5/7
* @Description: AdapterVolt220ACTo5DC
*/
public class AdapterVolt220ACTo12DC extends Volt220 implements Volt12 {
/**
* 转化电流
* @return
*/
private int volt220ACTo12DC(){
int dc = output220vAC()/18;
System.out.println("将220V交流电转化为12V直流");
return dc;
}
@Override
public int output12vDC() {
return volt220ACTo12DC();
}
}
运行一下看结果
public static void main(String[] args) throws CloneNotSupportedException {
Volt5 volt5 = new AdapterVolt220ACTo5DC();
Volt12 volt12 = new AdapterVolt220ACTo12DC();
volt5.output5vDC();
volt12.output12vDC();
}
输出结果:
输出220v交流电
将220V交流电转化为5V直流
输出220v交流电
将220V交流电转化为12V直流
具体对输出有什么要求,可以全部都定义在目标接口中,在adapter实现各个接口时,作出不同的适配方法。
对象的适配器模式其实和类的适配器模式没有太大的区别,只是将继承被适配的对象换成了内部对被适配对象的持有。
/**
* @author: hx
* @Time: 2019/5/7
* @Description: AdapterVolt220ACTo5DC
*/
public class AdapterVolt220ACTo5DC implements Volt5 {
private Volt220 mVolt220;
public AdapterVolt220ACTo5DC(Volt220 volt220) {
mVolt220 = volt220;
}
/**
* 转化电流
* @return
*/
private int volt220ACTo5DC(){
int dc = mVolt220.output220vAC()/44;
System.out.println("将220V交流电转化为5V直流");
return dc;
}
@Override
public int output5vDC() {
return volt220ACTo5DC();
}
}
/**
* @author: hx
* @Time: 2019/5/7
* @Description: AdapterVolt220ACTo5DC
*/
public class AdapterVolt220ACTo12DC implements Volt12 {
private Volt220 mVolt220;
public AdapterVolt220ACTo12DC(Volt220 volt220) {
mVolt220 = volt220;
}
/**
* 转化电流
* @return
*/
private int volt220ACTo12DC(){
int dc = mVolt220.output220vAC()/18;
System.out.println("将220V交流电转化为12V直流");
return dc;
}
@Override
public int output12vDC() {
return volt220ACTo12DC();
}
}
看调用和输出的结果
public static void main(String[] args) throws CloneNotSupportedException {
Volt220 volt220 = new Volt220();
Volt5 volt5 = new AdapterVolt220ACTo5DC(volt220);
Volt12 volt12 = new AdapterVolt220ACTo12DC(volt220);
volt5.output5vDC();
volt12.output12vDC();
}
输出结果:
输出220v交流电
将220V交流电转化为5V直流
输出220v交流电
将220V交流电转化为12V直流
接口的适配器,很好理解,就是一个接口里面方法太多,有时候我们只需要用到其中部分的功能,所以下写一个抽象类实现该接口所有方法,然后继承这个抽象类,复写我们需要的方法。
这个我们看一个源码
这个是原始接口
public interface Adapter {
/**
* Register an observer that is called when changes happen to the data used by this adapter.
*
* @param observer the object that gets notified when the data set changes.
*/
void registerDataSetObserver(DataSetObserver observer);
/**
* Unregister an observer that has previously been registered with this
* adapter via {@link #registerDataSetObserver}.
*
* @param observer the object to unregister.
*/
void unregisterDataSetObserver(DataSetObserver observer);
/**
* How many items are in the data set represented by this Adapter.
*
* @return Count of items.
*/
int getCount();
/**
* Get the data item associated with the specified position in the data set.
*
* @param position Position of the item whose data we want within the adapter's
* data set.
* @return The data at the specified position.
*/
Object getItem(int position);
/**
* Get the row id associated with the specified position in the list.
*
* @param position The position of the item within the adapter's data set whose row id we want.
* @return The id of the item at the specified position.
*/
long getItemId(int position);
/**
* Indicates whether the item ids are stable across changes to the
* underlying data.
*
* @return True if the same id always refers to the same object.
*/
boolean hasStableIds();
/**
* Get a View that displays the data at the specified position in the data set. You can either
* create a View manually or inflate it from an XML layout file. When the View is inflated, the
* parent View (GridView, ListView...) will apply default layout parameters unless you use
* {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)}
* to specify a root view and to prevent attachment to the root.
*
* @param position The position of the item within the adapter's data set of the item whose view
* we want.
* @param convertView The old view to reuse, if possible. Note: You should check that this view
* is non-null and of an appropriate type before using. If it is not possible to convert
* this view to display the correct data, this method can create a new view.
* Heterogeneous lists can specify their number of view types, so that this View is
* always of the right type (see {@link #getViewTypeCount()} and
* {@link #getItemViewType(int)}).
* @param parent The parent that this view will eventually be attached to
* @return A View corresponding to the data at the specified position.
*/
View getView(int position, View convertView, ViewGroup parent);
/**
* An item view type that causes the {@link AdapterView} to ignore the item
* view. For example, this can be used if the client does not want a
* particular view to be given for conversion in
* {@link #getView(int, View, ViewGroup)}.
*
* @see #getItemViewType(int)
* @see #getViewTypeCount()
*/
static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;
/**
* Get the type of View that will be created by {@link #getView} for the specified item.
*
* @param position The position of the item within the adapter's data set whose view type we
* want.
* @return An integer representing the type of View. Two views should share the same type if one
* can be converted to the other in {@link #getView}. Note: Integers must be in the
* range 0 to {@link #getViewTypeCount} - 1. {@link #IGNORE_ITEM_VIEW_TYPE} can
* also be returned.
* @see #IGNORE_ITEM_VIEW_TYPE
*/
int getItemViewType(int position);
/**
*
* Returns the number of types of Views that will be created by
* {@link #getView}. Each type represents a set of views that can be
* converted in {@link #getView}. If the adapter always returns the same
* type of View for all items, this method should return 1.
*
*
* This method will only be called when the adapter is set on the {@link AdapterView}.
*
*
* @return The number of types of Views that will be created by this adapter
*/
int getViewTypeCount();
static final int NO_SELECTION = Integer.MIN_VALUE;
/**
* @return true if this adapter doesn't contain any data. This is used to determine
* whether the empty view should be displayed. A typical implementation will return
* getCount() == 0 but since getCount() includes the headers and footers, specialized
* adapters might want a different behavior.
*/
boolean isEmpty();
/**
* Gets a string representation of the adapter data that can help
* {@link android.service.autofill.AutofillService} autofill the view backed by the adapter.
*
*
* It should only be set (i.e., non-{@code null} if the values do not represent PII
* (Personally Identifiable Information - sensitive data such as email addresses,
* credit card numbers, passwords, etc...). For
* example, it's ok to return a list of month names, but not a list of usernames. A good rule of
* thumb is that if the adapter data comes from static resources, such data is not PII - see
* {@link android.view.ViewStructure#setDataIsSensitive(boolean)} for more info.
*
* @return {@code null} by default, unless implementations override it.
*/
default @Nullable CharSequence[] getAutofillOptions() {
return null;
}
这个实现了原始接口的所有抽象方法
/**
* Common base class of common implementation for an {@link Adapter} that can be
* used in both {@link ListView} (by implementing the specialized
* {@link ListAdapter} interface) and {@link Spinner} (by implementing the
* specialized {@link SpinnerAdapter} interface).
*/
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
private CharSequence[] mAutofillOptions;
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
/**
* Notifies the attached observers that the underlying data is no longer valid
* or available. Once invoked this adapter is no longer valid and should
* not report further data set changes.
*/
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
public boolean areAllItemsEnabled() {
return true;
}
public boolean isEnabled(int position) {
return true;
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
public boolean isEmpty() {
return getCount() == 0;
}
@Override
public CharSequence[] getAutofillOptions() {
return mAutofillOptions;
}
/**
* Sets the value returned by {@link #getAutofillOptions()}
*/
public void setAutofillOptions(@Nullable CharSequence... options) {
mAutofillOptions = options;
}
最后这个针对自己的所需复写了部分的BaseAdapter方法
public class MenuAdapter extends BaseAdapter {
static final int ITEM_LAYOUT = R.layout.abc_popup_menu_item_layout;
MenuBuilder mAdapterMenu;
private int mExpandedIndex = -1;
private boolean mForceShowIcon;
private final boolean mOverflowOnly;
private final LayoutInflater mInflater;
public MenuAdapter(MenuBuilder menu, LayoutInflater inflater, boolean overflowOnly) {
mOverflowOnly = overflowOnly;
mInflater = inflater;
mAdapterMenu = menu;
findExpandedIndex();
}
public boolean getForceShowIcon() {
return mForceShowIcon;
}
public void setForceShowIcon(boolean forceShow) {
mForceShowIcon = forceShow;
}
@Override
public int getCount() {
ArrayList items = mOverflowOnly ?
mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
if (mExpandedIndex < 0) {
return items.size();
}
return items.size() - 1;
}
public MenuBuilder getAdapterMenu() {
return mAdapterMenu;
}
@Override
public MenuItemImpl getItem(int position) {
ArrayList items = mOverflowOnly ?
mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
position++;
}
return items.get(position);
}
@Override
public long getItemId(int position) {
// Since a menu item's ID is optional, we'll use the position as an
// ID for the item in the AdapterView
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
}
MenuView.ItemView itemView = (MenuView.ItemView) convertView;
if (mForceShowIcon) {
((ListMenuItemView) convertView).setForceShowIcon(true);
}
itemView.initialize(getItem(position), 0);
return convertView;
}
void findExpandedIndex() {
final MenuItemImpl expandedItem = mAdapterMenu.getExpandedItem();
if (expandedItem != null) {
final ArrayList items = mAdapterMenu.getNonActionItems();
final int count = items.size();
for (int i = 0; i < count; i++) {
final MenuItemImpl item = items.get(i);
if (item == expandedItem) {
mExpandedIndex = i;
return;
}
}
}
mExpandedIndex = -1;
}
@Override
public void notifyDataSetChanged() {
findExpandedIndex();
super.notifyDataSetChanged();
}
在android中,适配器模式是每个android的程序员都一定接触到过的,我们不管是用ListView还是RecycleViwe,都用到了适配器模式来适配数据原型和显示数据控件。
简单看一下ArrayAdapter这个类
public class ArrayAdapter extends BaseAdapter implements Filterable, ThemedSpinnerAdapter {
@Override
public int getCount() {
return mObjects.size();
}
@Override
public @Nullable T getItem(int position) {
return mObjects.get(position);
}
/**
* Returns the position of the specified item in the array.
*
* @param item The item to retrieve the position of.
*
* @return The position of the specified item.
*/
public int getPosition(@Nullable T item) {
return mObjects.indexOf(item);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public @NonNull View getView(int position, @Nullable View convertView,
@NonNull ViewGroup parent) {
return createViewFromResource(mInflater, position, convertView, parent, mResource);
}
}
主要的就是这个几个方法,传入构造传入上下文,显示需要的xml布局layout模板文件,还有原始数据集合。
在getCount中返回所有数据数量;getItem中返回某个位置的数据原型;getPosition返回某个item的位置;getTiemId返回当前位置;getView中,返回绑定了数据的view。
这里就将数据的两头,一边是原始数据模型,另一边则是显示的数据的ListView或者RecycleView之间进行了适配。