【轉】讀BaseAdapter的一點感悟

  適配器,做爲android應用層的開發中,具備很重要的做用。在諸如ListView,gallery等sdk中提供的展現批量數據的控件中,起到一個適配數據源的做用。sdk中已經爲咱們提供了一個簡單的而且適用性很廣的適配器SimpleAdapter,該類就是繼承自抽象類BaseAdapter實現的一個具體的適配器,而且能接收 List<? extends Map<String, ?>>形式的數據源格式的數據。可是不少狀況下,咱們使用ListView等容器的時候,每每要在渲染每一條數據的時候作一些咱們本身想作的事情,此時就要實現BaseAdapter來實現本身的一個Adapter。
  在進入正文以前,先說一下關於抽象類與接口的區別,如下摘自百度知道。
  你選擇使用接口和抽象類的依據是什麼? 接口和抽象類的概念不同。接口是對動做的抽象,抽象類是對根源的抽象。抽象類表示的是,這個對象是什麼。接口表示的是,這個對象能作什麼。好比,男人,女人,這兩個類(若是是類的話……),他們的抽象類是人。說明,他們都是人。人能夠吃東西,狗也能夠吃東西,你能夠把「吃東西」定義成一個接口,而後讓這些類去實現它.因此,在高級語言上,一個類只能繼承一個類(抽象類)(正如人不可能同時是生物和非生物),可是能夠實現多個接口(吃飯接口、走路接口)。第一點. 接口是抽象類的變體,接口中全部的方法都是抽象的。而抽象類是聲明方法的存在而不去實現它的類。 第二點. 接口能夠繼承,抽象類不行 第三點. 接口定義方法,不能實現,而抽象類能夠實現部分方法。 第四點. 接口中基本數據類型爲static 而抽類象不是的。當你關注一個事物的本質的時候,用抽象類;當你關注一個操做的時候,用接口。 接口能夠實現也能夠繼承,抽象類不行 抽象類的功能要遠超過接口,可是,定義抽象類的代價高。由於高級語言來講(從實際設計上來講也是)每一個類只能繼承一個類。在這個類中,你必須繼承或編寫出其全部子類的全部共性。雖然接口在功能上會弱化許多,可是它只是針對一個動做的描述。並且你能夠在一個類中同時實現多個接口。在設計階段會下降難度的。
  
  上面說的很清楚了,接口是對動做的抽象,而抽象類是對根源的抽象。抽象類表示的是這個對象是什麼,而接口表示的是這個對象能作什麼。同時在我閱讀BaseAdapter源碼的時候也有些感悟,BaseAdapter是一個抽象類,它實現了ListAdapter和SpinnerAdapter接口,而這兩個接口又實現了Adapter接口。咱們在構造本身的Adapter的時候常常要必須實現的getCount()和getView()方法都是在Adapter接口中定義的。
  在大牛們寫的源碼中,每每都是接口與抽象類同時使用。有關於BaseAdapter中的源碼,則把最基本的一些功能(能作什麼)抽象爲一個接口,放在最底層,然後經過繼承這個接口再抽象一些子接口,當有抽象類實現了這些子接口的時候,能夠選擇性的(這是抽象類容許的)實現接口中定義的功能,做爲一個具備統一功能的更具體的可是一樣以抽象的形式而存在,最後咱們要構造這種通過抽象的類型的具體類的時候,則須要將抽象類沒有實現的功能所有實現(這是必須的),甚至能夠重寫抽象類中已經實現的某些方法(通常狀況下沒必要這樣作),這時咱們能夠經過實例化咱們本身構造的具體類來完成一些工做了。
  這種層級關係讓我體驗到了從無到有的過程,也深入的理解了什麼叫抽象到具體的過程,在這種模型的基礎上,咱們能夠從容的擴展和具象化。沒必要擔憂功能的丟失或者偏離設計此類型的初衷,由簡化繁須要強大的根基,抽象類和接口的結合就是面向對象變成最強大的根基。
  下面貼出源碼來具體看
  既然上面說到了由簡化繁,那麼就從最基本的接口開始
  一、Adapterjava

/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;

/**
 * An Adapter object acts as a bridge between an {@link AdapterView} and the
 * underlying data for that view. The Adapter provides access to the data items.
 * The Adapter is also responsible for making a {@link android.view.View} for
 * each item in the data set.
 * 
 * @see android.widget.ArrayAdapter
 * @see android.widget.CursorAdapter
 * @see android.widget.SimpleCursorAdapter
 */
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 when the adapter is set on the
     * 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();
}

而後是繼承自這個接口的兩個接口
  二、ListAdapter、SpinnerAdapter
  (因爲內容很是少,則粘在一塊兒)android

package android.widget;

/**
 * Extended {@link Adapter} that is the bridge between a {@link ListView}
 * and the data that backs the list. Frequently that data comes from a Cursor,
 * but that is not
 * required. The ListView can display any data provided that it is wrapped in a
 * ListAdapter.
 */
public interface ListAdapter extends Adapter {

    /**
     * Indicates whether all the items in this adapter are enabled. If the
     * value returned by this method changes over time, there is no guarantee
     * it will take effect.  If true, it means all items are selectable and
     * clickable (there is no separator.)
     * 
     * @return True if all items are enabled, false otherwise.
     * 
     * @see #isEnabled(int) 
     */
    public boolean areAllItemsEnabled();

    /**
     * Returns true if the item at the specified position is not a separator.
     * (A separator is a non-selectable, non-clickable item).
     * 
     * The result is unspecified if position is invalid. An {@link ArrayIndexOutOfBoundsException}
     * should be thrown in that case for fast failure.
     *
     * @param position Index of the item
     * 
     * @return True if the item is not a separator
     * 
     * @see #areAllItemsEnabled() 
     */
    boolean isEnabled(int position);
}
import android.view.View;
import android.view.ViewGroup;

/**
 * Extended {@link Adapter} that is the bridge between a
 * {@link android.widget.Spinner} and its data. A spinner adapter allows to
 * define two different views: one that shows the data in the spinner itself
 * and one that shows the data in the drop down list when the spinner is
 * pressed.
 */
public interface SpinnerAdapter extends Adapter {
    /**
     * Gets a {@link android.view.View} that displays in the drop down popup
     * the data at the specified position in the data set.
     *
     * @param position index 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.
     * @param parent the parent that this view will eventually be attached to
     * @return a {@link android.view.View} corresponding to the data at the
     *         specified position.
     */
    public View getDropDownView(int position, View convertView, ViewGroup parent);
}

這裏BaseAdapter用到的是觀察者模式,在ListView的setAdapter裏註冊一個AdapterDataSetObserver觀察者,至關於在使用按鈕時候,對按鈕set一個OnClickListener。這裏的AdapterDataSetObserver是一個內部類,這個類繼承自DataSetObserver這個抽象類,而且重寫了onChanged()和onInvalidated()方法,而OnClickListener則是一個接口的子類,實現了這個接口的匿名類或者是實體類,重寫了OnClick()方法。
  三、BaseAdapterexpress

import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;

/**
 * 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();

    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;
    }
}

四、觀察者抽象類DataSetObserverapache

package android.database;

/**
 * Receives call backs when a data set has been changed, or made invalid. The typically data sets
 * that are observed are {@link Cursor}s or {@link android.widget.Adapter}s.
 * DataSetObserver must be implemented by objects which are added to a DataSetObservable.
 */
public abstract class DataSetObserver {
    /**
     * This method is called when the entire data set has changed,
     * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.
     */
    public void onChanged() {
        // Do nothing
    }

    /**
     * This method is called when the entire data becomes invalid,
     * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a
     * {@link Cursor}.
     */
    public void onInvalidated() {
        // Do nothing
    }
}

五、實現了功能的具體觀察者AdapterDataSetObserver----實則是抽象類AdapterView的一個內部類,而ListView類繼承自抽象類AbsListView,AbsListView繼承自抽象類AdapterView,其中這兩個抽象類的其餘實現咱們不關注,只關注有關於ListView的地方。app

  class AdapterDataSetObserver extends DataSetObserver {

        private Parcelable mInstanceState = null;

        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = getAdapter().getCount();

            // Detect the case where a cursor that was previously invalidated has
            // been repopulated with new data.
            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                    && mOldItemCount == 0 && mItemCount > 0) {
                AdapterView.this.onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            mDataChanged = true;

            if (AdapterView.this.getAdapter().hasStableIds()) {
                // Remember the current state for the case where our hosting activity is being
                // stopped and later restarted
                mInstanceState = AdapterView.this.onSaveInstanceState();
            }

            // Data is invalid so we should reset our state
            mOldItemCount = mItemCount;
            mItemCount = 0;
            mSelectedPosition = INVALID_POSITION;
            mSelectedRowId = INVALID_ROW_ID;
            mNextSelectedPosition = INVALID_POSITION;
            mNextSelectedRowId = INVALID_ROW_ID;
            mNeedSync = false;

            checkFocus();
            requestLayout();
        }

        public void clearSavedState() {
            mInstanceState = null;
        }
    }

六、觀察者操做類的抽象類Observable<T>
  爲了方便閱讀,刪去了註釋,只看方法名比較容易理解,這裏主要實現的是觀察者的註冊,實則是在一個ArrayList中不斷的添加觀察者DataSetObserver實體less

package android.database;

import java.util.ArrayList;

public abstract class Observable<T> {
    protected final ArrayList<T> mObservers = new ArrayList<T>();

    public void registerObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            if (mObservers.contains(observer)) {
                throw new IllegalStateException("Observer " + observer + " is already registered.");
            }
            mObservers.add(observer);
        }
    }

    public void unregisterObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            int index = mObservers.indexOf(observer);
            if (index == -1) {
                throw new IllegalStateException("Observer " + observer + " was not registered.");
            }
            mObservers.remove(index);
        }
    }

    public void unregisterAll() {
        synchronized(mObservers) {
            mObservers.clear();
        }
    }
}

七、觀察者操做類的具體實現DataSetObservable
  能夠看到,這裏定義了notifyChanged方法的實現,經過同步代碼塊逐條的對集合中的每一條數據進行OnChanged方法調用。ide

package android.database;

/**
 * A specialization of {@link Observable} for {@link DataSetObserver}
 * that provides methods for sending notifications to a list of
 * {@link DataSetObserver} objects.
 */
public class DataSetObservable extends Observable<DataSetObserver> {
    /**
     * Invokes {@link DataSetObserver#onChanged} on each observer.
     * Called when the contents of the data set have changed.  The recipient
     * will obtain the new contents the next time it queries the data set.
     */
    public void notifyChanged() {
        synchronized(mObservers) {
            // 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();
            }
        }
    }

    /**
     * Invokes {@link DataSetObserver#onInvalidated} on each observer.
     * Called when the data set is no longer valid and cannot be queried again,
     * such as when the data set has been closed.
     */
    public void notifyInvalidated() {
        synchronized (mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onInvalidated();
            }
        }
    }
}

八、最後就是ListView中setAdapter方法的實現了,小小的setAdapter方法中涉及到了BaseAdapter的引用,DataSetObserver的引用。
  這裏其實是經過入參傳入了咱們本身實現的Adapter,一個繼承自BaseAdapter的Adapter,而且在方法中已經實例化了一個上文提到的AdapterDataSetObserver,一個繼承自DataSetObserver的具體觀察者。ui

@Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        resetList();
        mRecycler.clear();

        if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }

        mOldSelectedPosition = INVALID_POSITION;
        mOldSelectedRowId = INVALID_ROW_ID;

        // AbsListView#setAdapter will update choice mode states.
        super.setAdapter(adapter);

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
            checkFocus();

            mDataSetObserver = new AdapterDataSetObserver();//AdapterDataSetObserver是ListView的基類AdapterView的內部類
            mAdapter.registerDataSetObserver(mDataSetObserver);//註冊一個觀察者

            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());

            int position;
            if (mStackFromBottom) {
                position = lookForSelectablePosition(mItemCount - 1, false);
            } else {
                position = lookForSelectablePosition(0, true);
            }
            setSelectedPositionInt(position);
            setNextSelectedPositionInt(position);

            if (mItemCount == 0) {
                // Nothing selected
                checkSelectionChanged();
            }
        } else {
            mAreAllItemsSelectable = true;
            checkFocus();
            // Nothing selected
            checkSelectionChanged();
        }

        requestLayout();//更新視圖
    }
相關文章
相關標籤/搜索