android设计模式二十三式(六)——适配器模式(Adapter)

适配器模式

我们先讲适配器模式,后面的装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式,都是依赖于适配器模式中的对象的适配器模式为起源的。

适配器模式,简单来讲,就是某个类的接口和另一个接口不匹配,将某个类的接口转换成客户端期望的另一个接口表示。目的是消除由于接口不匹配所造成的类的兼容性问题。

1.类的适配器模式

我们还是模拟一个场景,市电都是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实现各个接口时,作出不同的适配方法。

2.对象的适配器模式

对象的适配器模式其实和类的适配器模式没有太大的区别,只是将继承被适配的对象换成了内部对被适配对象的持有。

/**
 * @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直流

3.接口的适配器模式

接口的适配器,很好理解,就是一个接口里面方法太多,有时候我们只需要用到其中部分的功能,所以下写一个抽象类实现该接口所有方法,然后继承这个抽象类,复写我们需要的方法。

这个我们看一个源码

这个是原始接口

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之间进行了适配。

你可能感兴趣的:(android,设计模式)