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);
    
    /**
     * <p>
     * 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.
     * </p>
     * <p>
     * This method will only be called when the adapter is set on the {@link AdapterView}.
     * </p>
     * 
     * @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.
     *
     * <p>
     * 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<MenuItemImpl> 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<MenuItemImpl> 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<MenuItemImpl> 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<T> 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之間進行了適配。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章