zoukankan      html  css  js  c++  java
  • 【转】读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中的源码,则把最基本的一些功能(能做什么)抽象为一个接口,放在最底层,而后通过继承这个接口再抽象一些子接口,当有抽象类实现了这些子接口的时候,可以选择性的(这是抽象类允许的)实现接口中定义的功能,作为一个具有统一功能的更具体的但是同样以抽象的形式而存在,最后我们要构造这种经过抽象的类型的具体类的时候,则需要将抽象类没有实现的功能全部实现(这是必须的),甚至可以重写抽象类中已经实现的某些方法(一般情况下不必这样做),这时我们可以通过实例化我们自己构造的具体类来完成一些工作了。
      这种层级关系让我体验到了从无到有的过程,也深刻的理解了什么叫抽象到具体的过程,在这种模型的基础上,我们可以从容的扩展和具象化。不必担心功能的丢失或者偏离设计此类型的初衷,由简化繁需要强大的根基,抽象类和接口的结合就是面向对象变成最强大的根基。
      下面贴出源码来具体看
      既然上面说到了由简化繁,那么就从最基本的接口开始
      1、Adapter

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

    然后是继承自这个接口的两个接口
      2、ListAdapter、SpinnerAdapter
      (由于内容非常少,则粘在一起)

    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()方法。
      3、BaseAdapter

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

    4、观察者抽象类DataSetObserver

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

    5、实现了功能的具体观察者AdapterDataSetObserver----实则是抽象类AdapterView的一个内部类,而ListView类继承自抽象类AbsListView,AbsListView继承自抽象类AdapterView,其中这两个抽象类的其他实现我们不关注,只关注有关于ListView的地方。

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

    6、观察者操作类的抽象类Observable<T>
      为了方便阅读,删去了注释,只看方法名比较容易理解,这里主要实现的是观察者的注册,实则是在一个ArrayList中不断的添加观察者DataSetObserver实体

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

    7、观察者操作类的具体实现DataSetObservable
      可以看到,这里定义了notifyChanged方法的实现,通过同步代码块逐条的对集合中的每一条数据进行OnChanged方法调用。

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

    8、最后就是ListView中setAdapter方法的实现了,小小的setAdapter方法中涉及到了BaseAdapter的引用,DataSetObserver的引用。
      这里实际上是通过入参传入了我们自己实现的Adapter,一个继承自BaseAdapter的Adapter,并且在方法中已经实例化了一个上文提到的AdapterDataSetObserver,一个继承自DataSetObserver的具体观察者。

    @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();//更新视图
        }
  • 相关阅读:
    python day05
    python day04
    python day03
    python day02
    计算机基本了解
    流程控制
    MFC程序中创建文件夹(文件路径)
    svn移动目录并且保存历史日志
    C++单例模式的问题
    PtInRect 的详细范围
  • 原文地址:https://www.cnblogs.com/pili/p/6889207.html
Copyright © 2011-2022 走看看